genro-asgi Architecture Overview

Version: 2.0.0 Status: SOURCE OF TRUTH Last Updated: 2026-06-28


What is genro-asgi

genro-asgi is a minimal ASGI framework that provides:

  • AsgiServer: a demultiplexer that mounts one application per first-level URL segment and relays the ASGI call to it

  • Configuration: a Python config.py builder recipe rendered onto the server

  • Request System: transport-agnostic request handling (HTTP, WebSocket/WSX)

  • Response System: a single Response class with auto content-type detection

  • Executors: thread/process pools for blocking and CPU-bound work

  • WSX Protocol: RPC message format over WebSocket


Core Principles

1. Instance isolation, no globals

The server is an instance with its own state. Every child object holds a semantic reference to its parent (self.server, self.application, …). There are no module-level singletons; del server collects everything.

2. The server demultiplexes, apps route

AsgiServer does no routing of its own. The Dispatcher picks the app from the first path segment and relays the whole ASGI call; each app does its own routing in handle_request.

3. Config-driven

The server boots from a config.py whose ServerConfiguration (a subclass of AsgiConfigBuilder) is rendered onto it. The builder recipe is the single source of the server’s topology (apps, middleware, auth, databases, openapi).

4. Transport agnostic

Handlers receive a BaseRequest; the same handler works over HTTP or WebSocket (WSX), which uses MsgRequest(BaseRequest).


Module Structure

src/genro_asgi/
├── __init__.py           # Public exports (52 symbols)
├── __main__.py           # CLI entry point
├── types.py              # ASGI type definitions
├── exceptions.py         # HTTPException, WebSocket exceptions, Redirect
├── request.py            # BaseRequest, HttpRequest, MsgRequest, RequestRegistry
├── response.py           # Response (set_result / set_error), make_cookie
├── websocket.py          # WebSocket connection wrapper
├── lifespan.py           # Lifespan, ServerLifespan
├── loader.py             # AppLoader (isolated module loading)
├── resources.py          # ResourceLoader (hierarchical fallback)
├── storage.py            # LocalStorage, StorageNode
├── db.py                 # AsgiDbHandlerBase
├── config/               # config.py builder model (the live configuration)
├── server/               # server.py, dispatcher.py, auth_mixin.py, worker.py, server_app/
├── applications/         # AsgiApplication, OpenApiApplication, McpApplication
├── authentication/       # Backward-compatible shim (logic in server/auth_mixin.py)
├── datastructures/       # Headers, URL, QueryParams, State, Address
├── executors/            # BaseExecutor, LocalExecutor, ThreadExecutor, ExecutorRegistry
├── middleware/           # Auto-discovery + 7 middleware classes
├── routers/              # StaticRouter
├── session/              # Session, Avatar, MemorySessionStore
├── sys_applications/     # genro_api, login_page, plugin_config, swagger
├── utils/                # split_and_strip and small helpers
└── wsx/                  # WSX protocol (handler, protocol, registry)

Architecture Documents

Document

Description

01-server.md

AsgiServer, config.py boot, mount, dispatch

02-request-system.md

BaseRequest, HttpRequest, MsgRequest, RequestRegistry

03-response-system.md

The single Response class

04-executors.md

Blocking/CPU task execution

05-lifespan.md

Startup/shutdown lifecycle

06-wsx-protocol.md

WSX message format

07-streaming.md

Streaming and protections

08-routing.md

Demultiplex + per-app routing

09-auth-context.md

Authentication and context


Quick Start

The server boots from a config.py. You do not subclass AsgiServer; you write a ServerConfiguration recipe and point the server at it.

# config.py
from genro_asgi.config import AsgiConfigBuilder
from my_app import Application as MyApp     # an AsgiApplication subclass


class ServerConfiguration(AsgiConfigBuilder):
    def main(self, root):
        root.server(host="127.0.0.1", port=8000)
        root.middleware(cors=True)
        apps = root.applications(default="main")
        apps.application(code="main", app_class=MyApp)
from genro_asgi import AsgiServer

server = AsgiServer("config.py")  # the config.py directory is the server dir
server.run()                      # uvicorn on the configured host/port

Key Design Decisions

No envelope abstraction

Requests are the trackable unit; there is no RequestEnvelope/ResponseEnvelope wrapper. RequestRegistry tracks the in-flight request.

Single request.py and single Response

All request classes live in request.py; there is a single Response class in response.py (no JSONResponse/HTMLResponse/… hierarchy). set_result() picks the content type from the result.

WSX is the message protocol; handling lives in request.py

wsx/ holds the WSX message format (protocol.py), the connection handler (handler.py) and the connection registry (registry.py). WSX request handling uses MsgRequest(BaseRequest) from request.py.


Copyright: Softwell S.r.l. (2025-2026) License: Apache License 2.0