Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit bfd4adf
Showing
29 changed files
with
1,674 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[run] | ||
source = fcgiproto | ||
branch = 1 | ||
|
||
[report] | ||
show_missing = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
.project | ||
.pydevproject | ||
.idea/ | ||
.coverage | ||
.cache/ | ||
.tox/ | ||
.eggs/ | ||
*.egg-info/ | ||
*.pyc | ||
dist/ | ||
docs/_build/ | ||
build/ | ||
virtualenv/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
sudo: false | ||
|
||
language: python | ||
|
||
python: | ||
- "2.7" | ||
- "3.3" | ||
- "3.4" | ||
- "3.5" | ||
|
||
install: pip install tox-travis coveralls | ||
|
||
script: tox | ||
|
||
after_success: coveralls |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
This is the MIT license: http://www.opensource.org/licenses/mit-license.php | ||
|
||
Copyright (c) Alex Grönholm | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy of this | ||
software and associated documentation files (the "Software"), to deal in the Software | ||
without restriction, including without limitation the rights to use, copy, modify, merge, | ||
publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons | ||
to whom the Software is furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all copies or | ||
substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | ||
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR | ||
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE | ||
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR | ||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER | ||
DEALINGS IN THE SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
.. image:: https://travis-ci.org/agronholm/fcgiproto.svg?branch=master | ||
:target: https://travis-ci.org/agronholm/fcgiproto | ||
:alt: Build Status | ||
.. image:: https://coveralls.io/repos/github/agronholm/fcgiproto/badge.svg?branch=master | ||
:target: https://coveralls.io/github/agronholm/fcgiproto?branch=master | ||
:alt: Code Coverage | ||
|
||
The FastCGI_ protocol is a protocol commonly used to relay HTTP requests and responses between a | ||
front-end web server (nginx, Apache, etc.) and a back-end web application. | ||
|
||
This library implements this protocol for the web application end as a pure state-machine which | ||
only takes in bytes and returns a list of parsed events. This leaves users free to use any I/O | ||
approach they see fit (asyncio_, curio_, Twisted_, etc.). Sample code is provided for implementing | ||
a FastCGI server using a variety of I/O frameworks. | ||
|
||
.. _FastCGI: https://htmlpreview.github.io/?https://github.com/FastCGI-Archives/FastCGI.com/blob/master/docs/FastCGI%20Specification.html | ||
.. _asyncio: https://docs.python.org/3/library/asyncio.html | ||
.. _curio: https://github.com/dabeaz/curio | ||
.. _Twisted: https://twistedmatrix.com/ | ||
|
||
Project links | ||
------------- | ||
|
||
* `Documentation <http://fcgiproto.readthedocs.org/en/latest/>`_ | ||
* `Source code <https://github.com/agronholm/fcgiproto>`_ | ||
* `Issue tracker <https://github.com/agronholm/fcgiproto/issues>`_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
API Reference | ||
============= | ||
|
||
Classes | ||
------- | ||
|
||
.. autoclass:: fcgiproto.FastCGIConnection | ||
:members: | ||
|
||
.. autoclass:: fcgiproto.RequestEvent | ||
:members: | ||
|
||
.. autoclass:: fcgiproto.RequestBeginEvent | ||
:members: | ||
|
||
.. autoclass:: fcgiproto.RequestAbortEvent | ||
:members: | ||
|
||
.. autoclass:: fcgiproto.RequestDataEvent | ||
:members: | ||
|
||
.. autoclass:: fcgiproto.RequestSecondaryDataEvent | ||
:members: | ||
|
||
.. autoexception:: fcgiproto.ProtocolError | ||
|
||
Constants | ||
--------- | ||
|
||
* ``fcgiproto.FCGI_RESPONDER`` | ||
* ``fcgiproto.FCGI_AUTHORIZER`` | ||
* ``fcgiproto.FCGI_FILTER`` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
#!/usr/bin/env python | ||
# coding: utf-8 | ||
import pkg_resources | ||
|
||
extensions = [ | ||
'sphinx.ext.autodoc', | ||
] | ||
|
||
templates_path = ['_templates'] | ||
source_suffix = '.rst' | ||
master_doc = 'index' | ||
project = 'fcgiproto' | ||
author = u'Alex Grönholm' | ||
copyright = '2016, ' + author | ||
|
||
v = pkg_resources.get_distribution('fcgiproto').parsed_version | ||
version = v.base_version | ||
release = v.public | ||
|
||
language = None | ||
|
||
exclude_patterns = ['_build'] | ||
pygments_style = 'sphinx' | ||
todo_include_todos = False | ||
|
||
html_theme = 'classic' | ||
html_static_path = ['_static'] | ||
htmlhelp_basename = 'fcgiprotodoc' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
FastCGI state-machine protocol (fcgiproto) | ||
========================================== | ||
|
||
.. include:: ../README.rst | ||
:start-line: 7 | ||
:end-before: Project links | ||
|
||
|
||
Table of Contents | ||
================= | ||
|
||
.. toctree:: | ||
:maxdepth: 2 | ||
|
||
userguide | ||
api | ||
versionhistory |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
Protocol implementor's guide | ||
============================ | ||
|
||
Creating a real-world implementation of FastCGI using fcgiproto is quite straightforward. | ||
As with other sans-io protocols, you feed incoming data to fcgiproto and it vends events in return. | ||
To invoke actions on the connection, just call its methods, like | ||
:meth:`~fcgiproto.FastCGIConnection.send_headers` and so on. | ||
To get pending outgoing data, use the :meth:`~fcgiproto.FastCGIConnection.data_to_send` method. | ||
|
||
Connection configuration | ||
------------------------ | ||
|
||
The most common role is the responder role (``FCGI_RESPONDER``). The authorizer | ||
(``FCGI_AUTHORIZER``) and filter (``FCGI_FILTER``) roles are not commonly supported by web server | ||
software. As such, you will want to leave the default role setting alone, unless you really know | ||
what you're doing. | ||
|
||
It's also possible to set FCGI management values. The FastCGI specification defines names of three | ||
values: | ||
|
||
* ``FCGI_MAX_CONNS``: The maximum number of concurrent transport connections this application will | ||
accept, e.g. ``1`` or ``10`` | ||
* ``FCGI_MAX_REQS``: The maximum number of concurrent requests this application will accept, e.g. | ||
``1`` or ``50``. | ||
* ``FCGI_MPXS_CONNS``: ``0`` if this application does not multiplex connections (i.e. handle | ||
concurrent requests over each connection), ``1`` otherwise. | ||
|
||
The connection sets ``FCGI_MPXS_CONNS`` to ``1`` by default. It should be noted that the web server | ||
may never even query for these values, so leave this setting alone unless you know you need it. | ||
At least nginx does not attempt to multiplex FCGI connections, nor does it query for any management | ||
values. | ||
|
||
Implementor's responsibilities | ||
------------------------------ | ||
|
||
The logic in :class:`~fcgiproto.FastCGIConnection` will handle most complications of the protocol. | ||
That leaves just a handful of things for I/O implementors to keep in mind: | ||
|
||
* Always get any outgoing data from the connection (using | ||
:meth:`~fcgiproto.FastCGIConnection.data_to_send`) after calling either | ||
:meth:`~fcgiproto.FastCGIConnection.feed_data` or any of the other methods, and send it to the | ||
remote host | ||
* Remember to set ``Content-Length`` if your response contains a body | ||
* Respect the ``keep_connection`` flag in :class:`~fcgiproto.RequestBeginEvent`. | ||
Close the connection after calling :meth:`~fcgiproto.FastCGIConnection.end_request` if the flag | ||
is ``False``. | ||
|
||
Handling requests | ||
----------------- | ||
|
||
**RESPONDER** | ||
|
||
The sequence for handling responder requests (the most common case) is as follows: | ||
|
||
#. a :class:`~fcgiproto.RequestBeginEvent` is received | ||
#. one or more :class:`~fcgiproto.RequestDataEvent` are received, the last one having an empty | ||
bytestring as ``data`` attribute | ||
#. the application calls :meth:`~fcgiproto.FastCGIConnection.send_headers` once | ||
#. the application calls :meth:`~fcgiproto.FastCGIConnection.send_data` one or more times | ||
and the last call must have ``end_request`` set to ``True`` | ||
|
||
The implementor can decide whether to wait until all of the request body has been received, or | ||
start running the request handler code right after :class:`~fcgiproto.RequestBeginEvent` has been | ||
received (to facilitate streaming uploads for example). | ||
|
||
In FastCGI responses, the HTTP status code is sent using the ``Status`` header. As a convenience, | ||
the :meth:`~fcgiproto.FastCGIConnection.send_headers` method provides the ``status`` parameter | ||
to add this header. | ||
|
||
**AUTHORIZER** | ||
|
||
Authorizer requests differ from responder requests in the way that the application never receives | ||
any request body. They also don't receive the ``CONTENT_LENGTH``, ``PATH_INFO``, ``SCRIPT_NAME`` or | ||
``PATH_TRANSLATED`` parameters, which severely limits the usefulness of this role. | ||
|
||
The request-response sequence for authorizers goes as follows: | ||
|
||
#. a :class:`~fcgiproto.RequestBeginEvent` is received | ||
#. the application calls :meth:`~fcgiproto.FastCGIConnection.send_headers` once | ||
#. the application calls :meth:`~fcgiproto.FastCGIConnection.send_data` one or more times | ||
and the last call must have ``end_request`` set to ``True`` | ||
|
||
A response code other than ``200`` will be interpreted as a negative response. | ||
|
||
**FILTER** | ||
|
||
Filter applications receive all the same information as responders, but they are also sent a | ||
secondary data stream which they're supposed to filter. | ||
|
||
The request-response sequence for filters goes as follows: | ||
|
||
#. a :class:`~fcgiproto.RequestBeginEvent` is received | ||
#. one or more :class:`~fcgiproto.RequestDataEvent` are received, the last one having an empty | ||
bytestring as ``data`` attribute | ||
#. one or more :class:`~fcgiproto.RequestSecondaryDataEvent` are received, the last one having an | ||
empty bytestring as ``data`` attribute | ||
#. the application calls :meth:`~fcgiproto.FastCGIConnection.send_headers` once | ||
#. the application calls :meth:`~fcgiproto.FastCGIConnection.send_data` one or more times | ||
and the last call must have ``end_request`` set to ``True`` | ||
|
||
The application is expected to send the (modified) secondary data stream as the response body. | ||
It must read in all of the request body before starting to send a response (thus somewhat deviating | ||
from the sequence above), but it does not need to wait for the secondary data stream to end (for | ||
example if the response comes from a cache). | ||
|
||
Handling request aborts | ||
----------------------- | ||
|
||
If the application receives a :class:`~fcgiproto.RequestAbortEvent`, it should cease processing of | ||
the request at once. No headers or data should be sent from this point on for this request, and | ||
:meth:`~fcgiproto.FastCGIConnection.end_request` should be called as soon as possible. | ||
|
||
Running the examples | ||
-------------------- | ||
|
||
The ``examples`` directory in the project source tree contains example code for several popular | ||
I/O frameworks to get you started. Just run any of the server scripts and it will start a FastCGI | ||
server listening on port 9500. | ||
|
||
Since FastCGI requires a front-end server, a Docker script and nginx site configuration file have | ||
been provided as a convenience. Just run ``nginx_docker.sh`` from the ``examples`` directory and | ||
navigate to http://127.0.0.1/ to see the result. The example code displays a web page that shows | ||
the FastCGI parameters and the request body (if any). | ||
|
||
.. note:: You may have to make adjustments to the configuration if your Docker interface address or | ||
desired host HTTP port don't match the provided configuration. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
Version history | ||
=============== | ||
|
||
This library adheres to `Semantic Versioning <http://semver.org/>`_. | ||
|
||
**1.0.0** | ||
|
||
- Initial release |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
from asyncio import get_event_loop, Protocol | ||
|
||
from fcgiproto import FastCGIConnection, RequestBeginEvent, RequestDataEvent | ||
|
||
|
||
class FastCGIProtocol(Protocol): | ||
def __init__(self): | ||
self.transport = None | ||
self.conn = FastCGIConnection() | ||
self.requests = {} | ||
|
||
def connection_made(self, transport): | ||
self.transport = transport | ||
|
||
def data_received(self, data): | ||
try: | ||
for event in self.conn.feed_data(data): | ||
if isinstance(event, RequestBeginEvent): | ||
self.requests[event.request_id] = ( | ||
event.params, event.keep_connection, bytearray()) | ||
elif isinstance(event, RequestDataEvent): | ||
request_data = self.requests[event.request_id][2] | ||
if event.data: | ||
request_data.extend(event.data) | ||
else: | ||
params, keep_connection, request_data = self.requests.pop(event.request_id) | ||
self.handle_request(event.request_id, params, request_data) | ||
if not keep_connection: | ||
self.transport.close() | ||
|
||
self.transport.write(self.conn.data_to_send()) | ||
except Exception: | ||
self.transport.abort() | ||
raise | ||
|
||
def handle_request(self, request_id, params, content): | ||
fcgi_params = '\n'.join('<tr><td>%s</td><td>%s</td></tr>' % (key, value) | ||
for key, value in params.items()) | ||
content = content.decode('utf-8', errors='replace') | ||
response = ("""\ | ||
<!DOCTYPE html> | ||
<html> | ||
<body> | ||
<h2>FCGI parameters</h2> | ||
<table> | ||
%s | ||
</table> | ||
<h2>Request body</h2> | ||
<pre>%s</pre> | ||
</body> | ||
</html> | ||
""" % (fcgi_params, content)).encode('utf-8') | ||
headers = [ | ||
(b'Content-Length', str(len(response)).encode('ascii')), | ||
(b'Content-Type', b'text/html') | ||
] | ||
self.conn.send_headers(request_id, headers, 200) | ||
self.conn.send_data(request_id, response, end_request=True) | ||
|
||
|
||
loop = get_event_loop() | ||
coro = loop.create_server(FastCGIProtocol, port=9500, reuse_address=True) | ||
loop.run_until_complete(coro) | ||
|
||
try: | ||
loop.run_forever() | ||
except (KeyboardInterrupt, SystemExit): | ||
pass |
Oops, something went wrong.