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.
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 toThreadPoolExecutor).force_script_name(str): Override theSCRIPT_NAME(WSGI) orroot_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 theenvirondictionary.
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
- ASGI Specification: https://asgi.readthedocs.io/
- WSGI Specification (PEP 3333): https://peps.python.org/pep-3333/
- FastCGI Specification: https://fastcgi-archives.github.io/FastCGI_Specification.html
License
MIT
Release files for fcgisgi 0.2.3
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| fcgisgi-0.2.3.tar.gz | 32.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| fcgisgi-0.2.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 50.2 kB
Release files / fcgisgi-0.2.3.tar.gz
| Download URL | fcgisgi-0.2.3.tar.gz |
|---|---|
| Size | 32.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
de65bce2c7a370d6655cf73008285653ab3410421cf7a21314c1af3ac5311036
|
|
BLAKE2b-256 checksum How to use checksums |
d15eb515e49dd4eace78b976f0933c4c4fe7a07dc0e8517afa38836d59a31d39
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|
Release files / fcgisgi-0.2.3-py3-none-any.whl
| Download URL | fcgisgi-0.2.3-py3-none-any.whl |
|---|---|
| Size | 17.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
afe5a54c5fbbcdb21dfc9e4e8d872d638d989472fc32fb8885f11a789982f534
|
|
BLAKE2b-256 checksum How to use checksums |
af6400eafe58c6660931b555326e9f273d60855cf470b693cfdc4f217b616897
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|