Skip to content

Probes and control

Single-purpose handlers that do not carry an interaction.

hololinked.server.http.handlers.ThingDescriptionHandler

Bases: BaseHandler

Thing Description handler.

Source code in repo/hololinked/hololinked/server/http/handlers.py
class ThingDescriptionHandler(BaseHandler):
    """Thing Description handler."""

    def initialize(  # ty: ignore[invalid-method-override]
        self,
        resource: InteractionAffordance | PropertyAffordance,
        config: RuntimeConfig,
        logger: structlog.stdlib.BoundLogger,
        thing: Thing,
        owner_inst: HTTPServer,
        metadata: HandlerMetadata | None = None,
    ) -> None:
        """Set up the handler along with the service that generates the Thing Description."""
        super().initialize(
            resource=resource,
            config=config,
            logger=logger,
            metadata=metadata,
            thing=thing,
        )
        self.thing_description = self.config.thing_description_service(
            resource=resource,
            config=config,
            logger=logger,
            server=owner_inst,
            thing=thing,
        )

    async def get(self):
        """Generate and return the Thing Description of the `Thing`."""  # noqa: DOC501
        if not await self.has_access_control():
            self.finish()
            return

        try:
            self.set_custom_default_headers()
            _, _, _, body = self.get_execution_parameters()
            body = body.deserialize() or dict()
            if not isinstance(body, dict):
                raise ValueError("request body must be or convertable to JSON when supplied as path parameters")

            ignore_errors = body.get("ignore_errors", False)
            skip_names = body.get("skip_names", [])
            authority = body.get("authority", None)
            use_localhost = body.get("use_localhost", False)

            TD = await self.thing_description.generate(
                ignore_errors=ignore_errors,
                skip_names=skip_names,
                use_localhost=use_localhost,
                authority=authority,
            )

            self.set_status(200, "ok")
            self.set_header("Content-Type", "application/json")
            self.write(TD)
        except Exception as ex:
            self.set_status(500, str(ex).replace("\n", " "))
        self.finish()

Functions

initialize

initialize(resource: InteractionAffordance | PropertyAffordance, config: RuntimeConfig, logger: BoundLogger, thing: Thing, owner_inst: HTTPServer, metadata: HandlerMetadata | None = None) -> None

Set up the handler along with the service that generates the Thing Description.

Source code in repo/hololinked/hololinked/server/http/handlers.py
def initialize(  # ty: ignore[invalid-method-override]
    self,
    resource: InteractionAffordance | PropertyAffordance,
    config: RuntimeConfig,
    logger: structlog.stdlib.BoundLogger,
    thing: Thing,
    owner_inst: HTTPServer,
    metadata: HandlerMetadata | None = None,
) -> None:
    """Set up the handler along with the service that generates the Thing Description."""
    super().initialize(
        resource=resource,
        config=config,
        logger=logger,
        metadata=metadata,
        thing=thing,
    )
    self.thing_description = self.config.thing_description_service(
        resource=resource,
        config=config,
        logger=logger,
        server=owner_inst,
        thing=thing,
    )

get async

get()

Generate and return the Thing Description of the Thing.

Source code in repo/hololinked/hololinked/server/http/handlers.py
async def get(self):
    """Generate and return the Thing Description of the `Thing`."""  # noqa: DOC501
    if not await self.has_access_control():
        self.finish()
        return

    try:
        self.set_custom_default_headers()
        _, _, _, body = self.get_execution_parameters()
        body = body.deserialize() or dict()
        if not isinstance(body, dict):
            raise ValueError("request body must be or convertable to JSON when supplied as path parameters")

        ignore_errors = body.get("ignore_errors", False)
        skip_names = body.get("skip_names", [])
        authority = body.get("authority", None)
        use_localhost = body.get("use_localhost", False)

        TD = await self.thing_description.generate(
            ignore_errors=ignore_errors,
            skip_names=skip_names,
            use_localhost=use_localhost,
            authority=authority,
        )

        self.set_status(200, "ok")
        self.set_header("Content-Type", "application/json")
        self.write(TD)
    except Exception as ex:
        self.set_status(500, str(ex).replace("\n", " "))
    self.finish()

Serves the Thing Description document, generated by ThingDescriptionService.

