-
Notifications
You must be signed in to change notification settings - Fork 0
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
Showing
17 changed files
with
667 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 |
---|---|---|
|
@@ -4,6 +4,7 @@ on: | |
push: | ||
paths-ignore: | ||
- 'README.md' | ||
- 'docs/**' | ||
branches: | ||
- main | ||
pull_request: | ||
|
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
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,20 @@ | ||
# Minimal makefile for Sphinx documentation | ||
# | ||
|
||
# You can set these variables from the command line, and also | ||
# from the environment for the first two. | ||
SPHINXOPTS ?= | ||
SPHINXBUILD ?= sphinx-build | ||
SOURCEDIR = . | ||
BUILDDIR = _build | ||
|
||
# Put it first so that "make" without argument is like "make help". | ||
help: | ||
@$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) | ||
|
||
.PHONY: help Makefile | ||
|
||
# Catch-all target: route all unknown targets to Sphinx using the new | ||
# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). | ||
%: Makefile | ||
@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) |
Empty file.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
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,50 @@ | ||
# Configuration file for the Sphinx documentation builder. | ||
# | ||
# For the full list of built-in configuration values, see the documentation: | ||
# https://www.sphinx-doc.org/en/master/usage/configuration.html | ||
|
||
# -- Project information ----------------------------------------------------- | ||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information | ||
|
||
project = 'Rolo' | ||
copyright = '2024, LocalStack' | ||
author = 'Thomas Rausch' | ||
release = '0.6.x' | ||
|
||
# -- General configuration --------------------------------------------------- | ||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration | ||
|
||
extensions = [ | ||
'myst_parser' | ||
] | ||
|
||
templates_path = ['_templates'] | ||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] | ||
|
||
|
||
# -- Options for HTML output ------------------------------------------------- | ||
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output | ||
|
||
html_theme = "furo" | ||
html_static_path = ["_static"] | ||
html_title = "Rolo documentation" | ||
|
||
html_logo = "_static/rolo.png" | ||
html_theme_options = { | ||
"top_of_page_buttons": ["view", "edit"], | ||
"source_repository": "https://github.com/localstack/rolo/", | ||
"source_branch": "main", | ||
"source_directory": "docs/", | ||
"footer_icons": [ | ||
{ | ||
"name": "GitHub", | ||
"url": "https://github.com/localstack/rolo", | ||
"html": """ | ||
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 16 16"> | ||
<path fill-rule="evenodd" d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0 0 16 8c0-4.42-3.58-8-8-8z"></path> | ||
</svg> | ||
""", | ||
"class": "", | ||
}, | ||
], | ||
} |
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 @@ | ||
Gateway | ||
======= | ||
|
||
The `Gateway` serves as the factory for `HandlerChain` instances. | ||
It also serves as the interface to HTTP servers. | ||
|
||
## Creating a Gateway | ||
|
||
```python | ||
from rolo.gateway import Gateway | ||
|
||
gateway = Gateway( | ||
request_handlers=[ | ||
... | ||
], | ||
response_handlers=[ | ||
... | ||
], | ||
exception_handlers=[ | ||
... | ||
], | ||
finalizers=[ | ||
... | ||
] | ||
) | ||
``` | ||
|
||
## Protocol adapters | ||
|
||
You can use `rolo.gateway.wsgi` or `rolo.gateway.asgi` to expose a `Gateway` as either a WSGI or ASGI app. | ||
|
||
Read more in the [serving](serving.md) section. |
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,109 @@ | ||
Getting started | ||
=============== | ||
|
||
## Installation | ||
|
||
Rolo is hosted on [pypi](https://pypi.org/project/rolo/) and can be installed via pip. | ||
|
||
```sh | ||
pip install rolo | ||
``` | ||
|
||
## Hello World | ||
|
||
Rolo provides different ways of building a web application. | ||
It provides familiar concepts such as Router and `@route`, but also more flexible concepts like a Handler Chain. | ||
|
||
### Router | ||
|
||
Here is a simple [`Router`](router.md) that can be served as WSGI application using the Werkzeug dev server. | ||
If you are familiar with Flask, `@route` works in a similar way. | ||
|
||
```python | ||
from werkzeug import Request | ||
from werkzeug.serving import run_simple | ||
|
||
from rolo import Router, route | ||
from rolo.dispatcher import handler_dispatcher | ||
|
||
@route("/") | ||
def hello(request: Request): | ||
return {"message": "Hello World"} | ||
|
||
router = Router(dispatcher=handler_dispatcher()) | ||
router.add(hello) | ||
|
||
run_simple("localhost", 8000, router.wsgi()) | ||
``` | ||
|
||
And to test: | ||
```console | ||
curl localhost:8000/ | ||
``` | ||
Should yield | ||
```json | ||
{"message": "Hello World"} | ||
``` | ||
|
||
`rolo.Request` and `rolo.Response` objects work in the same way as Werkzeug's [Request / Response](https://werkzeug.palletsprojects.com/en/latest/wrappers/) wrappers. | ||
|
||
### Gateway | ||
|
||
A Gateway holds a set of handlers that are combined into a handler chain. | ||
Here is a simple example with a single request handler that dynamically creates a response object similar to httpbin. | ||
|
||
```python | ||
from werkzeug.serving import run_simple | ||
|
||
from rolo import Response | ||
from rolo.gateway import Gateway, RequestContext, HandlerChain | ||
from rolo.gateway.wsgi import WsgiGateway | ||
|
||
|
||
def echo_handler(chain: HandlerChain, context: RequestContext, response: Response): | ||
response.status_code = 200 | ||
response.set_json( | ||
{ | ||
"method": context.request.method, | ||
"path": context.request.path, | ||
"query": context.request.args, | ||
"headers": dict(context.request.headers), | ||
} | ||
) | ||
chain.stop() | ||
|
||
|
||
gateway = Gateway( | ||
request_handlers=[echo_handler], | ||
) | ||
|
||
run_simple("localhost", 8000, WsgiGateway(gateway)) | ||
``` | ||
|
||
And to test: | ||
```console | ||
curl -s -X POST "localhost:8000/foo/bar?a=1&b=2" | jq . | ||
``` | ||
Should give you: | ||
```json | ||
{ | ||
"method": "POST", | ||
"path": "/foo/bar", | ||
"query": { | ||
"a": "1", | ||
"b": "2" | ||
}, | ||
"headers": { | ||
"Host": "localhost:8000", | ||
"User-Agent": "curl/7.81.0", | ||
"Accept": "*/*" | ||
} | ||
} | ||
``` | ||
|
||
## Next Steps | ||
|
||
Learn how to | ||
* Use the [Router](router.md) | ||
* Use the [Handler Chain](handler_chain.md) | ||
* [Serve](serving.md) rolo through your favorite web server |
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,101 @@ | ||
Handler Chain | ||
============= | ||
|
||
The rolo handler chain implements a variant of the chain-of-responsibility pattern to process an incoming HTTP request. | ||
It is meant to be used together with a [`Gateway`](gateway.md), which is responsible for creating `HandlerChain` instances. | ||
|
||
Handler chains are a powerful abstraction to create complex HTTP server behavior, while keeping code cleanly encapsulated and the high-level logic easy to understand. | ||
You can find a simple example how to create a handler chain in the [Getting Started](getting_started.md) guide. | ||
|
||
## Behavior | ||
|
||
A handler chain consists of: | ||
* request handlers: process the request and attempt to create an initial response | ||
* response handlers: process the response | ||
* finalizers: handlers that are always executed at the end of running a handler chain | ||
* exception handlers: run when an exception occurs during the execution of a handler | ||
|
||
Each HTTP request coming into the server has its own `HandlerChain` instance, since the handler chain holds state for the handling of a request. | ||
A handler chain can be in three states that can be controlled by the handlers. | ||
|
||
* Running - the implicit state in which _all_ handlers are executed sequentially | ||
* Stopped - a handler has called `chain.stop()`. This stops the execution of all request handlers, and | ||
proceeds immediately to executing the response handlers. Response handlers and finalizers will be run, | ||
even if the chain has been stopped. | ||
* Terminated - a handler has called `chain.terminate()`. This stops the execution of all request | ||
handlers, and all response handlers, but runs the finalizers at the end. | ||
|
||
If an exception occurs during the execution of request handlers, the chain by default stops the chain, | ||
then runs each exception handler, and finally runs the response handlers. | ||
Exceptions that happen during the execution of response or exception handlers are logged but do not modify the control flow of the chain. | ||
|
||
## Handlers | ||
|
||
Request handlers, response handlers, and finalizers need to satisfy the `Handler` protocol: | ||
|
||
```python | ||
from rolo import Response | ||
from rolo.gateway import HandlerChain, RequestContext | ||
|
||
def handle(chain: HandlerChain, context: RequestContext, response: Response): | ||
... | ||
``` | ||
|
||
* `chain`: the HandlerChain instance currently being executed. The handler implementation can call for example `chain.stop()` to indicate that it should skip all other request handlers. | ||
* `context`: the RequestContext contains the rolo `Request` object, as well as a universal property store. You can simply call `context.myattr = ...` to pass a value down to the next handler | ||
* `response`: Handlers of a handler chain don't return a response, instead the response being populated is handed down from handler to handler, and can thus be enriched | ||
|
||
### Exception Handlers | ||
|
||
Exception handlers are similar, only they are also passed the `Exception` that was raised in the handler chain. | ||
|
||
```python | ||
from rolo import Response | ||
from rolo.gateway import HandlerChain, RequestContext | ||
|
||
def handle(chain: HandlerChain, exception: Exception, context: RequestContext, response: Response): | ||
... | ||
``` | ||
|
||
## Builtin Handlers | ||
|
||
### Router handler | ||
|
||
Sometimes you have a `Gateway` but also want to use the [`Router`](router.md). | ||
You can use the `RouterHandler` adapter to make a `Router` look like a handler chain `Handler`, and then pass it as handler to a Gateway. | ||
|
||
```python | ||
from rolo import Router | ||
from rolo.gateway import Gateway | ||
from rolo.gateway.handlers import RouterHandler | ||
|
||
router: Router = ... | ||
gateway: Gateway = ... | ||
|
||
gateway.request_handlers.append(RouterHandler(router)) | ||
``` | ||
|
||
### Empty response handler | ||
|
||
With the `EmptyResponseHandler` response handler automatically creates a default response if the response in the chain is empty. | ||
By default, it creates an empty 404 response, but it can be customized: | ||
|
||
```python | ||
from rolo.gateway.handlers import EmptyResponseHandler | ||
|
||
gateway.response_handlers.append(EmptyResponseHandler(status_code=404, body=b'404 Not Found')) | ||
``` | ||
|
||
### Werkzeug exception handler | ||
|
||
Werkzeug has a very useful [HTTP exception hierarchy](https://werkzeug.palletsprojects.com/en/latest/exceptions/) that can be used to programmatically trigger HTTP errors. | ||
For instance, a request handler may raise a `NotFound` error. | ||
To get the Gateway to automatically handle those exceptions and render them into JSON objects or HTML, you can use the `WerkzeugExceptionHandler`. | ||
|
||
```python | ||
from rolo.gateway.handlers import WerkzeugExceptionHandler | ||
|
||
gateway.exception_handlers.append(WerkzeugExceptionHandler(output_format="json")) | ||
``` | ||
|
||
In your request handler you can now raise any exception from `werkzeug.exceptions` and it will be rendered accordingly. |
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,42 @@ | ||
Rolo documentation | ||
================== | ||
|
||
<p align="center"> | ||
<img src="https://github.com/thrau/rolo/assets/3996682/268786a8-6335-412f-bc72-8080f97cbb5a" alt="Rolo HTTP"> | ||
</p> | ||
<p align="center"> | ||
<b>Rolo HTTP: A Python framework for building HTTP-based server applications.</b> | ||
</p> | ||
|
||
## Introduction | ||
|
||
Rolo is a flexible framework and library to build HTTP-based server applications beyond microservices and REST APIs. | ||
You can build HTTP-based RPC servers, websocket proxies, or other server types that typical web frameworks are not designed for. | ||
Rolo was originally designed to build the AWS RPC protocol server in [LocalStack](https://github.com/localstack/localstack). | ||
|
||
Rolo extends [Werkzeug](https://github.com/pallets/werkzeug/), a flexible Python HTTP server library, for you to use concepts you are familiar with like ``@route``, ``Request``, or ``Response``. | ||
It introduces the concept of a ``Gateway`` and ``HandlerChain``, an implementation variant of the [chain-of-responsibility pattern](https://en.wikipedia.org/wiki/Chain-of-responsibility_pattern). | ||
|
||
Rolo is designed for environments that do not use asyncio, but still require asynchronous HTTP features like HTTP2 SSE or Websockets. | ||
To allow asynchronous communication, Rolo introduces an ASGI/WSGI bridge, that allows you to serve Rolo applications through ASGI servers like Hypercorn. | ||
|
||
## Table of Content | ||
|
||
```{toctree} | ||
:caption: Quickstart | ||
:maxdepth: 2 | ||
getting_started | ||
``` | ||
|
||
```{toctree} | ||
:caption: User Guide | ||
:maxdepth: 2 | ||
router | ||
handler_chain | ||
gateway | ||
websockets | ||
serving | ||
``` | ||
|
Oops, something went wrong.