Skip to main content

A FastCGI to ASGI/WSGI adapter using asyncio

Project description

fcgisgi

A FastCGI-to-ASGI/WSGI adapter.

Run ASGI/WSGI Python applications behind legacy FastCGI-based web hosting servers (e.g., Apache mod_fcgid). An alternative to flup.server.fcgi package.

PyPI - Version

Features

  • FastCGI-to-ASGI adapter.
  • FastCGI-to-WSGI adapter (via thread pool).
  • asyncio-based server implementation.
  • Support spawning from mod_fcgid.

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.
    #     ""     if mounted at the root
    #     "/app" if mounted under "http://example.com/app/"
    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.2.tar.gz (26.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.2.2-py3-none-any.whl (15.1 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: fcgisgi-0.2.2.tar.gz
  • Upload date:
  • Size: 26.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.2.2.tar.gz
Algorithm Hash digest
SHA256 7251236b6a5e19cec72e36aee2419d41832148a7d620c3396c7af66a1fdde30f
MD5 f45c0e52a8767d2fdff4fd561ab35959
BLAKE2b-256 c873eabba42a8dd74fca5ce27106e1e8f31f57f37bf435919d9c46bf99ed296f

See more details on using hashes here.

File details

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

File metadata

  • Download URL: fcgisgi-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 15.1 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.2-py3-none-any.whl
Algorithm Hash digest
SHA256 c531899ac6ac988acfe8ced0fa93425b7d293041268eeffc9fa502f3ae3aae99
MD5 0a4e4648f68297575762cfe486382bc9
BLAKE2b-256 bc29dd5634a063982e3a91e349dd52b3e4de37e2f0416ec9039856a4e42579e8

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