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
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 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
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c8a9f0dc803f0ec7e68a5a29e16a14cb23b53b0d54689d101dda6e774cf155b7
|
|
| MD5 |
923f47b361d02693c03ffd4ac17f3718
|
|
| BLAKE2b-256 |
d7365b73a2658836f4fcb48da6cada96cedca45bcb2831d2b73528e04f78a637
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9fbba20770ea17a0651daf90bda4b10db7b0b0f5705c91eea3e99f290ffbba78
|
|
| MD5 |
d1b83958de7a020cf9ed79ea5f0061bb
|
|
| BLAKE2b-256 |
3d87e33b37f20d80db62171c72e70195ce7b1b5704746ec0ffb5c2fa28bf2cc8
|