hololinked.server.http.handlers.LivenessProbeHandler

Bases: BaseHandler

Liveness probe handler.

Source code in repo/hololinked/hololinked/server/http/handlers.py
class LivenessProbeHandler(BaseHandler):
    """Liveness probe handler."""

    def initialize(  # ty: ignore[invalid-method-override]
        self,
        config: RuntimeConfig,
        logger: structlog.stdlib.BoundLogger,
        owner_inst: HTTPServer | None = None,
    ) -> None:
        """Set up the handler with the HTTP server it probes."""
        self.config = config
        self.logger = logger.bind(path=self.request.path)
        self.server = owner_inst

    async def get(self):
        """Report that the server is alive and accepting requests."""
        self.set_status(200, "ok")
        self.set_custom_default_headers()
        self.finish()

Functions

initialize

initialize(config: RuntimeConfig, logger: BoundLogger, owner_inst: HTTPServer | None = None) -> None

Set up the handler with the HTTP server it probes.

Source code in repo/hololinked/hololinked/server/http/handlers.py
def initialize(  # ty: ignore[invalid-method-override]
    self,
    config: RuntimeConfig,
    logger: structlog.stdlib.BoundLogger,
    owner_inst: HTTPServer | None = None,
) -> None:
    """Set up the handler with the HTTP server it probes."""
    self.config = config
    self.logger = logger.bind(path=self.request.path)
    self.server = owner_inst

get async

get()

Report that the server is alive and accepting requests.

Source code in repo/hololinked/hololinked/server/http/handlers.py
async def get(self):
    """Report that the server is alive and accepting requests."""
    self.set_status(200, "ok")
    self.set_custom_default_headers()
    self.finish()

hololinked.server.http.handlers.ReadinessProbeHandler

Bases: BaseHandler

Readiness probe handler.

Source code in repo/hololinked/hololinked/server/http/handlers.py
class ReadinessProbeHandler(BaseHandler):
    """Readiness probe handler."""

    def initialize(  # ty: ignore[invalid-method-override]
        self,
        config: RuntimeConfig,
        logger: structlog.stdlib.BoundLogger,
        owner_inst: HTTPServer | None = None,
    ) -> None:
        """Set up the handler with the HTTP server it probes."""
        self.config = config
        self.logger = logger.bind(path=self.request.path)
        self.server = owner_inst

    async def get(self):
        """Report whether every served `Thing` is connected and answering a ping."""  # noqa: DOC501
        self.set_custom_default_headers()
        try:
            things = list(self.server.things.values()) if self.server else []
            if not things:
                self.set_status(200, "ok")  # nothing served, so nothing to be ready for
                self.finish()
                return
            eventloops = [thing.eventloop for thing in things]
            if any(eventloop is None or not eventloop.is_running for eventloop in eventloops):
                raise RuntimeError("the event loop is not running yet, retry later")
            replies = await asyncio.gather(
                *[
                    eventloop.execute(Operation(thing_id=thing.id, objekt="ping", operation=Operations.invokeaction))
                    for thing, eventloop in zip(things, eventloops, strict=True)
                    if eventloop is not None
                ]
            )
            if any(reply.is_error or reply.timed_out for reply in replies):
                self.set_status(500, "not all things are ready")
            else:
                self.set_status(200, "ok")
        except Exception as ex:
            self.logger.error(f"error while checking readiness - {str(ex)}")
            self.set_status(500, f"error while checking readiness - {str(ex)}")
        self.finish()

Functions

initialize

initialize(config: RuntimeConfig, logger: BoundLogger, owner_inst: HTTPServer | None = None) -> None

Set up the handler with the HTTP server it probes.

Source code in repo/hololinked/hololinked/server/http/handlers.py
def initialize(  # ty: ignore[invalid-method-override]
    self,
    config: RuntimeConfig,
    logger: structlog.stdlib.BoundLogger,
    owner_inst: HTTPServer | None = None,
) -> None:
    """Set up the handler with the HTTP server it probes."""
    self.config = config
    self.logger = logger.bind(path=self.request.path)
    self.server = owner_inst

get async

get()

Report whether every served Thing is connected and answering a ping.

