granian

repository·master·Indexed 26 days ago

https://github.com/emmett-framework/granian

A high-performance Rust-based HTTP server for Python applications. It serves as a single-dependency alternative to Gunicorn + Uvicorn, supporting ASGI, RSGI, and WSGI interfaces with HTTP/1 and HTTP/2 protocols. Features include Prometheus metrics, static file serving from the Rust runtime, and support for multiple AsyncIO event loops including uvloop and rloop.

Tokens
8.5K
Snippets
22
Records
57
Agent score
89%

What's inside granian

  1. Use free-threaded Python with Granian

    master

    Granian supports free-threaded Python (experimental). When using a free-threaded build:

    • Workers are threads instead of separate processes, meaning a single Python interpreter is shared across all workers.
    • The application is loaded once and shared.
    • For ASGI and RSGI, each worker runs its own AsyncIO event loop within its worker thread.
    • For WSGI, GIL-related limitations are theoretically absent.

    Warning: Free-threaded support is experimental and highly discouraged in production. Granian will refuse to start if the GIL is enabled on a free-threaded build.

  2. Choose a Runtime Mode

    master

    Granian supports two threading paradigms for its multi-threaded Rust runtime:

    • st (single-threaded): Spawns N single-threaded Rust runtimes. Often more efficient with a small number of processes.
    • mt (multi-threaded): Spawns a single multi-threaded runtime with N threads. Scales more efficiently with a large number of CPUs.
    • auto (default): Automatically selects the best mode based on your configuration.
  3. Install Granian with extra dependencies

    master

    Granian supports optional extra dependencies to add specific functionality. You can install them using the bracket syntax with pip.

    Available extras include:

    • dotenv: Allows loading environment files.
    • pname: Allows customizing process names.
    • reload: Adds reload functionality on file changes.
    • rloop
    • uvloop
    • winloop

    Example installation with pname and uvloop:

    $ pip install granian[pname,uvloop]
  4. Configure Workers and Threads

    master

    Granian's architecture differs from Gunicorn or Uvicorn. Use these settings to tune performance:

    • --workers: Total number of processes, each with a dedicated Python interpreter.
    • --blocking-threads: Number of threads per worker interacting with the Python interpreter. For WSGI, this is the max concurrency. For async protocols, this is fixed to 1.
    • --runtime-threads: Number of Rust threads per worker for network I/O.
    • --runtime-blocking-threads: Number of Rust threads per worker for blocking operations (e.g., file system).

    Suggestions:

    • Match --workers to CPU cores (or 1 per container in K8s/Docker).
    • For most applications, the default --runtime-threads and --runtime-blocking-threads are sufficient.
    • For async protocols, avoid tuning --blocking-threads unless you have a specific use case; use --backpressure instead.
  5. Configure Backpressure

    master

    Backpressure acts as a secondary backlog to prevent overwhelming the Python interpreter. It limits the number of connections the worker's accept loop will process concurrently.

    • Async protocols: The default value is usually sufficient.
    • Sync protocols (WSGI): Set this based on your application's concurrency needs. If your app makes external network requests, a higher value helps. If it's purely CPU-bound/serial, a low value is better.
    • Database connections: A good rule of thumb is to set backpressure to the number of available database connections.

    Warning: Backpressure limits connections, not individual requests. If you use long-running keep-alive connections (e.g., behind a reverse proxy), ensure --backpressure is higher than the expected number of keep-alive connections to avoid blocking new connections.

  6. Implement an RSGI application

    master

    An RSGI application is a single asynchronous callable (coroutine) that handles connection events. It is called once per connection (e.g., once per HTTP request or once per WebSocket connection) and must be asyncio-compatible.

    The application receives two arguments:

    • scope: An object containing connection information, including a type key specifying the protocol.
    • protocol: An object with awaitable methods used to transmit data to and from the client.

    RSGI is designed for high-performance Rust-based servers where I/O and threading are handled outside the Python interpreter, allowing Python code to leverage efficient lower-level protocols like HTTP/1, HTTP/2, HTTP/3, and WebSockets.

  7. Use Human-Readable Durations

    master

    Many Granian options (like --blocking-threads-idle-timeout, --http2-keep-alive-timeout, --rss-sample-interval, --workers-lifetime, --workers-kill-timeout, --respawn-interval, --metrics-scrape-interval) accept human-readable duration strings. If a plain number is provided, it is treated as seconds.

    Supported suffixes:

    • s: seconds
    • m: minutes
    • h: hours
    • d: days

    Example formats: 24h, 6m, 2s, 1h30m.

  8. Serve Static Files

    master

    Granian can serve static files directly from the Rust runtime, bypassing the Python application. You can define multiple routes and mounts. The number of routes must match the number of mounts.

    To serve a specific file (like index.html) when a directory is requested, use --static-path-dir-to-file.

    $ granian \
        --static-path-route /static \
        --static-path-mount assets/static \
        --static-path-route /media \
        --static-path-mount assets/media \
        package:app

    To serve index.html for directory listings:

    $ granian \
        --static-path-route /docs \
        --static-path-mount generated/docs \
        --static-path-dir-to-file index.html \
        package:app
  9. Run a WSGI application with Granian

    master

    To serve a WSGI application, use the --interface wsgi flag followed by the module and application name.

    Example WSGI Application (main.py):

    def app(environ, start_response):
        start_response('200 OK', [('content-type', 'text/plain')])
        return [b"Hello, world!"]

    CLI Command:

    $ granian --interface wsgi main:app
  10. Run an RSGI application with Granian

    master

    To serve an RSGI application, use the --interface rsgi flag followed by the module and application name.

    Example RSGI Application (main.py):

    async def app(scope, proto):
        assert scope.proto == 'http'
    
        proto.response_str(
            status=200,
            headers=[
                ('content-type', 'text/plain')
            ],
            body="Hello, world!"
        )

    CLI Command:

    $ granian --interface rsgi main:app