Skip to main content

A FastCGI to ASGI/WSGI adapter using asyncio

Project description

fcgisgi

A FastCGI-to-ASGI/WSGI adapter powered by Python's asyncio.

PyPI - Version

Features

  • FastCGI-to-ASGI adapter.
  • FastCGI-to-WSGI adapter (via thread pool).
  • asyncio-based server implementation.
  • Sans-IO FastCGI protocol implementation.

Installation

pip install fcgisgi

Usage

ASGI

import asyncio
from fcgisgi import run_asgi_server

async def app(scope, receive, send):
    assert scope['type'] == 'http'
    await send({
        'type': 'http.response.start',
        'status': 200,
        'headers': [
            (b'content-type', b'text/plain'),
        ],
    })
    await send({
        'type': 'http.response.body',
        'body': b'Hello, world!',
    })

if __name__ == "__main__":
    # Bind to a TCP port or a Unix socket
    asyncio.run(run_asgi_server(app, bind_address=("127.0.0.1", 9000)))
    # asyncio.run(run_asgi_server(app, bind_address="/var/run/fcgisgi.sock"))

    # Alternatively, use the default FastCGI socket (fd=0) inherited from
    # the parent process (e.g., Apache mod_fcgid).
    # asyncio.run(run_asgi_server(app))

WSGI

import asyncio
from fcgisgi import run_wsgi_server

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

if __name__ == "__main__":
    asyncio.run(run_wsgi_server(app, bind_address=("127.0.0.1", 9000)))

Using mod_fcgid

When using Apache with mod_fcgid, the SCRIPT_NAME parameter often includes the FastCGI script filename (e.g., /index.fcgi), which can interfere with your application's routing.

Use the force_script_name option to override the mount point (root path) of your application, ensuring the routing engine receives the expected path.

.htaccess

AddHandler fcgid-script .fcgi
RewriteEngine On
RewriteBase /
# Static files
RewriteRule ^(static|assets|\.well-known)/ - [L]
RewriteRule ^(favicon\.ico|robots\.txt)$ - [L]
# Route everything else to the fcgi script
RewriteRule ^.* index.fcgi/$0 [QSA,END]

index.fcgi

#!/bin/sh
PATH=/path/to/venv/bin:$PATH
export PATH
exec python entrypoint.py

entrypoint.py

import asyncio
from fcgisgi import run_asgi_server

async def app(scope, receive, send):
    """ SNIP """

if __name__ == "__main__":
    # Specify the mount point (e.g., "" if mounted at the root)
    asyncio.run(run_asgi_server(app, force_script_name=""))

Configuration

You can pass additional configuration parameters to run_asgi_server or run_wsgi_server via keyword arguments:

  • startup_timeout (float): Timeout for ASGI lifespan startup (default: 55.0).
  • shutdown_timeout (float): Timeout for graceful shutdown on SIGTERM (default: 55.0).
  • max_workers (int): Maximum number of worker threads for WSGI applications (passed to ThreadPoolExecutor).
  • force_script_name (str): Override the SCRIPT_NAME (WSGI) or root_path (ASGI) parameter. Useful for normalizing routing behind a prefix.

Example:

asyncio.run(run_asgi_server(
    app, 
    startup_timeout=30.0, 
    shutdown_timeout=30.0
))

FCGI_PARAMS

The original FCGI_PARAMS passed from the web server can be retrieved as a list of (bytes, bytes) key-value pairs, preserving their original order and any duplicates.

  • ASGI: scope['extensions']['fcgisgi']['fcgi_params'] in the connection/request scope.
  • WSGI: environ['fcgisgi.fcgi_params'] in the environ dictionary.

ASGI Middleware Example

You can use a middleware to expose custom FastCGI parameters (e.g., RAW_URI) as HTTP headers:

class FCGIParamToHeaderMiddleware:
    def __init__(self, app, param_name=b"RAW_URI", header_name=b"x-fcgi-raw-uri"):
        self.app = app
        self.param_name = param_name
        self.header_name = header_name

    async def __call__(self, scope, receive, send):
        if scope["type"] == "http":
            fcgi_params = scope.get("extensions", {}).get("fcgisgi", {}).get("fcgi_params", [])
            for key, value in fcgi_params:
                if key == self.param_name:
                    scope["headers"].insert(0, (self.header_name, value))
                    break
        return await self.app(scope, receive, send)

References

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

fcgisgi-0.2.0.tar.gz (26.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

fcgisgi-0.2.0-py3-none-any.whl (15.2 kB view details)

Uploaded Python 3

File details

Details for the file fcgisgi-0.2.0.tar.gz.

File metadata

  • Download URL: fcgisgi-0.2.0.tar.gz
  • Upload date:
  • Size: 26.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for fcgisgi-0.2.0.tar.gz
Algorithm Hash digest
SHA256 c8a9f0dc803f0ec7e68a5a29e16a14cb23b53b0d54689d101dda6e774cf155b7
MD5 923f47b361d02693c03ffd4ac17f3718
BLAKE2b-256 d7365b73a2658836f4fcb48da6cada96cedca45bcb2831d2b73528e04f78a637

See more details on using hashes here.

File details

Details for the file fcgisgi-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: fcgisgi-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 15.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for fcgisgi-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9fbba20770ea17a0651daf90bda4b10db7b0b0f5705c91eea3e99f290ffbba78
MD5 d1b83958de7a020cf9ed79ea5f0061bb
BLAKE2b-256 3d87e33b37f20d80db62171c72e70195ce7b1b5704746ec0ffb5c2fa28bf2cc8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page