Metadata-Version: 2.4
Name: simple-fastcgi
Version: 0.0.4
Summary: simple FastCGI sync/async handler in pure python
Author-email: USN484259 <USN484259@outlook.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/USN484259/simple-fastcgi
Keywords: fastcgi,fcgi,cgi,http,rfc3875
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# Simple FastCGI

simple FastCGI protocol parser and sync/async handler in pure python


## Example 1: echo (sync)

```python
import json
from simple_fastcgi import *

class example_handler(HttpResponseMixin, FcgiHandler):
	def handle(self):
		self.send_response(200, "application/json", self.environ)

if __name__ == "__main__":
	with FcgiServer(example_handler) as server:
		server.serve_forever()
```


## Example 2: dir listing (async)

```python
import os
import json
import asyncio
from functools import partial
from urllib.parse import parse_qs
from simple_fastcgi import *


async def dir_listing(path):
	with os.scandir(path) as it:
		for entry in it:
			record = {
				"name": entry.name
			}
			try:
				if entry.is_dir():
					record["type"] = "dir"
				elif entry.is_file():
					record["type"] = "file"
				else:
					continue

				stat = entry.stat()
				record["size"] = stat.st_size
				record["mtime"] = int(stat.st_mtime * 1000)
			except Exception:
				pass

			line = json.dumps(record, ensure_ascii = False) + '\n'
			yield line


class dir_listing_handler(AsyncHttpResponseMixin, AsyncFcgiHandler):
	async def handle(self):
		try:
			doc_root = self.environ["DOCUMENT_ROOT"]
			query = parse_qs(self.environ["QUERY_STRING"])
			path = query["path"][0].lstrip("/.")

			func = partial(dir_listing, os.path.join(doc_root, path))
			return await self.send_response(200, "application/x-ndjson", data = func)
		except Exception as e:
			return await self.send_response(404)


async def main():
	async with AsyncFcgiServer(dir_listing_handler) as server:
		await server.serve_forever()

if __name__ == "__main__":
	asyncio.run(main())
```


## API reference

The import module name is **simple_fastcgi**

### Exceptions

**ProtocolError**

Raised when encountered FastCGI protocol violations.


### Handlers

**class FcgiHandler**

FastCGI Handler, subclass of BaseRequestHandler.
To implement your own handler, subclass FcgiHandler and override the **handle()** method.

**environ**

Member *environ* is a *dictionary* containing key-value pairs from the webserver.
The keys are converted to upper-case.

**server**

Member *server* refers to the server object that creates this handler.

**handle()**

Override this method and do all the work here to serve a request.

**aborted()**

Check if the FastCGI request has been aborted by the webserver.

**read(sz = -1)**

Read and return up to *sz* bytes from input stream. If *sz* < 0, read until EOF.
Returns empty bytes object on EOF.

**readinto(buffer)**

Read from input stream into *buffer*, return the actual read byte count.
Returns 0 on EOF.

**readall()**

Read input stream until EOF and return all the data.

**write(data, \*, flush = False)**

Write *data* to the output stream. If *flush* is True, flush the data to underlying socket.

**write_err(data)**

Write *data* to the error stream.



**class AsyncFcgiHandler**

Async variant of **FcgiHandler**. methods are the same, except being async.

**environ**

**server**

async **handle()**

**aborted()**

async **read(sz = -1)**

async **readinto(buffer)**

async **readall()**

async **write(data, \*, flush = False)**

async **write_err(data)**


### Servers

**class FcgiServer**

Subclass of **BaseServer**, but instead listening on FCGI_LISTENSOCK_FILENO ( fd 0 ).
Can be used with context manager.

**\_\_init\_\_(handler, sockfd = sys.stdin.fileno())**

Initialize a server object. Subclass may override and do extra initialization.

*handler* is the handler-class which is instantiated on each request.

*sockfd* is a listening socket on which the server is running, defaults to fd 0 in conforming to FastCGI Specification.

**fileno()**

Get the underlying fd of the server object. Returns *sockfd*.

**shutdown()**

Stop the server.

**serve_forever()**

Start the server and block until shutdown.

**service_actions()**

Called in serve_forever loop. Subclass may override and do their own actions.


**class AsyncFcgiServer**

Async variant of **FcgiServer**. methods are the same, except being async.
Can be used with async context manager.

Note there is no **service_actions()** in **AsyncFcgiServer**, since it runs on event loop instead of its own loop, and one can easily schedule tasks to event loop.

**\_\_init\_\_(handler, sockfd = sys.stdin.fileno())**

**fileno()**

**get_loop()**

Get the event loop on which the server is running.

async **shutdown()**

async **serve_forever()**


### Mixins

**class HttpResponseMixin**

Helper mixin class for **FcgiServer**, to construct CGI/HTTP responses.

**send_response(code, /, mime_type = "text/plain", data = None, \*, extra_headers = [], flush = True)**

Construct a CGI document response and write to output stream.

*code* is the HTTP status code of the response.

*mime_type* is the content-type of the response.

*extra_headers* is the extra HTTP header fields that should be included in the response header.

*data* is the payload of the response.
- if *data* is a function, it is assumed to be a generator and called to get chunks of data.
- if *data* is a string, it is encoded in UTF-8.
- if "json" appears in *mime_type* and *data* is not bytes-like, try to encode *data* as json.
- else, append *data* to payload as-is.

if *data* is a function and *flush* is True, each data chunk is followed by a flush.

Note that you **can** mix this method with *write()* calls. For example, you can call this method,
passing first data chunk as *data* parameter, followed by multiple *write()* calls to append more data.

Also note that this method does **not** calculate or append a *content-length* header for you.
You need to handle *content-length* header yourself, either omit this header, or calculate in advance.

**send_redirect(target)**

Construct a CGI redirect response and write to output stream.

*target* is the redirect target, it can be either *local-Location* or *client-Location* as defined in CGI Specification.


**AsyncHttpResponseMixin**

Async variant of **HttpResponseMixin**. methods are the same, except being async

async **send_response(code, /, mime_type = "text/plain", data = None, \*, extra_headers = [], flush = True)**

Note that if *data* is a function, it is assumed to be an **async generator** instead.

async **send_redirect(target)**


## Reference

+ [FastCGI Specification](https://fast-cgi.github.io/spec.html) (Unofficial)
+ [RFC 3875](https://www.rfc-editor.org/rfc/rfc3875): The Common Gateway Interface (CGI) Version 1.1

