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.

Features

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

Installation

(Once published)

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.1.0.tar.gz (24.6 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.1.0-py3-none-any.whl (14.3 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for fcgisgi-0.1.0.tar.gz
Algorithm Hash digest
SHA256 50a98b9389546a0a9fdbc0d98335979ddea7d069e3d02bd5b682860d9ef87b40
MD5 2a7df2c363038700d372b5806caf4f84
BLAKE2b-256 e8e262a5fc237231ebcc1c53b2f4aab7c5e6d73d8b8ae946c6296783110a0885

See more details on using hashes here.

File details

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

File metadata

  • Download URL: fcgisgi-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 14.3 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.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 99fb61667424a8a9196a4b5bffe6a68ee32ee3dd7f8cf6a8879eed33637acec7
MD5 56c7303dfa581b45fa8410542a661853
BLAKE2b-256 5ef588994835fcb8ab9edc04b9281ec681b9e2b1d23a7346c7ac6f588cae332d

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