Source code in repo/hololinked/hololinked/server/http/handlers.py
async def get(self):
    """Report whether every served `Thing` is connected and answering a ping."""  # noqa: DOC501
    self.set_custom_default_headers()
    try:
        things = list(self.server.things.values()) if self.server else []
        if not things:
            self.set_status(200, "ok")  # nothing served, so nothing to be ready for
            self.finish()
            return
        eventloops = [thing.eventloop for thing in things]
        if any(eventloop is None or not eventloop.is_running for eventloop in eventloops):
            raise RuntimeError("the event loop is not running yet, retry later")
        replies = await asyncio.gather(
            *[
                eventloop.execute(Operation(thing_id=thing.id, objekt="ping", operation=Operations.invokeaction))
                for thing, eventloop in zip(things, eventloops, strict=True)
                if eventloop is not None
            ]
        )
        if any(reply.is_error or reply.timed_out for reply in replies):
            self.set_status(500, "not all things are ready")
        else:
            self.set_status(200, "ok")
    except Exception as ex:
        self.logger.error(f"error while checking readiness - {str(ex)}")
        self.set_status(500, f"error while checking readiness - {str(ex)}")
    self.finish()

Liveness answers whether the server process is up; readiness answers whether it is ready to serve the Thing.

hololinked.server.http.handlers.StopHandler

Bases: BaseHandler

Stops the tornado HTTP server.

Source code in repo/hololinked/hololinked/server/http/handlers.py
class StopHandler(BaseHandler):
    """Stops the tornado HTTP server."""

    def initialize(  # ty: ignore[invalid-method-override]
        self,
        config: RuntimeConfig,
        logger: structlog.stdlib.BoundLogger,
        owner_inst: HTTPServer,
    ) -> None:
        """Set up the handler with the HTTP server it stops."""
        self.config = config
        self.logger = logger.bind(path=self.request.path)
        self.allowed_clients = self.config.allowed_clients
        self.security_schemes = self.config.security_schemes
        self.server = owner_inst

    async def post(self):
        """Schedule a graceful shutdown of the HTTP server."""
        if not await self.has_access_control():
            return
        try:
            self.set_custom_default_headers()
            # Stop the Tornado server
            origin = self.request.headers.get("Origin")
            self.logger.info(f"stopping HTTP server as per client request from {origin}, scheduling a stop message...")
            # create a task in current loop
            loop = get_current_async_loop()
            loop.create_task(self.server.async_stop())
            # dont call it in sequence, its not clear whether its designed for that
            self.set_status(204, "ok")
        except Exception as ex:
            self.logger.error(f"error while stopping HTTP server - {str(ex)}")
            self.set_status(500, f"error while stopping HTTP server - {str(ex)}")
        self.finish()

Functions

initialize

initialize(config: RuntimeConfig, logger: BoundLogger, owner_inst: HTTPServer) -> None

Set up the handler with the HTTP server it stops.

Source code in repo/hololinked/hololinked/server/http/handlers.py
def initialize(  # ty: ignore[invalid-method-override]
    self,
    config: RuntimeConfig,
    logger: structlog.stdlib.BoundLogger,
    owner_inst: HTTPServer,
) -> None:
    """Set up the handler with the HTTP server it stops."""
    self.config = config
    self.logger = logger.bind(path=self.request.path)
    self.allowed_clients = self.config.allowed_clients
    self.security_schemes = self.config.security_schemes
    self.server = owner_inst

post async

post()

Schedule a graceful shutdown of the HTTP server.

Source code in repo/hololinked/hololinked/server/http/handlers.py
async def post(self):
    """Schedule a graceful shutdown of the HTTP server."""
    if not await self.has_access_control():
        return
    try:
        self.set_custom_default_headers()
        # Stop the Tornado server
        origin = self.request.headers.get("Origin")
        self.logger.info(f"stopping HTTP server as per client request from {origin}, scheduling a stop message...")
        # create a task in current loop
        loop = get_current_async_loop()
        loop.create_task(self.server.async_stop())
        # dont call it in sequence, its not clear whether its designed for that
        self.set_status(204, "ok")
    except Exception as ex:
        self.logger.error(f"error while stopping HTTP server - {str(ex)}")
        self.set_status(500, f"error while stopping HTTP server - {str(ex)}")
    self.finish()

Shuts the server down over HTTP. POST only.