Skip to content

Controllers

Lilya embraces both functional and object-oriented programming (OOP) methodologies. Within the Lilya framework, the OOP paradigm is referred to as a controller, a nomenclature inspired by other notable technologies.

The Controller serves as the orchestrator for handling standard HTTP requests and managing WebSocket sessions.

Internally, the Controller and WebSocketController implement the same response wrappers as the Path and WebSocketPath making sure that remains as one source of truth and that also means that the auto discovery of the parameters also works here.

The Controller class

This object also serves as ASGI application, which means that embraces the internal implementation of the __call__ and dispatches the requests.

This is also responsible for implementing the HTTP dispatching of the requests, only.

from lilya.controllers import Controller
from lilya.requests import Request
from lilya.responses import Ok


class ASGIApp(Controller):

    async def get(self, request: Request):
        return Ok({"detail": "Hello, world!"})

    async def post(self):
        return Ok({"detail": "Hello, world!"})

When employing a Lilya application instance for routing management, you have the option to dispatch to a Controller class.

Warning

It's crucial to dispatch directly to the class, not to an instance of the class.

Here's an example for clarification:

from lilya.apps import Lilya
from lilya.controllers import Controller
from lilya.responses import Ok, Response
from lilya.routing import Path


class ASGIAppController(Controller):
    async def get(self):
        return Response("Hello, World!")


class AuthController(Controller):
    async def get(self, username: str):
        return Ok({"message": f"Hello, {username}"})


app = Lilya(
    routes=[
        Path("/", handler=ASGIAppController),
        Path("/{username}", handler=AuthController),
    ]
)

In this scenario, the ASGIApp class is dispatched, not an instance of it.

Controller classes, when encountering request methods that do not map to a corresponding handler, will automatically respond with 405 Method Not Allowed responses.

The WebSocketController class

The WebSocketController class serves as an ASGI application, encapsulating the functionality of a WebSocket instance.

The ASGI connection scope is accessible on the endpoint instance through .scope and features an attribute called encoding. This attribute, which may be optionally set, is utilized to validate the expected WebSocket data in the on_receive method.

The available encoding types are:

  • 'json'
  • 'bytes'
  • 'text'

There are three methods that can be overridden to handle specific ASGI WebSocket message types:

  1. async def on_connect(websocket, **kwargs)
  2. async def on_receive(websocket, data)
  3. async def on_disconnect(websocket, close_code)
from typing import Any

from lilya.controllers import WebSocketController
from lilya.websockets import WebSocket


class ASGIApp(WebSocketController):
    encoding = "bytes"

    async def on_connect(self, websocket: WebSocket):
        await websocket.accept()

    async def on_receive(self, websocket: WebSocket, data: Any):
        await websocket.send_bytes(b"Message: " + data)

    async def on_disconnect(self, websocket: WebSocket, close_code: int): ...

The WebSocketController is also compatible with the Lilya application class.

from typing import Any

from lilya.apps import Lilya
from lilya.controllers import Controller, WebSocketController
from lilya.responses import HTMLResponse
from lilya.routing import Path, WebSocketPath
from lilya.websockets import WebSocket

html = """
<!DOCTYPE html>
<html>
    <head>
        <title>Chat</title>
    </head>
    <body>
        <h1>Chat</h1>
        <form action="" onsubmit="sendMessage(event)">
            <input type="text" id="messageText" autocomplete="off"/>
            <button>Submit</button>
        </form>
        <ul id='messages'>
        </ul>
        <script>
            var ws = new WebSocket("ws://localhost:8000/websocket");
            ws.onmessage = function(event) {
                var messages = document.getElementById('messages')
                var message = document.createElement('li')
                var content = document.createTextNode(event.data)
                message.appendChild(content)
                messages.appendChild(message)
            };
            function sendMessage(event) {
                var input = document.getElementById("messageText")
                ws.send(input.value)
                input.value = ''
                event.preventDefault()
            }
        </script>
    </body>
</html>
"""


class HomepageController(Controller):
    async def get(self):
        return HTMLResponse(html)


class EchoController(WebSocketController):
    encoding = "text"

    async def on_receive(self, websocket: WebSocket, data: Any):
        await websocket.send_text(f"Message text was: {data}")


app = Lilya(
    routes=[
        Path("/", HomepageController),
        WebSocketPath()("/websocket", EchoController),
    ]
)