mirror of
https://github.com/kennethreitz/responder.git
synced 2026-06-05 23:00:17 +00:00
deploy: 226bd63ed3
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+50
-33
@@ -1,34 +1,32 @@
|
||||
Deployment
|
||||
==========
|
||||
|
||||
Responder applications are standard ASGI apps. You can deploy them anywhere
|
||||
you'd deploy a Python web service.
|
||||
Responder applications are standard `ASGI <https://asgi.readthedocs.io/>`_
|
||||
apps. ASGI (Asynchronous Server Gateway Interface) is the modern successor
|
||||
to WSGI — it supports async, WebSockets, and HTTP/2. This means you can
|
||||
deploy a Responder app anywhere that runs Python, using any ASGI server.
|
||||
|
||||
|
||||
Running Locally
|
||||
---------------
|
||||
|
||||
The simplest way to run your application::
|
||||
|
||||
# api.py
|
||||
import responder
|
||||
|
||||
api = responder.API()
|
||||
|
||||
@api.route("/")
|
||||
def hello(req, resp):
|
||||
resp.text = "hello, world!"
|
||||
During development, ``api.run()`` is all you need::
|
||||
|
||||
if __name__ == "__main__":
|
||||
api.run()
|
||||
|
||||
This starts a production uvicorn server on ``127.0.0.1:5042``.
|
||||
This starts a `uvicorn <https://www.uvicorn.org/>`_ server on
|
||||
``127.0.0.1:5042``. Uvicorn is a lightning-fast ASGI server built on
|
||||
`uvloop <https://uvloop.readthedocs.io/>`_ — it handles thousands of
|
||||
concurrent connections efficiently and protects against slowloris attacks,
|
||||
making a reverse proxy like nginx optional for many deployments.
|
||||
|
||||
|
||||
Docker
|
||||
------
|
||||
|
||||
A minimal Dockerfile for deploying a Responder application::
|
||||
Docker is the most common way to package and deploy web applications.
|
||||
Here's a minimal Dockerfile::
|
||||
|
||||
FROM python:3.13-slim
|
||||
WORKDIR /app
|
||||
@@ -43,44 +41,63 @@ Build and run::
|
||||
$ docker build -t myapi .
|
||||
$ docker run -p 8000:80 myapi
|
||||
|
||||
The ``python:3.13-slim`` image is about 150MB — small enough for fast
|
||||
deploys but includes everything you need. For even smaller images, you
|
||||
can use ``python:3.13-alpine``, though some packages may need extra
|
||||
build dependencies.
|
||||
|
||||
|
||||
Cloud Platforms
|
||||
---------------
|
||||
|
||||
Responder automatically honors the ``PORT`` environment variable, which is
|
||||
set by most cloud platforms. When ``PORT`` is set, Responder binds to
|
||||
``0.0.0.0`` on that port automatically.
|
||||
Responder automatically honors the ``PORT`` environment variable. When
|
||||
``PORT`` is set, the server binds to ``0.0.0.0`` on that port — this is
|
||||
the convention that virtually every cloud platform uses.
|
||||
|
||||
This works out of the box with:
|
||||
This means zero configuration on:
|
||||
|
||||
- **Fly.io**
|
||||
- **Railway**
|
||||
- **Render**
|
||||
- **Google Cloud Run**
|
||||
- **Azure Container Apps**
|
||||
- **AWS App Runner**
|
||||
- **Fly.io** — ``fly launch`` and you're done
|
||||
- **Railway** — push your code, Railway sets ``PORT``
|
||||
- **Render** — set start command to ``python api.py``
|
||||
- **Google Cloud Run** — containerize and deploy
|
||||
- **Azure Container Apps** — same pattern
|
||||
- **AWS App Runner** — and here too
|
||||
|
||||
Just deploy your code and set the start command to ``python api.py``.
|
||||
The pattern is always the same: deploy your code, set the start command
|
||||
to ``python api.py``, and the platform handles the rest.
|
||||
|
||||
|
||||
Uvicorn Directly
|
||||
----------------
|
||||
|
||||
For more control over the production server, you can bypass ``api.run()``
|
||||
and use uvicorn directly::
|
||||
For production deployments where you want more control, bypass
|
||||
``api.run()`` and use uvicorn directly::
|
||||
|
||||
$ uvicorn api:api --host 0.0.0.0 --port 8000 --workers 4
|
||||
|
||||
This gives you access to all of uvicorn's options: worker count, SSL
|
||||
certificates, access logging, and more. See the
|
||||
`uvicorn documentation <https://www.uvicorn.org/>`_ for details.
|
||||
The ``--workers`` flag spawns multiple processes, each handling requests
|
||||
independently. A good starting point is 2-4 workers per CPU core.
|
||||
|
||||
Uvicorn supports many options — SSL certificates, access logging, graceful
|
||||
shutdown timeouts, and more. See the
|
||||
`uvicorn documentation <https://www.uvicorn.org/deployment/>`_ for details.
|
||||
|
||||
|
||||
Reverse Proxy
|
||||
-------------
|
||||
|
||||
In production, you may want to place Responder behind a reverse proxy like
|
||||
nginx or Caddy for SSL termination, load balancing, or serving static assets.
|
||||
For high-traffic production deployments, you may want a reverse proxy like
|
||||
`nginx <https://nginx.org/>`_ or `Caddy <https://caddyserver.com/>`_ in
|
||||
front of your application for:
|
||||
|
||||
- **SSL/TLS termination** — let the proxy handle HTTPS certificates
|
||||
- **Load balancing** — distribute traffic across multiple app instances
|
||||
- **Static asset serving** — offload static files to the proxy
|
||||
- **Rate limiting** — at the infrastructure level
|
||||
|
||||
Responder's ``TrustedHostMiddleware`` and ``HTTPSRedirectMiddleware`` work
|
||||
correctly behind proxies that set standard forwarding headers.
|
||||
correctly behind proxies that set standard forwarding headers
|
||||
(``X-Forwarded-For``, ``X-Forwarded-Proto``).
|
||||
|
||||
That said, uvicorn is production-ready on its own. Many applications run
|
||||
uvicorn directly without a reverse proxy and do just fine.
|
||||
|
||||
+155
-57
@@ -2,164 +2,229 @@ Quick Start
|
||||
===========
|
||||
|
||||
This guide will walk you through the basics of building a web service with
|
||||
Responder. By the end, you'll know how to declare routes, handle requests,
|
||||
send responses, render templates, and process background tasks.
|
||||
Responder. By the end, you'll understand how HTTP requests and responses
|
||||
work, how to define routes, read data from clients, send data back, render
|
||||
HTML templates, and process work in the background.
|
||||
|
||||
|
||||
Create a Web Service
|
||||
--------------------
|
||||
|
||||
The first thing you need to do is declare a web service. This is the central
|
||||
object that holds all your routes, middleware, and configuration::
|
||||
Every web application starts with a single object — the application
|
||||
instance. In Responder, this is the ``API`` class. It holds your routes,
|
||||
middleware, templates, and configuration. Think of it as the central
|
||||
nervous system of your web service::
|
||||
|
||||
import responder
|
||||
|
||||
api = responder.API()
|
||||
|
||||
That's it. One import, one line. You now have a fully functional ASGI
|
||||
application with gzip compression, static file serving, session support,
|
||||
and a production-ready server — all wired up and ready to go.
|
||||
|
||||
|
||||
Hello World
|
||||
-----------
|
||||
|
||||
Next, add a route. Here, we'll make the root URL say "hello, world!"::
|
||||
A web service isn't very useful until it can respond to requests. In HTTP,
|
||||
a *route* maps a URL path to a function that handles it. When a client
|
||||
(like a browser or ``curl``) sends a request to that path, your function
|
||||
runs and produces a response.
|
||||
|
||||
Here's the simplest possible route::
|
||||
|
||||
@api.route("/")
|
||||
def hello_world(req, resp):
|
||||
resp.text = "hello, world!"
|
||||
|
||||
Every view receives a ``req`` (request) and ``resp`` (response) object. You
|
||||
don't need to return anything — just mutate the response directly.
|
||||
Two things to notice:
|
||||
|
||||
1. Every view function receives two arguments: ``req`` (the incoming
|
||||
request) and ``resp`` (the outgoing response).
|
||||
2. You don't return anything. Instead, you *mutate* the response object
|
||||
directly. This is a deliberate design choice — it keeps the API
|
||||
consistent whether you're setting text, JSON, headers, cookies, or
|
||||
status codes.
|
||||
|
||||
|
||||
Run the Server
|
||||
--------------
|
||||
|
||||
Start your web service with ``api.run()``::
|
||||
Start your web service with a single call::
|
||||
|
||||
api.run()
|
||||
|
||||
This spins up a production-grade uvicorn server on port ``5042``, ready for
|
||||
incoming HTTP requests.
|
||||
This spins up a production-grade `uvicorn <https://www.uvicorn.org/>`_
|
||||
server on port ``5042``, ready for incoming HTTP requests. Open
|
||||
``http://localhost:5042`` in your browser and you'll see your hello world
|
||||
response.
|
||||
|
||||
You can customize the port with ``api.run(port=8000)``. The ``PORT``
|
||||
environment variable is also honored automatically — when set, Responder
|
||||
binds to ``0.0.0.0`` on that port, which is what cloud platforms like
|
||||
Fly.io, Railway, and Google Cloud Run expect.
|
||||
binds to ``0.0.0.0`` on that port, which is what cloud platforms expect.
|
||||
|
||||
.. note::
|
||||
|
||||
Both sync and async views are supported. The ``async`` keyword is always
|
||||
optional — use it when you need to ``await`` something.
|
||||
optional — use it when you need to ``await`` something, like reading a
|
||||
request body or querying a database.
|
||||
|
||||
|
||||
Route Parameters
|
||||
----------------
|
||||
|
||||
If you want dynamic URLs, use Python's familiar f-string syntax to declare
|
||||
variables in your routes::
|
||||
Static URLs like ``/about`` are useful, but most applications need dynamic
|
||||
routes — URLs that contain variable data, like a user ID or a product slug.
|
||||
|
||||
In Responder, you declare route parameters using Python's f-string syntax::
|
||||
|
||||
@api.route("/hello/{who}")
|
||||
def hello_to(req, resp, *, who):
|
||||
resp.text = f"hello, {who}!"
|
||||
|
||||
A ``GET`` request to ``/hello/world`` will respond with ``hello, world!``.
|
||||
A request to ``/hello/guido`` will respond with ``hello, guido!``.
|
||||
|
||||
Route parameters are passed as keyword-only arguments (after the ``*``).
|
||||
Route parameters are passed as *keyword-only* arguments (after the ``*``
|
||||
in the function signature). This is a Python feature that makes the
|
||||
interface explicit — you always know which arguments come from the URL.
|
||||
|
||||
|
||||
Type Convertors
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
You can constrain route parameters to specific types. The parameter will be
|
||||
automatically converted before it reaches your view::
|
||||
By default, route parameters are strings. But often you want them as
|
||||
integers, UUIDs, or other types. Responder can convert them automatically
|
||||
using type annotations in the route pattern::
|
||||
|
||||
@api.route("/add/{a:int}/{b:int}")
|
||||
async def add(req, resp, *, a, b):
|
||||
resp.text = f"{a} + {b} = {a + b}"
|
||||
|
||||
Here, ``a`` and ``b`` will arrive as Python ``int`` objects, not strings.
|
||||
If someone requests ``/add/3/hello``, they'll get a 404 — the route won't
|
||||
match because ``hello`` isn't a valid integer.
|
||||
|
||||
Supported types:
|
||||
|
||||
- ``str`` — matches any string without slashes (default)
|
||||
- ``int`` — matches digits, converts to ``int``
|
||||
- ``float`` — matches decimal numbers, converts to ``float``
|
||||
- ``str`` — matches any string without slashes (this is the default)
|
||||
- ``int`` — matches digits and converts to ``int``
|
||||
- ``float`` — matches decimal numbers and converts to ``float``
|
||||
- ``uuid`` — matches UUID strings like ``550e8400-e29b-41d4-a716-446655440000``
|
||||
- ``path`` — matches any string *including* slashes, useful for file paths
|
||||
like ``/files/{filepath:path}``
|
||||
|
||||
|
||||
Sending Responses
|
||||
-----------------
|
||||
|
||||
Responder gives you several ways to send data back to the client. Just set
|
||||
the appropriate property on the response object.
|
||||
When an HTTP server receives a request, it must send back a response. Every
|
||||
HTTP response has three parts: a status code (like ``200 OK`` or ``404 Not
|
||||
Found``), headers (metadata like ``Content-Type``), and a body (the actual
|
||||
data).
|
||||
|
||||
**Text and HTML**::
|
||||
Responder lets you set all three by mutating the response object.
|
||||
|
||||
**Text and HTML** — the simplest response types. ``resp.text`` sets the
|
||||
``Content-Type`` to ``text/plain``, while ``resp.html`` sets it to
|
||||
``text/html``::
|
||||
|
||||
resp.text = "plain text response"
|
||||
resp.html = "<h1>HTML response</h1>"
|
||||
|
||||
**JSON** — the most common pattern for APIs. Set ``resp.media`` to any
|
||||
JSON-serializable Python object::
|
||||
**JSON** — the lingua franca of web APIs. Set ``resp.media`` to any
|
||||
JSON-serializable Python object — a dict, a list, whatever — and Responder
|
||||
will serialize it to JSON and set the right headers::
|
||||
|
||||
@api.route("/hello/{who}/json")
|
||||
def hello_json(req, resp, *, who):
|
||||
resp.media = {"hello": who}
|
||||
|
||||
If the client sends an ``Accept: application/x-yaml`` header, the same data
|
||||
will be returned as YAML instead. Content negotiation is automatic.
|
||||
will be returned as YAML instead. This is called *content negotiation* —
|
||||
the server and client agree on a format. It happens automatically.
|
||||
|
||||
**Files** — serve a file from disk with automatic content-type detection::
|
||||
**Files** — serve a file from disk. Responder uses Python's ``mimetypes``
|
||||
module to figure out the ``Content-Type`` from the file extension::
|
||||
|
||||
resp.file("reports/annual.pdf")
|
||||
|
||||
**Raw bytes**::
|
||||
**Raw bytes** — for binary data like images or protocol buffers::
|
||||
|
||||
resp.content = b"\x89PNG\r\n..."
|
||||
|
||||
**Status codes and headers**::
|
||||
**Status codes** — HTTP status codes tell the client what happened. ``200``
|
||||
means success, ``201`` means something was created, ``404`` means not found,
|
||||
``500`` means the server broke. Set it directly::
|
||||
|
||||
resp.status_code = 201
|
||||
|
||||
**Headers** — HTTP headers carry metadata. Common ones include
|
||||
``Content-Type``, ``Cache-Control``, ``Authorization``, and custom
|
||||
application headers::
|
||||
|
||||
resp.headers["X-Custom"] = "value"
|
||||
|
||||
**Redirects**::
|
||||
**Redirects** — tell the client to go somewhere else::
|
||||
|
||||
api.redirect(resp, location="/new-url")
|
||||
|
||||
This sends a ``301 Moved Permanently`` response by default. The client's
|
||||
browser will automatically follow the redirect.
|
||||
|
||||
|
||||
Reading Requests
|
||||
----------------
|
||||
|
||||
The request object gives you access to everything the client sent.
|
||||
The other half of HTTP is the request — the data the client sends to your
|
||||
server. This includes the HTTP method (GET, POST, PUT, DELETE), the URL,
|
||||
headers, query parameters, cookies, and optionally a body.
|
||||
|
||||
**Method and URL**::
|
||||
Responder wraps all of this in the ``req`` object.
|
||||
|
||||
**Method and URL** — every HTTP request has a method (what the client wants
|
||||
to do) and a URL (what resource it's about)::
|
||||
|
||||
req.method # "get", "post", etc. (lowercase)
|
||||
req.full_url # "http://example.com/path?q=1"
|
||||
req.url # parsed URL object
|
||||
|
||||
**Headers** — case-insensitive, just like you'd expect::
|
||||
**Headers** — HTTP headers carry metadata from the client, like what
|
||||
content types it accepts, authentication tokens, and more. Responder's
|
||||
headers dict is case-insensitive, because the HTTP spec says header names
|
||||
are case-insensitive::
|
||||
|
||||
req.headers["Content-Type"]
|
||||
req.headers["content-type"] # same thing
|
||||
|
||||
**Query parameters**::
|
||||
**Query parameters** — the part of the URL after the ``?``. These are
|
||||
commonly used for search, filtering, and pagination::
|
||||
|
||||
# GET /search?q=python&page=2
|
||||
req.params["q"] # "python"
|
||||
req.params["page"] # "2"
|
||||
|
||||
**Path parameters** — also available on the request object::
|
||||
Note that query parameters are always strings. If you need an integer,
|
||||
you'll need to convert it yourself: ``int(req.params["page"])``.
|
||||
|
||||
**Path parameters** — the dynamic parts of the URL that matched your route
|
||||
pattern. These are also available on the request object, which is useful
|
||||
in before-request hooks where they aren't passed as function arguments::
|
||||
|
||||
req.path_params["user_id"] # same as the keyword argument
|
||||
|
||||
**Request body** — for POST/PUT/PATCH requests, you need to ``await`` the
|
||||
body content::
|
||||
**Request body** — for POST, PUT, and PATCH requests, the client sends
|
||||
data in the body. Since reading the body is an I/O operation, you need to
|
||||
``await`` it::
|
||||
|
||||
# JSON body
|
||||
# JSON body (the most common format for APIs)
|
||||
data = await req.media()
|
||||
|
||||
# Form data
|
||||
# Form data (from HTML forms)
|
||||
data = await req.media("form")
|
||||
|
||||
# File uploads
|
||||
# File uploads (multipart)
|
||||
files = await req.media("files")
|
||||
|
||||
# Raw bytes
|
||||
@@ -170,41 +235,59 @@ body content::
|
||||
|
||||
**Other useful properties**::
|
||||
|
||||
req.is_json # True if content type is JSON
|
||||
req.cookies # dict of cookies
|
||||
req.session # session data (dict)
|
||||
req.client # (host, port) tuple
|
||||
req.is_secure # True if HTTPS
|
||||
req.is_json # True if the content type is JSON
|
||||
req.cookies # dict of cookies sent by the client
|
||||
req.session # session data (a signed, server-side dict)
|
||||
req.client # (host, port) tuple — the client's IP address
|
||||
req.is_secure # True if the request came over HTTPS
|
||||
|
||||
|
||||
Rendering Templates
|
||||
-------------------
|
||||
|
||||
Responder includes built-in `Jinja2 <https://jinja.palletsprojects.com/>`_
|
||||
support. Templates are loaded from the ``templates/`` directory by default.
|
||||
While APIs typically return JSON, many web applications need to render
|
||||
HTML pages. Responder includes built-in support for
|
||||
`Jinja2 <https://jinja.palletsprojects.com/>`_, one of the most popular
|
||||
templating engines in the Python ecosystem.
|
||||
|
||||
The simplest way is to use ``api.template()``::
|
||||
Templates let you write HTML with placeholders that get filled in with
|
||||
dynamic data. This keeps your presentation logic (HTML) separate from
|
||||
your application logic (Python) — a pattern called
|
||||
*separation of concerns*.
|
||||
|
||||
The simplest way to render a template is ``api.template()``. Templates
|
||||
are loaded from the ``templates/`` directory by default::
|
||||
|
||||
@api.route("/hello/{name}/html")
|
||||
def hello_html(req, resp, *, name):
|
||||
resp.html = api.template("hello.html", name=name)
|
||||
|
||||
You can also use the ``Templates`` class directly for more control::
|
||||
The template file ``templates/hello.html`` might look like::
|
||||
|
||||
<h1>Hello, {{ name }}!</h1>
|
||||
|
||||
The ``{{ name }}`` part is a Jinja2 expression — it gets replaced with
|
||||
the value you passed in.
|
||||
|
||||
You can also use the ``Templates`` class directly for more control over
|
||||
the template directory and configuration::
|
||||
|
||||
from responder.templates import Templates
|
||||
|
||||
templates = Templates(directory="templates")
|
||||
templates = Templates(directory="my_templates")
|
||||
|
||||
@api.route("/page")
|
||||
def page(req, resp):
|
||||
resp.html = templates.render("page.html", title="Hello")
|
||||
|
||||
Async rendering is supported too::
|
||||
For applications that need non-blocking template rendering (rare, but
|
||||
useful under extreme load), async rendering is supported::
|
||||
|
||||
templates = Templates(directory="templates", enable_async=True)
|
||||
resp.html = await templates.render_async("page.html", title="Hello")
|
||||
|
||||
You can render template strings without a file::
|
||||
And for quick one-off templates, you can render a string directly without
|
||||
a file::
|
||||
|
||||
resp.html = api.template_string("Hello, {{ name }}!", name="world")
|
||||
|
||||
@@ -213,7 +296,13 @@ Background Tasks
|
||||
----------------
|
||||
|
||||
Sometimes you want to accept a request, respond immediately, and do the
|
||||
actual processing later. Responder makes this easy with background tasks::
|
||||
actual processing later. This is a common pattern for operations that take
|
||||
a long time — sending emails, processing images, updating caches, or
|
||||
calling slow external APIs.
|
||||
|
||||
Responder makes this easy with background tasks. Decorate any function
|
||||
with ``@api.background.task`` and it will run in a thread pool, separate
|
||||
from the request/response cycle::
|
||||
|
||||
@api.route("/incoming")
|
||||
async def receive_incoming(req, resp):
|
||||
@@ -227,8 +316,17 @@ actual processing later. Responder makes this easy with background tasks::
|
||||
|
||||
process_data(data)
|
||||
|
||||
# Respond immediately — processing continues in the background
|
||||
# This response is sent immediately, while process_data
|
||||
# continues running in the background.
|
||||
resp.media = {"status": "accepted"}
|
||||
|
||||
The ``@api.background.task`` decorator wraps any function to run in a thread
|
||||
pool. The client gets an immediate response while the work continues.
|
||||
The client gets an instant response — the heavy lifting happens after.
|
||||
This is the same pattern used by task queues like Celery, but much simpler
|
||||
for lightweight use cases where you don't need a full message broker.
|
||||
|
||||
.. note::
|
||||
|
||||
Background tasks run in threads, not processes. They share memory with
|
||||
your application, which makes them fast to start but means CPU-intensive
|
||||
work will block the event loop. For heavy computation, consider a proper
|
||||
task queue.
|
||||
|
||||
@@ -279,7 +279,7 @@ Tips
|
||||
``API()`` configuration (like ``cors=True``), create a new instance
|
||||
in the test rather than sharing the fixture.
|
||||
|
||||
- **Use ``api.url_for()``** instead of hard-coded paths. It's a small
|
||||
- Use ``api.url_for()`` instead of hard-coded paths. It's a small
|
||||
thing, but it makes refactoring painless.
|
||||
|
||||
- **Test the contract, not the implementation.** Assert on status codes,
|
||||
|
||||
+265
-176
@@ -1,15 +1,27 @@
|
||||
Feature Tour
|
||||
============
|
||||
|
||||
This section walks through Responder's features in detail. Each section
|
||||
includes working code examples you can copy into your application.
|
||||
This section walks through Responder's features in depth. Each section
|
||||
explains the concept, shows working code, and explains the design choices
|
||||
behind it. If you're new to web development, this is a good place to learn
|
||||
how modern web frameworks work under the hood.
|
||||
|
||||
|
||||
Method Filtering
|
||||
----------------
|
||||
|
||||
By default, a route matches all HTTP methods. If you want to restrict a
|
||||
route to specific methods, pass the ``methods`` parameter::
|
||||
HTTP defines several *methods* (also called verbs) that describe what a
|
||||
client wants to do with a resource. The most common are:
|
||||
|
||||
- ``GET`` — retrieve data
|
||||
- ``POST`` — create something new
|
||||
- ``PUT`` — replace something entirely
|
||||
- ``PATCH`` — update part of something
|
||||
- ``DELETE`` — remove something
|
||||
|
||||
By default, a Responder route matches all methods. This is fine for simple
|
||||
endpoints, but REST APIs typically map different methods to different
|
||||
operations. Use the ``methods`` parameter to restrict a route::
|
||||
|
||||
@api.route("/items", methods=["GET"])
|
||||
def list_items(req, resp):
|
||||
@@ -20,15 +32,22 @@ route to specific methods, pass the ``methods`` parameter::
|
||||
data = await req.media()
|
||||
resp.media = {"created": data}
|
||||
|
||||
Note the ``check_existing=False`` — this allows you to register multiple
|
||||
handlers for the same path with different methods.
|
||||
Note the ``check_existing=False`` — Responder normally prevents you from
|
||||
registering two routes with the same path (to catch typos). When you
|
||||
intentionally want multiple handlers for the same path with different
|
||||
methods, you need to opt in.
|
||||
|
||||
|
||||
Class-Based Views
|
||||
-----------------
|
||||
|
||||
For more complex resources, you can use class-based views. Responder will
|
||||
dispatch to the appropriate method handler based on the HTTP method::
|
||||
Function-based views are great for simple endpoints, but sometimes you want
|
||||
to group related HTTP methods together into a single resource. This is
|
||||
where class-based views come in — a pattern popularized by
|
||||
`Falcon <https://falconframework.org/>`_.
|
||||
|
||||
Responder dispatches to the appropriate method handler based on the HTTP
|
||||
method::
|
||||
|
||||
@api.route("/{greeting}")
|
||||
class GreetingResource:
|
||||
@@ -47,14 +66,19 @@ middleware scoped to a single route. Method-specific handlers (``on_get``,
|
||||
``on_post``, ``on_put``, ``on_delete``, etc.) are called after.
|
||||
|
||||
No inheritance required — just define a class with the right method names.
|
||||
This is simpler than Django's ``View`` classes and more Pythonic than
|
||||
framework-specific base classes.
|
||||
|
||||
|
||||
Lifespan Events
|
||||
---------------
|
||||
|
||||
Modern applications often need to set up resources on startup (database
|
||||
connections, caches, ML models) and tear them down on shutdown. Responder
|
||||
supports the lifespan context manager pattern::
|
||||
Real applications need to set up resources when they start (database
|
||||
connection pools, ML models, caches) and tear them down when they stop.
|
||||
This is called the application *lifespan*.
|
||||
|
||||
The modern approach is the *context manager* pattern, where startup and
|
||||
shutdown are two halves of the same block::
|
||||
|
||||
from contextlib import asynccontextmanager
|
||||
|
||||
@@ -68,7 +92,11 @@ supports the lifespan context manager pattern::
|
||||
|
||||
api = responder.API(lifespan=lifespan)
|
||||
|
||||
You can also use the traditional event decorator style::
|
||||
Everything before ``yield`` runs at startup. Everything after runs at
|
||||
shutdown. If startup fails, the server won't start. If shutdown raises,
|
||||
it's logged but the server still exits.
|
||||
|
||||
The traditional event decorator style also works::
|
||||
|
||||
@api.on_event("startup")
|
||||
async def startup():
|
||||
@@ -78,57 +106,71 @@ You can also use the traditional event decorator style::
|
||||
async def shutdown():
|
||||
print("shutting down")
|
||||
|
||||
The context manager approach is preferred for new code — it makes the
|
||||
startup/shutdown relationship explicit and keeps related code together.
|
||||
The context manager is preferred for new code — it keeps related startup
|
||||
and shutdown logic together and makes resource cleanup more explicit.
|
||||
|
||||
|
||||
Serving Files
|
||||
-------------
|
||||
|
||||
Serve files from disk with automatic content-type detection. Responder
|
||||
uses Python's ``mimetypes`` module to figure out the right ``Content-Type``
|
||||
header for you::
|
||||
Web applications often need to serve files — downloads, reports, images.
|
||||
Responder makes this simple with ``resp.file()``, which reads a file from
|
||||
disk and sets the ``Content-Type`` header automatically using Python's
|
||||
``mimetypes`` module::
|
||||
|
||||
@api.route("/download")
|
||||
def download(req, resp):
|
||||
resp.file("reports/annual.pdf")
|
||||
|
||||
You can override the content type if needed::
|
||||
You can override the content type if the automatic detection isn't right::
|
||||
|
||||
@api.route("/image")
|
||||
def image(req, resp):
|
||||
resp.file("photos/cat.jpg", content_type="image/jpeg")
|
||||
|
||||
For large files, use ``resp.stream_file()`` to avoid loading the entire
|
||||
file into memory. This streams the file in chunks::
|
||||
|
||||
@api.route("/export")
|
||||
def export(req, resp):
|
||||
resp.stream_file("data/export.csv")
|
||||
|
||||
|
||||
Custom Error Handling
|
||||
---------------------
|
||||
|
||||
By default, unhandled exceptions result in a 500 Internal Server Error.
|
||||
You can register custom handlers for specific exception types to return
|
||||
structured error responses::
|
||||
In production, you don't want your users to see raw Python tracebacks.
|
||||
Responder lets you register custom handlers for specific exception types,
|
||||
so you can return clean, structured error responses::
|
||||
|
||||
@api.exception_handler(ValueError)
|
||||
async def handle_value_error(req, resp, exc):
|
||||
resp.status_code = 400
|
||||
resp.media = {"error": str(exc)}
|
||||
|
||||
Now, any route that raises a ``ValueError`` will return a clean 400 response
|
||||
with a JSON error message instead of a generic 500 page.
|
||||
Now, any route that raises a ``ValueError`` will return a clean JSON
|
||||
response with a 400 status code instead of a generic 500 error page.
|
||||
|
||||
This is a common pattern in API development — you define your own exception
|
||||
classes for different error conditions, register handlers for each, and
|
||||
your API always returns consistent, machine-readable error responses.
|
||||
|
||||
|
||||
Before-Request Hooks
|
||||
--------------------
|
||||
|
||||
Run code before every request. This is useful for logging, adding common
|
||||
headers, or setting up per-request state::
|
||||
Sometimes you need to run the same code before every request —
|
||||
authentication checks, request logging, adding common headers, or setting
|
||||
up per-request state. Before-request hooks let you do this without
|
||||
duplicating code in every route::
|
||||
|
||||
@api.route(before_request=True)
|
||||
def add_headers(req, resp):
|
||||
resp.headers["X-API-Version"] = "3.1"
|
||||
resp.headers["X-API-Version"] = "3.2"
|
||||
|
||||
**Short-circuiting:** If your hook sets ``resp.status_code``, the route
|
||||
handler will be skipped entirely and the response will be sent immediately.
|
||||
This is the pattern for authentication guards::
|
||||
**Short-circuiting** is the really powerful part. If your hook sets
|
||||
``resp.status_code``, the route handler is skipped entirely and the
|
||||
response is sent immediately. This is the pattern for authentication::
|
||||
|
||||
@api.route(before_request=True)
|
||||
def auth_check(req, resp):
|
||||
@@ -137,19 +179,36 @@ This is the pattern for authentication guards::
|
||||
resp.media = {"error": "unauthorized"}
|
||||
|
||||
If the ``Authorization`` header is missing, the client gets a 401 response
|
||||
and the actual route handler never runs.
|
||||
and the actual route handler never runs. This is cleaner than adding
|
||||
auth checks to every individual route.
|
||||
|
||||
WebSocket hooks work the same way::
|
||||
|
||||
@api.before_request(websocket=True)
|
||||
async def ws_auth(ws):
|
||||
await ws.accept()
|
||||
After-Request Hooks
|
||||
-------------------
|
||||
|
||||
The complement to before-request hooks. After-request hooks run after the
|
||||
route handler completes but before the response is sent. They're useful
|
||||
for logging, adding response headers, or any post-processing::
|
||||
|
||||
@api.after_request()
|
||||
def log_response(req, resp):
|
||||
print(f"{req.method} {req.full_url} -> {resp.status_code}")
|
||||
|
||||
@api.after_request()
|
||||
async def add_timing(req, resp):
|
||||
resp.headers["X-Served-By"] = "responder"
|
||||
|
||||
|
||||
WebSocket Support
|
||||
-----------------
|
||||
|
||||
Responder supports WebSockets for real-time, bidirectional communication::
|
||||
HTTP is a request-response protocol — the client asks, the server answers.
|
||||
But some applications need real-time, bidirectional communication: chat
|
||||
apps, live dashboards, multiplayer games, collaborative editors.
|
||||
|
||||
`WebSockets <https://en.wikipedia.org/wiki/WebSocket>`_ solve this by
|
||||
upgrading an HTTP connection into a persistent, full-duplex channel where
|
||||
both sides can send messages at any time::
|
||||
|
||||
@api.route("/ws", websocket=True)
|
||||
async def websocket(ws):
|
||||
@@ -161,14 +220,54 @@ Responder supports WebSockets for real-time, bidirectional communication::
|
||||
|
||||
You can send and receive in multiple formats:
|
||||
|
||||
- ``send_text`` / ``receive_text`` — plain text
|
||||
- ``send_json`` / ``receive_json`` — JSON objects
|
||||
- ``send_text`` / ``receive_text`` — plain text strings
|
||||
- ``send_json`` / ``receive_json`` — JSON objects (auto-serialized)
|
||||
- ``send_bytes`` / ``receive_bytes`` — raw binary data
|
||||
|
||||
WebSocket routes are marked with ``websocket=True`` in the route decorator.
|
||||
They receive a ``ws`` object instead of ``req`` and ``resp``.
|
||||
|
||||
|
||||
Server-Sent Events (SSE)
|
||||
-------------------------
|
||||
|
||||
SSE is a simpler alternative to WebSockets for *one-way* real-time
|
||||
communication — the server pushes events to the client, but the client
|
||||
can't send messages back. This is perfect for live feeds, progress bars,
|
||||
notification streams, and AI response streaming.
|
||||
|
||||
Unlike WebSockets, SSE works over plain HTTP, is automatically reconnected
|
||||
by the browser, and doesn't require any special client-side libraries::
|
||||
|
||||
@api.route("/events")
|
||||
async def events(req, resp):
|
||||
@resp.sse
|
||||
async def stream():
|
||||
for i in range(10):
|
||||
yield {"data": f"message {i}"}
|
||||
|
||||
On the client side, you consume SSE events with JavaScript's built-in
|
||||
``EventSource`` API::
|
||||
|
||||
const source = new EventSource("/events");
|
||||
source.onmessage = (event) => {
|
||||
console.log(event.data);
|
||||
};
|
||||
|
||||
Each yielded value can be a string (treated as data) or a dict with the
|
||||
standard SSE fields::
|
||||
|
||||
yield {"event": "update", "data": "hello", "id": "1", "retry": "5000"}
|
||||
yield "simple string message"
|
||||
|
||||
|
||||
GraphQL
|
||||
-------
|
||||
|
||||
`GraphQL <https://graphql.org/>`_ is a query language for APIs that lets
|
||||
clients request exactly the data they need — no more, no less. Instead of
|
||||
multiple REST endpoints, you define a schema and let clients query it.
|
||||
|
||||
Responder includes built-in GraphQL support via
|
||||
`Graphene <https://graphene-python.org/>`_. Set up a full GraphQL endpoint
|
||||
with a single method call::
|
||||
@@ -183,9 +282,10 @@ with a single method call::
|
||||
|
||||
api.graphql("/graphql", schema=graphene.Schema(query=Query))
|
||||
|
||||
Visiting ``/graphql`` in a browser renders the GraphiQL interactive IDE,
|
||||
where you can explore your schema and test queries. Programmatic clients
|
||||
can POST JSON queries to the same endpoint.
|
||||
Visiting ``/graphql`` in a browser renders the
|
||||
`GraphiQL <https://github.com/graphql/graphiql>`_ interactive IDE, where
|
||||
you can explore your schema, write queries, and see results in real-time.
|
||||
Programmatic clients can POST JSON queries to the same endpoint.
|
||||
|
||||
You can access the Responder request and response objects in your resolvers
|
||||
through ``info.context["request"]`` and ``info.context["response"]``.
|
||||
@@ -194,8 +294,12 @@ through ``info.context["request"]`` and ``info.context["response"]``.
|
||||
OpenAPI Documentation
|
||||
---------------------
|
||||
|
||||
Responder can generate an OpenAPI schema and serve interactive API
|
||||
documentation automatically::
|
||||
`OpenAPI <https://www.openapis.org/>`_ (formerly Swagger) is the industry
|
||||
standard for describing REST APIs. An OpenAPI specification lets you
|
||||
auto-generate interactive documentation, client libraries, and validation
|
||||
logic.
|
||||
|
||||
Responder generates OpenAPI specs from your code::
|
||||
|
||||
api = responder.API(
|
||||
title="Pet Store",
|
||||
@@ -211,9 +315,11 @@ This gives you:
|
||||
|
||||
There are three ways to document your endpoints.
|
||||
|
||||
**Pydantic models** — the recommended approach for new APIs. Use
|
||||
``request_model`` and ``response_model`` to annotate your routes, and
|
||||
Responder will generate the schema automatically::
|
||||
**Pydantic models** — the recommended approach. Use ``request_model`` and
|
||||
``response_model`` to annotate your routes, and Responder generates the
|
||||
schema automatically. When ``request_model`` is set, request bodies are
|
||||
also validated automatically — invalid inputs get a ``422`` response with
|
||||
detailed error messages::
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
@@ -232,19 +338,11 @@ Responder will generate the schema automatically::
|
||||
data = await req.media()
|
||||
resp.media = {"id": 1, **data}
|
||||
|
||||
This generates a full OpenAPI path with ``requestBody`` and ``responses``
|
||||
schemas, all linked by ``$ref`` to your Pydantic models in
|
||||
``components/schemas``.
|
||||
When ``response_model`` is set, the response is serialized through the
|
||||
model — extra fields are stripped and types are enforced.
|
||||
|
||||
You can also register standalone schemas with the ``@api.schema`` decorator::
|
||||
|
||||
@api.schema("Pet")
|
||||
class Pet(BaseModel):
|
||||
name: str
|
||||
age: int = 0
|
||||
|
||||
**YAML docstrings** — inline your OpenAPI spec directly in the docstring.
|
||||
This gives you full control over every detail::
|
||||
**YAML docstrings** — for full control, embed OpenAPI YAML in the
|
||||
docstring::
|
||||
|
||||
@api.route("/pets")
|
||||
def list_pets(req, resp):
|
||||
@@ -258,8 +356,7 @@ This gives you full control over every detail::
|
||||
"""
|
||||
resp.media = [{"name": "Fido"}]
|
||||
|
||||
**Marshmallow schemas** — if you're already using marshmallow for
|
||||
validation, Responder integrates with it via the apispec plugin::
|
||||
**Marshmallow schemas** — if you're already using marshmallow::
|
||||
|
||||
from marshmallow import Schema, fields
|
||||
|
||||
@@ -267,19 +364,43 @@ validation, Responder integrates with it via the apispec plugin::
|
||||
class PetSchema(Schema):
|
||||
name = fields.Str()
|
||||
|
||||
All three approaches can be mixed in the same API. Pydantic models,
|
||||
marshmallow schemas, and YAML docstrings all contribute to the same
|
||||
generated OpenAPI specification.
|
||||
All three approaches can be mixed in the same API. You can choose from
|
||||
multiple documentation themes: ``swagger_ui`` (default), ``redoc``,
|
||||
``rapidoc``, or ``elements``.
|
||||
|
||||
You can choose from multiple documentation themes:
|
||||
``swagger_ui`` (default), ``redoc``, ``rapidoc``, or ``elements``.
|
||||
|
||||
Route Groups
|
||||
------------
|
||||
|
||||
As your application grows, you'll want to organize routes logically.
|
||||
Route groups let you share a URL prefix across related endpoints — a
|
||||
common pattern for API versioning::
|
||||
|
||||
v1 = api.group("/v1")
|
||||
|
||||
@v1.route("/users")
|
||||
def list_users(req, resp):
|
||||
resp.media = []
|
||||
|
||||
@v1.route("/users/{user_id:int}")
|
||||
def get_user(req, resp, *, user_id):
|
||||
resp.media = {"id": user_id}
|
||||
|
||||
v2 = api.group("/v2")
|
||||
|
||||
@v2.route("/users")
|
||||
def list_users_v2(req, resp):
|
||||
resp.media = {"users": [], "total": 0}
|
||||
|
||||
This keeps your code organized without affecting the routing logic.
|
||||
|
||||
|
||||
Mounting Other Apps
|
||||
-------------------
|
||||
|
||||
Responder can mount any WSGI or ASGI application at a subroute. This means
|
||||
you can gradually migrate from Flask, or run multiple frameworks side by side::
|
||||
Responder can mount any WSGI or ASGI application at a subroute. This is
|
||||
incredibly useful for gradual migrations — you can run Flask and Responder
|
||||
side by side, moving routes over one at a time::
|
||||
|
||||
from flask import Flask
|
||||
|
||||
@@ -293,12 +414,17 @@ you can gradually migrate from Flask, or run multiple frameworks side by side::
|
||||
|
||||
Requests to ``/flask/`` will be handled by Flask. Everything else goes
|
||||
through Responder. Both WSGI and ASGI apps are supported — Responder
|
||||
wraps WSGI apps automatically.
|
||||
wraps WSGI apps in an ASGI adapter automatically.
|
||||
|
||||
|
||||
Cookies
|
||||
-------
|
||||
|
||||
`Cookies <https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies>`_ are
|
||||
small pieces of data that the server asks the browser to store and send
|
||||
back with every subsequent request. They're the foundation of sessions,
|
||||
authentication tokens, and user preferences on the web.
|
||||
|
||||
Reading and writing cookies is straightforward::
|
||||
|
||||
# Read cookies from the request
|
||||
@@ -307,26 +433,28 @@ Reading and writing cookies is straightforward::
|
||||
# Set a cookie on the response
|
||||
resp.cookies["hello"] = "world"
|
||||
|
||||
For more control over cookie directives, use ``set_cookie``::
|
||||
For production use, you'll want to set security directives. The
|
||||
``httponly`` flag prevents JavaScript from reading the cookie (defending
|
||||
against XSS attacks), and ``secure`` ensures it's only sent over HTTPS::
|
||||
|
||||
resp.set_cookie(
|
||||
"token",
|
||||
value="abc123",
|
||||
max_age=3600,
|
||||
secure=True,
|
||||
httponly=True,
|
||||
max_age=3600, # expires in 1 hour
|
||||
secure=True, # HTTPS only
|
||||
httponly=True, # no JavaScript access
|
||||
path="/",
|
||||
)
|
||||
|
||||
Supported directives: ``key``, ``value``, ``expires``, ``max_age``,
|
||||
``domain``, ``path``, ``secure``, ``httponly``.
|
||||
|
||||
|
||||
Cookie-Based Sessions
|
||||
---------------------
|
||||
|
||||
Responder has built-in support for signed, cookie-based sessions. Just
|
||||
read from and write to the ``session`` dictionary::
|
||||
Sessions let you store per-user data across multiple requests. Responder's
|
||||
built-in sessions are cookie-based — the session data is serialized, signed
|
||||
with your secret key, and stored in a cookie. The signature prevents
|
||||
tampering: if someone modifies the cookie, the signature won't match and
|
||||
the data will be rejected::
|
||||
|
||||
@api.route("/login")
|
||||
def login(req, resp):
|
||||
@@ -336,13 +464,9 @@ read from and write to the ``session`` dictionary::
|
||||
def profile(req, resp):
|
||||
resp.media = {"user": req.session.get("username")}
|
||||
|
||||
The session data is stored in a cookie called ``Responder-Session``. It's
|
||||
signed for tamper protection, so you can trust that the data originated
|
||||
from your server.
|
||||
|
||||
.. warning::
|
||||
|
||||
For production use, always set a secret key::
|
||||
Always set a secret key in production. The default key is not secret::
|
||||
|
||||
api = responder.API(secret_key="your-secret-key-here")
|
||||
|
||||
@@ -350,141 +474,98 @@ from your server.
|
||||
Static Files
|
||||
------------
|
||||
|
||||
Static files are served from the ``static/`` directory by default::
|
||||
Most web applications serve static assets — CSS stylesheets, JavaScript
|
||||
files, images, fonts. Responder serves these from the ``static/`` directory
|
||||
by default::
|
||||
|
||||
api = responder.API(static_dir="static", static_route="/static")
|
||||
|
||||
Place your CSS, JavaScript, images, and other assets in the ``static/``
|
||||
directory and they'll be served automatically.
|
||||
Place your assets in the ``static/`` directory and they'll be served
|
||||
automatically at ``/static/style.css``, ``/static/app.js``, etc.
|
||||
|
||||
For single-page applications, you can serve ``index.html`` as the default
|
||||
response for all unmatched routes::
|
||||
For single-page applications (React, Vue, Angular), you can serve
|
||||
``index.html`` as the default response for all unmatched routes::
|
||||
|
||||
api.add_route("/", static=True)
|
||||
|
||||
You can add additional static directories at runtime::
|
||||
|
||||
api.static_app.add_directory("extra_assets")
|
||||
|
||||
|
||||
CORS
|
||||
----
|
||||
|
||||
Enable Cross-Origin Resource Sharing for your API::
|
||||
`CORS <https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS>`_ (Cross-
|
||||
Origin Resource Sharing) is a security mechanism that controls which
|
||||
websites can make requests to your API. Browsers enforce this — if your
|
||||
API is at ``api.example.com`` and your frontend is at ``app.example.com``,
|
||||
the browser will block requests unless your API explicitly allows it.
|
||||
|
||||
Enable CORS and configure which origins are allowed::
|
||||
|
||||
api = responder.API(cors=True, cors_params={
|
||||
"allow_origins": ["https://example.com"],
|
||||
"allow_origins": ["https://app.example.com"],
|
||||
"allow_methods": ["GET", "POST"],
|
||||
"allow_headers": ["*"],
|
||||
"allow_credentials": True,
|
||||
"max_age": 600,
|
||||
})
|
||||
|
||||
The default CORS policy is restrictive — you must explicitly enable the
|
||||
origins, methods, and headers your frontend needs.
|
||||
The default policy is restrictive — you must explicitly allow each origin.
|
||||
Using ``["*"]`` for allow_origins permits any website to call your API,
|
||||
which is fine for public APIs but not for private ones.
|
||||
|
||||
|
||||
HSTS
|
||||
----
|
||||
|
||||
Force all traffic to HTTPS with a single flag::
|
||||
`HSTS <https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security>`_
|
||||
(HTTP Strict Transport Security) tells browsers to always use HTTPS when
|
||||
communicating with your server. Once a browser sees the HSTS header, it
|
||||
will refuse to connect over plain HTTP, even if the user types ``http://``
|
||||
in the address bar::
|
||||
|
||||
api = responder.API(enable_hsts=True)
|
||||
|
||||
This adds the ``Strict-Transport-Security`` header and redirects HTTP
|
||||
requests to HTTPS.
|
||||
|
||||
|
||||
Trusted Hosts
|
||||
-------------
|
||||
|
||||
Protect against HTTP Host header attacks by restricting which hostnames
|
||||
your application will respond to::
|
||||
The ``Host`` header in an HTTP request tells the server which domain name
|
||||
the client used. Attackers can forge this header to trick your application
|
||||
into generating URLs to malicious domains (a class of attack called *Host
|
||||
header injection*).
|
||||
|
||||
Restrict which hostnames your application accepts::
|
||||
|
||||
api = responder.API(allowed_hosts=["example.com", "*.example.com"])
|
||||
|
||||
Requests with a ``Host`` header that doesn't match any of the patterns
|
||||
will receive a 400 Bad Request response. Wildcard domains are supported.
|
||||
|
||||
By default, all hostnames are allowed.
|
||||
|
||||
|
||||
Server-Sent Events (SSE)
|
||||
------------------------
|
||||
|
||||
Stream real-time updates to the client using Server-Sent Events. This is
|
||||
great for live feeds, progress updates, and AI streaming responses::
|
||||
|
||||
@api.route("/events")
|
||||
async def events(req, resp):
|
||||
@resp.sse
|
||||
async def stream():
|
||||
for i in range(10):
|
||||
yield {"data": f"message {i}"}
|
||||
|
||||
Each yielded value can be a string (treated as data) or a dict with
|
||||
``data``, ``event``, ``id``, and ``retry`` fields::
|
||||
|
||||
yield {"event": "update", "data": "hello", "id": "1"}
|
||||
yield "simple string message"
|
||||
|
||||
|
||||
Streaming Files
|
||||
---------------
|
||||
|
||||
For large files, use ``resp.stream_file()`` to stream the content without
|
||||
loading the entire file into memory::
|
||||
|
||||
@api.route("/download")
|
||||
def download(req, resp):
|
||||
resp.stream_file("large-dataset.csv")
|
||||
|
||||
For small files where memory isn't a concern, ``resp.file()`` loads the
|
||||
entire file at once — simpler but less efficient for large files.
|
||||
|
||||
|
||||
After-Request Hooks
|
||||
-------------------
|
||||
|
||||
Run code after every request, useful for logging, adding headers, or
|
||||
cleanup::
|
||||
|
||||
@api.after_request()
|
||||
def log_response(req, resp):
|
||||
print(f"{req.method} {req.full_url} -> {resp.status_code}")
|
||||
|
||||
|
||||
Route Groups
|
||||
------------
|
||||
|
||||
Organize related routes with a shared URL prefix. Useful for API versioning
|
||||
and logical grouping::
|
||||
|
||||
v1 = api.group("/v1")
|
||||
|
||||
@v1.route("/users")
|
||||
def list_users(req, resp):
|
||||
resp.media = []
|
||||
|
||||
@v1.route("/users/{user_id:int}")
|
||||
def get_user(req, resp, *, user_id):
|
||||
resp.media = {"id": user_id}
|
||||
Requests with unrecognized hosts get a ``400 Bad Request``. Wildcard
|
||||
patterns are supported. By default, all hostnames are allowed.
|
||||
|
||||
|
||||
Request ID
|
||||
----------
|
||||
|
||||
Auto-generate unique request IDs for tracing and debugging. If the client
|
||||
sends an ``X-Request-ID`` header, it's forwarded; otherwise a new UUID is
|
||||
generated::
|
||||
In distributed systems, tracing a single request across multiple services
|
||||
is essential for debugging. Request IDs are unique identifiers attached to
|
||||
each request — if something goes wrong, you can search your logs for that
|
||||
ID and find every related event.
|
||||
|
||||
Responder can auto-generate request IDs. If the client sends an
|
||||
``X-Request-ID`` header (common in microservice architectures), it's
|
||||
forwarded. Otherwise, a new UUID is generated::
|
||||
|
||||
api = responder.API(request_id=True)
|
||||
|
||||
The ID appears in the ``X-Request-ID`` response header.
|
||||
|
||||
|
||||
Rate Limiting
|
||||
-------------
|
||||
|
||||
Built-in token bucket rate limiter::
|
||||
Rate limiting prevents individual clients from overwhelming your API with
|
||||
too many requests. It's essential for public APIs, and good practice even
|
||||
for internal ones.
|
||||
|
||||
Responder includes a built-in token bucket rate limiter::
|
||||
|
||||
from responder.ext.ratelimit import RateLimiter
|
||||
|
||||
@@ -492,17 +573,25 @@ Built-in token bucket rate limiter::
|
||||
limiter.install(api)
|
||||
|
||||
When the limit is exceeded, clients receive a ``429 Too Many Requests``
|
||||
response with ``Retry-After`` and ``X-RateLimit-Remaining`` headers.
|
||||
response with a ``Retry-After`` header. Every response includes
|
||||
``X-RateLimit-Limit`` and ``X-RateLimit-Remaining`` headers so clients
|
||||
can pace themselves.
|
||||
|
||||
The rate limiter is per-client, keyed by IP address.
|
||||
|
||||
|
||||
MessagePack
|
||||
-----------
|
||||
|
||||
In addition to JSON and YAML, Responder supports MessagePack for efficient
|
||||
binary serialization::
|
||||
`MessagePack <https://msgpack.org/>`_ is a binary serialization format
|
||||
that's more compact and faster to parse than JSON. It's useful for
|
||||
high-throughput APIs, IoT devices, and anywhere bandwidth matters.
|
||||
|
||||
# Decode MessagePack request body
|
||||
Responder supports MessagePack alongside JSON and YAML::
|
||||
|
||||
# Decode a MessagePack request body
|
||||
data = await req.media("msgpack")
|
||||
|
||||
# Content negotiation also works — clients can send
|
||||
# Accept: application/x-msgpack to receive MessagePack responses.
|
||||
Content negotiation works too — clients can send
|
||||
``Accept: application/x-msgpack`` to receive MessagePack responses
|
||||
instead of JSON.
|
||||
|
||||
+49
-34
@@ -42,29 +42,27 @@
|
||||
|
||||
<section id="deployment">
|
||||
<h1>Deployment<a class="headerlink" href="#deployment" title="Link to this heading">¶</a></h1>
|
||||
<p>Responder applications are standard ASGI apps. You can deploy them anywhere
|
||||
you’d deploy a Python web service.</p>
|
||||
<p>Responder applications are standard <a class="reference external" href="https://asgi.readthedocs.io/">ASGI</a>
|
||||
apps. ASGI (Asynchronous Server Gateway Interface) is the modern successor
|
||||
to WSGI — it supports async, WebSockets, and HTTP/2. This means you can
|
||||
deploy a Responder app anywhere that runs Python, using any ASGI server.</p>
|
||||
<section id="running-locally">
|
||||
<h2>Running Locally<a class="headerlink" href="#running-locally" title="Link to this heading">¶</a></h2>
|
||||
<p>The simplest way to run your application:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># api.py</span>
|
||||
<span class="kn">import</span><span class="w"> </span><span class="nn">responder</span>
|
||||
|
||||
<span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">()</span>
|
||||
|
||||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">hello</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s2">"hello, world!"</span>
|
||||
|
||||
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
|
||||
<p>During development, <code class="docutils literal notranslate"><span class="pre">api.run()</span></code> is all you need:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
|
||||
<span class="n">api</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This starts a production uvicorn server on <code class="docutils literal notranslate"><span class="pre">127.0.0.1:5042</span></code>.</p>
|
||||
<p>This starts a <a class="reference external" href="https://www.uvicorn.org/">uvicorn</a> server on
|
||||
<code class="docutils literal notranslate"><span class="pre">127.0.0.1:5042</span></code>. Uvicorn is a lightning-fast ASGI server built on
|
||||
<a class="reference external" href="https://uvloop.readthedocs.io/">uvloop</a> — it handles thousands of
|
||||
concurrent connections efficiently and protects against slowloris attacks,
|
||||
making a reverse proxy like nginx optional for many deployments.</p>
|
||||
</section>
|
||||
<section id="docker">
|
||||
<h2>Docker<a class="headerlink" href="#docker" title="Link to this heading">¶</a></h2>
|
||||
<p>A minimal Dockerfile for deploying a Responder application:</p>
|
||||
<p>Docker is the most common way to package and deploy web applications.
|
||||
Here’s a minimal Dockerfile:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">FROM</span> <span class="n">python</span><span class="p">:</span><span class="mf">3.13</span><span class="o">-</span><span class="n">slim</span>
|
||||
<span class="n">WORKDIR</span> <span class="o">/</span><span class="n">app</span>
|
||||
<span class="n">COPY</span> <span class="o">.</span> <span class="o">.</span>
|
||||
@@ -79,40 +77,57 @@ you’d deploy a Python web service.</p>
|
||||
$ docker run -p 8000:80 myapi
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">python:3.13-slim</span></code> image is about 150MB — small enough for fast
|
||||
deploys but includes everything you need. For even smaller images, you
|
||||
can use <code class="docutils literal notranslate"><span class="pre">python:3.13-alpine</span></code>, though some packages may need extra
|
||||
build dependencies.</p>
|
||||
</section>
|
||||
<section id="cloud-platforms">
|
||||
<h2>Cloud Platforms<a class="headerlink" href="#cloud-platforms" title="Link to this heading">¶</a></h2>
|
||||
<p>Responder automatically honors the <code class="docutils literal notranslate"><span class="pre">PORT</span></code> environment variable, which is
|
||||
set by most cloud platforms. When <code class="docutils literal notranslate"><span class="pre">PORT</span></code> is set, Responder binds to
|
||||
<code class="docutils literal notranslate"><span class="pre">0.0.0.0</span></code> on that port automatically.</p>
|
||||
<p>This works out of the box with:</p>
|
||||
<p>Responder automatically honors the <code class="docutils literal notranslate"><span class="pre">PORT</span></code> environment variable. When
|
||||
<code class="docutils literal notranslate"><span class="pre">PORT</span></code> is set, the server binds to <code class="docutils literal notranslate"><span class="pre">0.0.0.0</span></code> on that port — this is
|
||||
the convention that virtually every cloud platform uses.</p>
|
||||
<p>This means zero configuration on:</p>
|
||||
<ul class="simple">
|
||||
<li><p><strong>Fly.io</strong></p></li>
|
||||
<li><p><strong>Railway</strong></p></li>
|
||||
<li><p><strong>Render</strong></p></li>
|
||||
<li><p><strong>Google Cloud Run</strong></p></li>
|
||||
<li><p><strong>Azure Container Apps</strong></p></li>
|
||||
<li><p><strong>AWS App Runner</strong></p></li>
|
||||
<li><p><strong>Fly.io</strong> — <code class="docutils literal notranslate"><span class="pre">fly</span> <span class="pre">launch</span></code> and you’re done</p></li>
|
||||
<li><p><strong>Railway</strong> — push your code, Railway sets <code class="docutils literal notranslate"><span class="pre">PORT</span></code></p></li>
|
||||
<li><p><strong>Render</strong> — set start command to <code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">api.py</span></code></p></li>
|
||||
<li><p><strong>Google Cloud Run</strong> — containerize and deploy</p></li>
|
||||
<li><p><strong>Azure Container Apps</strong> — same pattern</p></li>
|
||||
<li><p><strong>AWS App Runner</strong> — and here too</p></li>
|
||||
</ul>
|
||||
<p>Just deploy your code and set the start command to <code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">api.py</span></code>.</p>
|
||||
<p>The pattern is always the same: deploy your code, set the start command
|
||||
to <code class="docutils literal notranslate"><span class="pre">python</span> <span class="pre">api.py</span></code>, and the platform handles the rest.</p>
|
||||
</section>
|
||||
<section id="uvicorn-directly">
|
||||
<h2>Uvicorn Directly<a class="headerlink" href="#uvicorn-directly" title="Link to this heading">¶</a></h2>
|
||||
<p>For more control over the production server, you can bypass <code class="docutils literal notranslate"><span class="pre">api.run()</span></code>
|
||||
and use uvicorn directly:</p>
|
||||
<p>For production deployments where you want more control, bypass
|
||||
<code class="docutils literal notranslate"><span class="pre">api.run()</span></code> and use uvicorn directly:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ uvicorn api:api --host 0.0.0.0 --port 8000 --workers 4
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This gives you access to all of uvicorn’s options: worker count, SSL
|
||||
certificates, access logging, and more. See the
|
||||
<a class="reference external" href="https://www.uvicorn.org/">uvicorn documentation</a> for details.</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">--workers</span></code> flag spawns multiple processes, each handling requests
|
||||
independently. A good starting point is 2-4 workers per CPU core.</p>
|
||||
<p>Uvicorn supports many options — SSL certificates, access logging, graceful
|
||||
shutdown timeouts, and more. See the
|
||||
<a class="reference external" href="https://www.uvicorn.org/deployment/">uvicorn documentation</a> for details.</p>
|
||||
</section>
|
||||
<section id="reverse-proxy">
|
||||
<h2>Reverse Proxy<a class="headerlink" href="#reverse-proxy" title="Link to this heading">¶</a></h2>
|
||||
<p>In production, you may want to place Responder behind a reverse proxy like
|
||||
nginx or Caddy for SSL termination, load balancing, or serving static assets.</p>
|
||||
<p>For high-traffic production deployments, you may want a reverse proxy like
|
||||
<a class="reference external" href="https://nginx.org/">nginx</a> or <a class="reference external" href="https://caddyserver.com/">Caddy</a> in
|
||||
front of your application for:</p>
|
||||
<ul class="simple">
|
||||
<li><p><strong>SSL/TLS termination</strong> — let the proxy handle HTTPS certificates</p></li>
|
||||
<li><p><strong>Load balancing</strong> — distribute traffic across multiple app instances</p></li>
|
||||
<li><p><strong>Static asset serving</strong> — offload static files to the proxy</p></li>
|
||||
<li><p><strong>Rate limiting</strong> — at the infrastructure level</p></li>
|
||||
</ul>
|
||||
<p>Responder’s <code class="docutils literal notranslate"><span class="pre">TrustedHostMiddleware</span></code> and <code class="docutils literal notranslate"><span class="pre">HTTPSRedirectMiddleware</span></code> work
|
||||
correctly behind proxies that set standard forwarding headers.</p>
|
||||
correctly behind proxies that set standard forwarding headers
|
||||
(<code class="docutils literal notranslate"><span class="pre">X-Forwarded-For</span></code>, <code class="docutils literal notranslate"><span class="pre">X-Forwarded-Proto</span></code>).</p>
|
||||
<p>That said, uvicorn is production-ready on its own. Many applications run
|
||||
uvicorn directly without a reverse proxy and do just fine.</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
||||
+3
-4
@@ -138,9 +138,12 @@ with — you’re in the right place.</p>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#serving-files">Serving Files</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#custom-error-handling">Custom Error Handling</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#before-request-hooks">Before-Request Hooks</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#after-request-hooks">After-Request Hooks</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#websocket-support">WebSocket Support</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#server-sent-events-sse">Server-Sent Events (SSE)</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#graphql">GraphQL</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#openapi-documentation">OpenAPI Documentation</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#route-groups">Route Groups</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#mounting-other-apps">Mounting Other Apps</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#cookies">Cookies</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#cookie-based-sessions">Cookie-Based Sessions</a></li>
|
||||
@@ -148,10 +151,6 @@ with — you’re in the right place.</p>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#cors">CORS</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#hsts">HSTS</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#trusted-hosts">Trusted Hosts</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#server-sent-events-sse">Server-Sent Events (SSE)</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#streaming-files">Streaming Files</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#after-request-hooks">After-Request Hooks</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#route-groups">Route Groups</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#request-id">Request ID</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#rate-limiting">Rate Limiting</a></li>
|
||||
<li class="toctree-l2"><a class="reference internal" href="tour.html#messagepack">MessagePack</a></li>
|
||||
|
||||
+148
-60
@@ -43,146 +43,204 @@
|
||||
<section id="quick-start">
|
||||
<h1>Quick Start<a class="headerlink" href="#quick-start" title="Link to this heading">¶</a></h1>
|
||||
<p>This guide will walk you through the basics of building a web service with
|
||||
Responder. By the end, you’ll know how to declare routes, handle requests,
|
||||
send responses, render templates, and process background tasks.</p>
|
||||
Responder. By the end, you’ll understand how HTTP requests and responses
|
||||
work, how to define routes, read data from clients, send data back, render
|
||||
HTML templates, and process work in the background.</p>
|
||||
<section id="create-a-web-service">
|
||||
<h2>Create a Web Service<a class="headerlink" href="#create-a-web-service" title="Link to this heading">¶</a></h2>
|
||||
<p>The first thing you need to do is declare a web service. This is the central
|
||||
object that holds all your routes, middleware, and configuration:</p>
|
||||
<p>Every web application starts with a single object — the application
|
||||
instance. In Responder, this is the <code class="docutils literal notranslate"><span class="pre">API</span></code> class. It holds your routes,
|
||||
middleware, templates, and configuration. Think of it as the central
|
||||
nervous system of your web service:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">import</span><span class="w"> </span><span class="nn">responder</span>
|
||||
|
||||
<span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">()</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>That’s it. One import, one line. You now have a fully functional ASGI
|
||||
application with gzip compression, static file serving, session support,
|
||||
and a production-ready server — all wired up and ready to go.</p>
|
||||
</section>
|
||||
<section id="hello-world">
|
||||
<h2>Hello World<a class="headerlink" href="#hello-world" title="Link to this heading">¶</a></h2>
|
||||
<p>Next, add a route. Here, we’ll make the root URL say “hello, world!”:</p>
|
||||
<p>A web service isn’t very useful until it can respond to requests. In HTTP,
|
||||
a <em>route</em> maps a URL path to a function that handles it. When a client
|
||||
(like a browser or <code class="docutils literal notranslate"><span class="pre">curl</span></code>) sends a request to that path, your function
|
||||
runs and produces a response.</p>
|
||||
<p>Here’s the simplest possible route:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">hello_world</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s2">"hello, world!"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Every view receives a <code class="docutils literal notranslate"><span class="pre">req</span></code> (request) and <code class="docutils literal notranslate"><span class="pre">resp</span></code> (response) object. You
|
||||
don’t need to return anything — just mutate the response directly.</p>
|
||||
<p>Two things to notice:</p>
|
||||
<ol class="arabic simple">
|
||||
<li><p>Every view function receives two arguments: <code class="docutils literal notranslate"><span class="pre">req</span></code> (the incoming
|
||||
request) and <code class="docutils literal notranslate"><span class="pre">resp</span></code> (the outgoing response).</p></li>
|
||||
<li><p>You don’t return anything. Instead, you <em>mutate</em> the response object
|
||||
directly. This is a deliberate design choice — it keeps the API
|
||||
consistent whether you’re setting text, JSON, headers, cookies, or
|
||||
status codes.</p></li>
|
||||
</ol>
|
||||
</section>
|
||||
<section id="run-the-server">
|
||||
<h2>Run the Server<a class="headerlink" href="#run-the-server" title="Link to this heading">¶</a></h2>
|
||||
<p>Start your web service with <code class="docutils literal notranslate"><span class="pre">api.run()</span></code>:</p>
|
||||
<p>Start your web service with a single call:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This spins up a production-grade uvicorn server on port <code class="docutils literal notranslate"><span class="pre">5042</span></code>, ready for
|
||||
incoming HTTP requests.</p>
|
||||
<p>This spins up a production-grade <a class="reference external" href="https://www.uvicorn.org/">uvicorn</a>
|
||||
server on port <code class="docutils literal notranslate"><span class="pre">5042</span></code>, ready for incoming HTTP requests. Open
|
||||
<code class="docutils literal notranslate"><span class="pre">http://localhost:5042</span></code> in your browser and you’ll see your hello world
|
||||
response.</p>
|
||||
<p>You can customize the port with <code class="docutils literal notranslate"><span class="pre">api.run(port=8000)</span></code>. The <code class="docutils literal notranslate"><span class="pre">PORT</span></code>
|
||||
environment variable is also honored automatically — when set, Responder
|
||||
binds to <code class="docutils literal notranslate"><span class="pre">0.0.0.0</span></code> on that port, which is what cloud platforms like
|
||||
Fly.io, Railway, and Google Cloud Run expect.</p>
|
||||
binds to <code class="docutils literal notranslate"><span class="pre">0.0.0.0</span></code> on that port, which is what cloud platforms expect.</p>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>Both sync and async views are supported. The <code class="docutils literal notranslate"><span class="pre">async</span></code> keyword is always
|
||||
optional — use it when you need to <code class="docutils literal notranslate"><span class="pre">await</span></code> something.</p>
|
||||
optional — use it when you need to <code class="docutils literal notranslate"><span class="pre">await</span></code> something, like reading a
|
||||
request body or querying a database.</p>
|
||||
</div>
|
||||
</section>
|
||||
<section id="route-parameters">
|
||||
<h2>Route Parameters<a class="headerlink" href="#route-parameters" title="Link to this heading">¶</a></h2>
|
||||
<p>If you want dynamic URLs, use Python’s familiar f-string syntax to declare
|
||||
variables in your routes:</p>
|
||||
<p>Static URLs like <code class="docutils literal notranslate"><span class="pre">/about</span></code> are useful, but most applications need dynamic
|
||||
routes — URLs that contain variable data, like a user ID or a product slug.</p>
|
||||
<p>In Responder, you declare route parameters using Python’s f-string syntax:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/hello/</span><span class="si">{who}</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">hello_to</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">who</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"hello, </span><span class="si">{</span><span class="n">who</span><span class="si">}</span><span class="s2">!"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>A <code class="docutils literal notranslate"><span class="pre">GET</span></code> request to <code class="docutils literal notranslate"><span class="pre">/hello/world</span></code> will respond with <code class="docutils literal notranslate"><span class="pre">hello,</span> <span class="pre">world!</span></code>.</p>
|
||||
<p>Route parameters are passed as keyword-only arguments (after the <code class="docutils literal notranslate"><span class="pre">*</span></code>).</p>
|
||||
<p>A <code class="docutils literal notranslate"><span class="pre">GET</span></code> request to <code class="docutils literal notranslate"><span class="pre">/hello/world</span></code> will respond with <code class="docutils literal notranslate"><span class="pre">hello,</span> <span class="pre">world!</span></code>.
|
||||
A request to <code class="docutils literal notranslate"><span class="pre">/hello/guido</span></code> will respond with <code class="docutils literal notranslate"><span class="pre">hello,</span> <span class="pre">guido!</span></code>.</p>
|
||||
<p>Route parameters are passed as <em>keyword-only</em> arguments (after the <code class="docutils literal notranslate"><span class="pre">*</span></code>
|
||||
in the function signature). This is a Python feature that makes the
|
||||
interface explicit — you always know which arguments come from the URL.</p>
|
||||
<section id="type-convertors">
|
||||
<h3>Type Convertors<a class="headerlink" href="#type-convertors" title="Link to this heading">¶</a></h3>
|
||||
<p>You can constrain route parameters to specific types. The parameter will be
|
||||
automatically converted before it reaches your view:</p>
|
||||
<p>By default, route parameters are strings. But often you want them as
|
||||
integers, UUIDs, or other types. Responder can convert them automatically
|
||||
using type annotations in the route pattern:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/add/{a:int}/{b:int}"</span><span class="p">)</span>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">add</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">a</span><span class="si">}</span><span class="s2"> + </span><span class="si">{</span><span class="n">b</span><span class="si">}</span><span class="s2"> = </span><span class="si">{</span><span class="n">a</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">b</span><span class="si">}</span><span class="s2">"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Here, <code class="docutils literal notranslate"><span class="pre">a</span></code> and <code class="docutils literal notranslate"><span class="pre">b</span></code> will arrive as Python <code class="docutils literal notranslate"><span class="pre">int</span></code> objects, not strings.
|
||||
If someone requests <code class="docutils literal notranslate"><span class="pre">/add/3/hello</span></code>, they’ll get a 404 — the route won’t
|
||||
match because <code class="docutils literal notranslate"><span class="pre">hello</span></code> isn’t a valid integer.</p>
|
||||
<p>Supported types:</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">str</span></code> — matches any string without slashes (default)</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">int</span></code> — matches digits, converts to <code class="docutils literal notranslate"><span class="pre">int</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">float</span></code> — matches decimal numbers, converts to <code class="docutils literal notranslate"><span class="pre">float</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">str</span></code> — matches any string without slashes (this is the default)</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">int</span></code> — matches digits and converts to <code class="docutils literal notranslate"><span class="pre">int</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">float</span></code> — matches decimal numbers and converts to <code class="docutils literal notranslate"><span class="pre">float</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">uuid</span></code> — matches UUID strings like <code class="docutils literal notranslate"><span class="pre">550e8400-e29b-41d4-a716-446655440000</span></code></p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">path</span></code> — matches any string <em>including</em> slashes, useful for file paths</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">path</span></code> — matches any string <em>including</em> slashes, useful for file paths
|
||||
like <code class="docutils literal notranslate"><span class="pre">/files/{filepath:path}</span></code></p></li>
|
||||
</ul>
|
||||
</section>
|
||||
</section>
|
||||
<section id="sending-responses">
|
||||
<h2>Sending Responses<a class="headerlink" href="#sending-responses" title="Link to this heading">¶</a></h2>
|
||||
<p>Responder gives you several ways to send data back to the client. Just set
|
||||
the appropriate property on the response object.</p>
|
||||
<p><strong>Text and HTML</strong>:</p>
|
||||
<p>When an HTTP server receives a request, it must send back a response. Every
|
||||
HTTP response has three parts: a status code (like <code class="docutils literal notranslate"><span class="pre">200</span> <span class="pre">OK</span></code> or <code class="docutils literal notranslate"><span class="pre">404</span> <span class="pre">Not</span>
|
||||
<span class="pre">Found</span></code>), headers (metadata like <code class="docutils literal notranslate"><span class="pre">Content-Type</span></code>), and a body (the actual
|
||||
data).</p>
|
||||
<p>Responder lets you set all three by mutating the response object.</p>
|
||||
<p><strong>Text and HTML</strong> — the simplest response types. <code class="docutils literal notranslate"><span class="pre">resp.text</span></code> sets the
|
||||
<code class="docutils literal notranslate"><span class="pre">Content-Type</span></code> to <code class="docutils literal notranslate"><span class="pre">text/plain</span></code>, while <code class="docutils literal notranslate"><span class="pre">resp.html</span></code> sets it to
|
||||
<code class="docutils literal notranslate"><span class="pre">text/html</span></code>:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">resp</span><span class="o">.</span><span class="n">text</span> <span class="o">=</span> <span class="s2">"plain text response"</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">html</span> <span class="o">=</span> <span class="s2">"<h1>HTML response</h1>"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>JSON</strong> — the most common pattern for APIs. Set <code class="docutils literal notranslate"><span class="pre">resp.media</span></code> to any
|
||||
JSON-serializable Python object:</p>
|
||||
<p><strong>JSON</strong> — the lingua franca of web APIs. Set <code class="docutils literal notranslate"><span class="pre">resp.media</span></code> to any
|
||||
JSON-serializable Python object — a dict, a list, whatever — and Responder
|
||||
will serialize it to JSON and set the right headers:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/hello/</span><span class="si">{who}</span><span class="s2">/json"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">hello_json</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">who</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"hello"</span><span class="p">:</span> <span class="n">who</span><span class="p">}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>If the client sends an <code class="docutils literal notranslate"><span class="pre">Accept:</span> <span class="pre">application/x-yaml</span></code> header, the same data
|
||||
will be returned as YAML instead. Content negotiation is automatic.</p>
|
||||
<p><strong>Files</strong> — serve a file from disk with automatic content-type detection:</p>
|
||||
will be returned as YAML instead. This is called <em>content negotiation</em> —
|
||||
the server and client agree on a format. It happens automatically.</p>
|
||||
<p><strong>Files</strong> — serve a file from disk. Responder uses Python’s <code class="docutils literal notranslate"><span class="pre">mimetypes</span></code>
|
||||
module to figure out the <code class="docutils literal notranslate"><span class="pre">Content-Type</span></code> from the file extension:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">resp</span><span class="o">.</span><span class="n">file</span><span class="p">(</span><span class="s2">"reports/annual.pdf"</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Raw bytes</strong>:</p>
|
||||
<p><strong>Raw bytes</strong> — for binary data like images or protocol buffers:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">resp</span><span class="o">.</span><span class="n">content</span> <span class="o">=</span> <span class="sa">b</span><span class="s2">"</span><span class="se">\x89</span><span class="s2">PNG</span><span class="se">\r\n</span><span class="s2">..."</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Status codes and headers</strong>:</p>
|
||||
<p><strong>Status codes</strong> — HTTP status codes tell the client what happened. <code class="docutils literal notranslate"><span class="pre">200</span></code>
|
||||
means success, <code class="docutils literal notranslate"><span class="pre">201</span></code> means something was created, <code class="docutils literal notranslate"><span class="pre">404</span></code> means not found,
|
||||
<code class="docutils literal notranslate"><span class="pre">500</span></code> means the server broke. Set it directly:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">201</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"X-Custom"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"value"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Redirects</strong>:</p>
|
||||
<p><strong>Headers</strong> — HTTP headers carry metadata. Common ones include
|
||||
<code class="docutils literal notranslate"><span class="pre">Content-Type</span></code>, <code class="docutils literal notranslate"><span class="pre">Cache-Control</span></code>, <code class="docutils literal notranslate"><span class="pre">Authorization</span></code>, and custom
|
||||
application headers:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"X-Custom"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"value"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Redirects</strong> — tell the client to go somewhere else:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span><span class="o">.</span><span class="n">redirect</span><span class="p">(</span><span class="n">resp</span><span class="p">,</span> <span class="n">location</span><span class="o">=</span><span class="s2">"/new-url"</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This sends a <code class="docutils literal notranslate"><span class="pre">301</span> <span class="pre">Moved</span> <span class="pre">Permanently</span></code> response by default. The client’s
|
||||
browser will automatically follow the redirect.</p>
|
||||
</section>
|
||||
<section id="reading-requests">
|
||||
<h2>Reading Requests<a class="headerlink" href="#reading-requests" title="Link to this heading">¶</a></h2>
|
||||
<p>The request object gives you access to everything the client sent.</p>
|
||||
<p><strong>Method and URL</strong>:</p>
|
||||
<p>The other half of HTTP is the request — the data the client sends to your
|
||||
server. This includes the HTTP method (GET, POST, PUT, DELETE), the URL,
|
||||
headers, query parameters, cookies, and optionally a body.</p>
|
||||
<p>Responder wraps all of this in the <code class="docutils literal notranslate"><span class="pre">req</span></code> object.</p>
|
||||
<p><strong>Method and URL</strong> — every HTTP request has a method (what the client wants
|
||||
to do) and a URL (what resource it’s about):</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">req</span><span class="o">.</span><span class="n">method</span> <span class="c1"># "get", "post", etc. (lowercase)</span>
|
||||
<span class="n">req</span><span class="o">.</span><span class="n">full_url</span> <span class="c1"># "http://example.com/path?q=1"</span>
|
||||
<span class="n">req</span><span class="o">.</span><span class="n">url</span> <span class="c1"># parsed URL object</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Headers</strong> — case-insensitive, just like you’d expect:</p>
|
||||
<p><strong>Headers</strong> — HTTP headers carry metadata from the client, like what
|
||||
content types it accepts, authentication tokens, and more. Responder’s
|
||||
headers dict is case-insensitive, because the HTTP spec says header names
|
||||
are case-insensitive:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">req</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"Content-Type"</span><span class="p">]</span>
|
||||
<span class="n">req</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"content-type"</span><span class="p">]</span> <span class="c1"># same thing</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Query parameters</strong>:</p>
|
||||
<p><strong>Query parameters</strong> — the part of the URL after the <code class="docutils literal notranslate"><span class="pre">?</span></code>. These are
|
||||
commonly used for search, filtering, and pagination:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># GET /search?q=python&page=2</span>
|
||||
<span class="n">req</span><span class="o">.</span><span class="n">params</span><span class="p">[</span><span class="s2">"q"</span><span class="p">]</span> <span class="c1"># "python"</span>
|
||||
<span class="n">req</span><span class="o">.</span><span class="n">params</span><span class="p">[</span><span class="s2">"page"</span><span class="p">]</span> <span class="c1"># "2"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Path parameters</strong> — also available on the request object:</p>
|
||||
<p>Note that query parameters are always strings. If you need an integer,
|
||||
you’ll need to convert it yourself: <code class="docutils literal notranslate"><span class="pre">int(req.params["page"])</span></code>.</p>
|
||||
<p><strong>Path parameters</strong> — the dynamic parts of the URL that matched your route
|
||||
pattern. These are also available on the request object, which is useful
|
||||
in before-request hooks where they aren’t passed as function arguments:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">req</span><span class="o">.</span><span class="n">path_params</span><span class="p">[</span><span class="s2">"user_id"</span><span class="p">]</span> <span class="c1"># same as the keyword argument</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Request body</strong> — for POST/PUT/PATCH requests, you need to <code class="docutils literal notranslate"><span class="pre">await</span></code> the
|
||||
body content:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># JSON body</span>
|
||||
<p><strong>Request body</strong> — for POST, PUT, and PATCH requests, the client sends
|
||||
data in the body. Since reading the body is an I/O operation, you need to
|
||||
<code class="docutils literal notranslate"><span class="pre">await</span></code> it:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># JSON body (the most common format for APIs)</span>
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">media</span><span class="p">()</span>
|
||||
|
||||
<span class="c1"># Form data</span>
|
||||
<span class="c1"># Form data (from HTML forms)</span>
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">media</span><span class="p">(</span><span class="s2">"form"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># File uploads</span>
|
||||
<span class="c1"># File uploads (multipart)</span>
|
||||
<span class="n">files</span> <span class="o">=</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">media</span><span class="p">(</span><span class="s2">"files"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Raw bytes</span>
|
||||
@@ -193,40 +251,56 @@ body content:</p>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Other useful properties</strong>:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">req</span><span class="o">.</span><span class="n">is_json</span> <span class="c1"># True if content type is JSON</span>
|
||||
<span class="n">req</span><span class="o">.</span><span class="n">cookies</span> <span class="c1"># dict of cookies</span>
|
||||
<span class="n">req</span><span class="o">.</span><span class="n">session</span> <span class="c1"># session data (dict)</span>
|
||||
<span class="n">req</span><span class="o">.</span><span class="n">client</span> <span class="c1"># (host, port) tuple</span>
|
||||
<span class="n">req</span><span class="o">.</span><span class="n">is_secure</span> <span class="c1"># True if HTTPS</span>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">req</span><span class="o">.</span><span class="n">is_json</span> <span class="c1"># True if the content type is JSON</span>
|
||||
<span class="n">req</span><span class="o">.</span><span class="n">cookies</span> <span class="c1"># dict of cookies sent by the client</span>
|
||||
<span class="n">req</span><span class="o">.</span><span class="n">session</span> <span class="c1"># session data (a signed, server-side dict)</span>
|
||||
<span class="n">req</span><span class="o">.</span><span class="n">client</span> <span class="c1"># (host, port) tuple — the client's IP address</span>
|
||||
<span class="n">req</span><span class="o">.</span><span class="n">is_secure</span> <span class="c1"># True if the request came over HTTPS</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="rendering-templates">
|
||||
<h2>Rendering Templates<a class="headerlink" href="#rendering-templates" title="Link to this heading">¶</a></h2>
|
||||
<p>Responder includes built-in <a class="reference external" href="https://jinja.palletsprojects.com/">Jinja2</a>
|
||||
support. Templates are loaded from the <code class="docutils literal notranslate"><span class="pre">templates/</span></code> directory by default.</p>
|
||||
<p>The simplest way is to use <code class="docutils literal notranslate"><span class="pre">api.template()</span></code>:</p>
|
||||
<p>While APIs typically return JSON, many web applications need to render
|
||||
HTML pages. Responder includes built-in support for
|
||||
<a class="reference external" href="https://jinja.palletsprojects.com/">Jinja2</a>, one of the most popular
|
||||
templating engines in the Python ecosystem.</p>
|
||||
<p>Templates let you write HTML with placeholders that get filled in with
|
||||
dynamic data. This keeps your presentation logic (HTML) separate from
|
||||
your application logic (Python) — a pattern called
|
||||
<em>separation of concerns</em>.</p>
|
||||
<p>The simplest way to render a template is <code class="docutils literal notranslate"><span class="pre">api.template()</span></code>. Templates
|
||||
are loaded from the <code class="docutils literal notranslate"><span class="pre">templates/</span></code> directory by default:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/hello/</span><span class="si">{name}</span><span class="s2">/html"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">hello_html</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">html</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">template</span><span class="p">(</span><span class="s2">"hello.html"</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="n">name</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You can also use the <code class="docutils literal notranslate"><span class="pre">Templates</span></code> class directly for more control:</p>
|
||||
<p>The template file <code class="docutils literal notranslate"><span class="pre">templates/hello.html</span></code> might look like:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><h1>Hello, {{ name }}!</h1>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">{{</span> <span class="pre">name</span> <span class="pre">}}</span></code> part is a Jinja2 expression — it gets replaced with
|
||||
the value you passed in.</p>
|
||||
<p>You can also use the <code class="docutils literal notranslate"><span class="pre">Templates</span></code> class directly for more control over
|
||||
the template directory and configuration:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.templates</span><span class="w"> </span><span class="kn">import</span> <span class="n">Templates</span>
|
||||
|
||||
<span class="n">templates</span> <span class="o">=</span> <span class="n">Templates</span><span class="p">(</span><span class="n">directory</span><span class="o">=</span><span class="s2">"templates"</span><span class="p">)</span>
|
||||
<span class="n">templates</span> <span class="o">=</span> <span class="n">Templates</span><span class="p">(</span><span class="n">directory</span><span class="o">=</span><span class="s2">"my_templates"</span><span class="p">)</span>
|
||||
|
||||
<span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/page"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">page</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">html</span> <span class="o">=</span> <span class="n">templates</span><span class="o">.</span><span class="n">render</span><span class="p">(</span><span class="s2">"page.html"</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s2">"Hello"</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Async rendering is supported too:</p>
|
||||
<p>For applications that need non-blocking template rendering (rare, but
|
||||
useful under extreme load), async rendering is supported:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">templates</span> <span class="o">=</span> <span class="n">Templates</span><span class="p">(</span><span class="n">directory</span><span class="o">=</span><span class="s2">"templates"</span><span class="p">,</span> <span class="n">enable_async</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">html</span> <span class="o">=</span> <span class="k">await</span> <span class="n">templates</span><span class="o">.</span><span class="n">render_async</span><span class="p">(</span><span class="s2">"page.html"</span><span class="p">,</span> <span class="n">title</span><span class="o">=</span><span class="s2">"Hello"</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You can render template strings without a file:</p>
|
||||
<p>And for quick one-off templates, you can render a string directly without
|
||||
a file:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">resp</span><span class="o">.</span><span class="n">html</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">template_string</span><span class="p">(</span><span class="s2">"Hello, {{ name }}!"</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s2">"world"</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
@@ -234,7 +308,12 @@ support. Templates are loaded from the <code class="docutils literal notranslate
|
||||
<section id="background-tasks">
|
||||
<h2>Background Tasks<a class="headerlink" href="#background-tasks" title="Link to this heading">¶</a></h2>
|
||||
<p>Sometimes you want to accept a request, respond immediately, and do the
|
||||
actual processing later. Responder makes this easy with background tasks:</p>
|
||||
actual processing later. This is a common pattern for operations that take
|
||||
a long time — sending emails, processing images, updating caches, or
|
||||
calling slow external APIs.</p>
|
||||
<p>Responder makes this easy with background tasks. Decorate any function
|
||||
with <code class="docutils literal notranslate"><span class="pre">@api.background.task</span></code> and it will run in a thread pool, separate
|
||||
from the request/response cycle:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/incoming"</span><span class="p">)</span>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">receive_incoming</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">media</span><span class="p">()</span>
|
||||
@@ -247,12 +326,21 @@ actual processing later. Responder makes this easy with background tasks:</p>
|
||||
|
||||
<span class="n">process_data</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Respond immediately — processing continues in the background</span>
|
||||
<span class="c1"># This response is sent immediately, while process_data</span>
|
||||
<span class="c1"># continues running in the background.</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"status"</span><span class="p">:</span> <span class="s2">"accepted"</span><span class="p">}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">@api.background.task</span></code> decorator wraps any function to run in a thread
|
||||
pool. The client gets an immediate response while the work continues.</p>
|
||||
<p>The client gets an instant response — the heavy lifting happens after.
|
||||
This is the same pattern used by task queues like Celery, but much simpler
|
||||
for lightweight use cases where you don’t need a full message broker.</p>
|
||||
<div class="admonition note">
|
||||
<p class="admonition-title">Note</p>
|
||||
<p>Background tasks run in threads, not processes. They share memory with
|
||||
your application, which makes them fast to start but means CPU-intensive
|
||||
work will block the event loop. For heavy computation, consider a proper
|
||||
task queue.</p>
|
||||
</div>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
|
||||
+1
-1
File diff suppressed because one or more lines are too long
+1
-1
@@ -303,7 +303,7 @@ network overhead. Avoid <code class="docutils literal notranslate"><span class="
|
||||
<li><p><strong>One API per test</strong> when testing configuration. If you need a specific
|
||||
<code class="docutils literal notranslate"><span class="pre">API()</span></code> configuration (like <code class="docutils literal notranslate"><span class="pre">cors=True</span></code>), create a new instance
|
||||
in the test rather than sharing the fixture.</p></li>
|
||||
<li><p><strong>Use ``api.url_for()``</strong> instead of hard-coded paths. It’s a small
|
||||
<li><p>Use <code class="docutils literal notranslate"><span class="pre">api.url_for()</span></code> instead of hard-coded paths. It’s a small
|
||||
thing, but it makes refactoring painless.</p></li>
|
||||
<li><p><strong>Test the contract, not the implementation.</strong> Assert on status codes,
|
||||
response bodies, and headers — not on internal state.</p></li>
|
||||
|
||||
@@ -42,12 +42,24 @@
|
||||
|
||||
<section id="feature-tour">
|
||||
<h1>Feature Tour<a class="headerlink" href="#feature-tour" title="Link to this heading">¶</a></h1>
|
||||
<p>This section walks through Responder’s features in detail. Each section
|
||||
includes working code examples you can copy into your application.</p>
|
||||
<p>This section walks through Responder’s features in depth. Each section
|
||||
explains the concept, shows working code, and explains the design choices
|
||||
behind it. If you’re new to web development, this is a good place to learn
|
||||
how modern web frameworks work under the hood.</p>
|
||||
<section id="method-filtering">
|
||||
<h2>Method Filtering<a class="headerlink" href="#method-filtering" title="Link to this heading">¶</a></h2>
|
||||
<p>By default, a route matches all HTTP methods. If you want to restrict a
|
||||
route to specific methods, pass the <code class="docutils literal notranslate"><span class="pre">methods</span></code> parameter:</p>
|
||||
<p>HTTP defines several <em>methods</em> (also called verbs) that describe what a
|
||||
client wants to do with a resource. The most common are:</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">GET</span></code> — retrieve data</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">POST</span></code> — create something new</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">PUT</span></code> — replace something entirely</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">PATCH</span></code> — update part of something</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">DELETE</span></code> — remove something</p></li>
|
||||
</ul>
|
||||
<p>By default, a Responder route matches all methods. This is fine for simple
|
||||
endpoints, but REST APIs typically map different methods to different
|
||||
operations. Use the <code class="docutils literal notranslate"><span class="pre">methods</span></code> parameter to restrict a route:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/items"</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s2">"GET"</span><span class="p">])</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">list_items</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"items"</span><span class="p">:</span> <span class="p">[]}</span>
|
||||
@@ -58,13 +70,19 @@ route to specific methods, pass the <code class="docutils literal notranslate"><
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"created"</span><span class="p">:</span> <span class="n">data</span><span class="p">}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Note the <code class="docutils literal notranslate"><span class="pre">check_existing=False</span></code> — this allows you to register multiple
|
||||
handlers for the same path with different methods.</p>
|
||||
<p>Note the <code class="docutils literal notranslate"><span class="pre">check_existing=False</span></code> — Responder normally prevents you from
|
||||
registering two routes with the same path (to catch typos). When you
|
||||
intentionally want multiple handlers for the same path with different
|
||||
methods, you need to opt in.</p>
|
||||
</section>
|
||||
<section id="class-based-views">
|
||||
<h2>Class-Based Views<a class="headerlink" href="#class-based-views" title="Link to this heading">¶</a></h2>
|
||||
<p>For more complex resources, you can use class-based views. Responder will
|
||||
dispatch to the appropriate method handler based on the HTTP method:</p>
|
||||
<p>Function-based views are great for simple endpoints, but sometimes you want
|
||||
to group related HTTP methods together into a single resource. This is
|
||||
where class-based views come in — a pattern popularized by
|
||||
<a class="reference external" href="https://falconframework.org/">Falcon</a>.</p>
|
||||
<p>Responder dispatches to the appropriate method handler based on the HTTP
|
||||
method:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/</span><span class="si">{greeting}</span><span class="s2">"</span><span class="p">)</span>
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">GreetingResource</span><span class="p">:</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">on_get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">greeting</span><span class="p">):</span>
|
||||
@@ -81,13 +99,17 @@ dispatch to the appropriate method handler based on the HTTP method:</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">on_request</span></code> method is called for all HTTP methods, much like
|
||||
middleware scoped to a single route. Method-specific handlers (<code class="docutils literal notranslate"><span class="pre">on_get</span></code>,
|
||||
<code class="docutils literal notranslate"><span class="pre">on_post</span></code>, <code class="docutils literal notranslate"><span class="pre">on_put</span></code>, <code class="docutils literal notranslate"><span class="pre">on_delete</span></code>, etc.) are called after.</p>
|
||||
<p>No inheritance required — just define a class with the right method names.</p>
|
||||
<p>No inheritance required — just define a class with the right method names.
|
||||
This is simpler than Django’s <code class="docutils literal notranslate"><span class="pre">View</span></code> classes and more Pythonic than
|
||||
framework-specific base classes.</p>
|
||||
</section>
|
||||
<section id="lifespan-events">
|
||||
<h2>Lifespan Events<a class="headerlink" href="#lifespan-events" title="Link to this heading">¶</a></h2>
|
||||
<p>Modern applications often need to set up resources on startup (database
|
||||
connections, caches, ML models) and tear them down on shutdown. Responder
|
||||
supports the lifespan context manager pattern:</p>
|
||||
<p>Real applications need to set up resources when they start (database
|
||||
connection pools, ML models, caches) and tear them down when they stop.
|
||||
This is called the application <em>lifespan</em>.</p>
|
||||
<p>The modern approach is the <em>context manager</em> pattern, where startup and
|
||||
shutdown are two halves of the same block:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">contextlib</span><span class="w"> </span><span class="kn">import</span> <span class="n">asynccontextmanager</span>
|
||||
|
||||
<span class="nd">@asynccontextmanager</span>
|
||||
@@ -101,7 +123,10 @@ supports the lifespan context manager pattern:</p>
|
||||
<span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">lifespan</span><span class="o">=</span><span class="n">lifespan</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You can also use the traditional event decorator style:</p>
|
||||
<p>Everything before <code class="docutils literal notranslate"><span class="pre">yield</span></code> runs at startup. Everything after runs at
|
||||
shutdown. If startup fails, the server won’t start. If shutdown raises,
|
||||
it’s logged but the server still exits.</p>
|
||||
<p>The traditional event decorator style also works:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">on_event</span><span class="p">(</span><span class="s2">"startup"</span><span class="p">)</span>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">startup</span><span class="p">():</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"starting up"</span><span class="p">)</span>
|
||||
@@ -111,52 +136,65 @@ supports the lifespan context manager pattern:</p>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="s2">"shutting down"</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>The context manager approach is preferred for new code — it makes the
|
||||
startup/shutdown relationship explicit and keeps related code together.</p>
|
||||
<p>The context manager is preferred for new code — it keeps related startup
|
||||
and shutdown logic together and makes resource cleanup more explicit.</p>
|
||||
</section>
|
||||
<section id="serving-files">
|
||||
<h2>Serving Files<a class="headerlink" href="#serving-files" title="Link to this heading">¶</a></h2>
|
||||
<p>Serve files from disk with automatic content-type detection. Responder
|
||||
uses Python’s <code class="docutils literal notranslate"><span class="pre">mimetypes</span></code> module to figure out the right <code class="docutils literal notranslate"><span class="pre">Content-Type</span></code>
|
||||
header for you:</p>
|
||||
<p>Web applications often need to serve files — downloads, reports, images.
|
||||
Responder makes this simple with <code class="docutils literal notranslate"><span class="pre">resp.file()</span></code>, which reads a file from
|
||||
disk and sets the <code class="docutils literal notranslate"><span class="pre">Content-Type</span></code> header automatically using Python’s
|
||||
<code class="docutils literal notranslate"><span class="pre">mimetypes</span></code> module:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/download"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">download</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">file</span><span class="p">(</span><span class="s2">"reports/annual.pdf"</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You can override the content type if needed:</p>
|
||||
<p>You can override the content type if the automatic detection isn’t right:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/image"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">image</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">file</span><span class="p">(</span><span class="s2">"photos/cat.jpg"</span><span class="p">,</span> <span class="n">content_type</span><span class="o">=</span><span class="s2">"image/jpeg"</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>For large files, use <code class="docutils literal notranslate"><span class="pre">resp.stream_file()</span></code> to avoid loading the entire
|
||||
file into memory. This streams the file in chunks:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/export"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">export</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">stream_file</span><span class="p">(</span><span class="s2">"data/export.csv"</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="custom-error-handling">
|
||||
<h2>Custom Error Handling<a class="headerlink" href="#custom-error-handling" title="Link to this heading">¶</a></h2>
|
||||
<p>By default, unhandled exceptions result in a 500 Internal Server Error.
|
||||
You can register custom handlers for specific exception types to return
|
||||
structured error responses:</p>
|
||||
<p>In production, you don’t want your users to see raw Python tracebacks.
|
||||
Responder lets you register custom handlers for specific exception types,
|
||||
so you can return clean, structured error responses:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">exception_handler</span><span class="p">(</span><span class="ne">ValueError</span><span class="p">)</span>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">handle_value_error</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="n">exc</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">status_code</span> <span class="o">=</span> <span class="mi">400</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"error"</span><span class="p">:</span> <span class="nb">str</span><span class="p">(</span><span class="n">exc</span><span class="p">)}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Now, any route that raises a <code class="docutils literal notranslate"><span class="pre">ValueError</span></code> will return a clean 400 response
|
||||
with a JSON error message instead of a generic 500 page.</p>
|
||||
<p>Now, any route that raises a <code class="docutils literal notranslate"><span class="pre">ValueError</span></code> will return a clean JSON
|
||||
response with a 400 status code instead of a generic 500 error page.</p>
|
||||
<p>This is a common pattern in API development — you define your own exception
|
||||
classes for different error conditions, register handlers for each, and
|
||||
your API always returns consistent, machine-readable error responses.</p>
|
||||
</section>
|
||||
<section id="before-request-hooks">
|
||||
<h2>Before-Request Hooks<a class="headerlink" href="#before-request-hooks" title="Link to this heading">¶</a></h2>
|
||||
<p>Run code before every request. This is useful for logging, adding common
|
||||
headers, or setting up per-request state:</p>
|
||||
<p>Sometimes you need to run the same code before every request —
|
||||
authentication checks, request logging, adding common headers, or setting
|
||||
up per-request state. Before-request hooks let you do this without
|
||||
duplicating code in every route:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="n">before_request</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">add_headers</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"X-API-Version"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"3.1"</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"X-API-Version"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"3.2"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Short-circuiting:</strong> If your hook sets <code class="docutils literal notranslate"><span class="pre">resp.status_code</span></code>, the route
|
||||
handler will be skipped entirely and the response will be sent immediately.
|
||||
This is the pattern for authentication guards:</p>
|
||||
<p><strong>Short-circuiting</strong> is the really powerful part. If your hook sets
|
||||
<code class="docutils literal notranslate"><span class="pre">resp.status_code</span></code>, the route handler is skipped entirely and the
|
||||
response is sent immediately. This is the pattern for authentication:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="n">before_request</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">auth_check</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="k">if</span> <span class="s2">"Authorization"</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">req</span><span class="o">.</span><span class="n">headers</span><span class="p">:</span>
|
||||
@@ -165,17 +203,32 @@ This is the pattern for authentication guards:</p>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>If the <code class="docutils literal notranslate"><span class="pre">Authorization</span></code> header is missing, the client gets a 401 response
|
||||
and the actual route handler never runs.</p>
|
||||
<p>WebSocket hooks work the same way:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">before_request</span><span class="p">(</span><span class="n">websocket</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">ws_auth</span><span class="p">(</span><span class="n">ws</span><span class="p">):</span>
|
||||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
|
||||
and the actual route handler never runs. This is cleaner than adding
|
||||
auth checks to every individual route.</p>
|
||||
</section>
|
||||
<section id="after-request-hooks">
|
||||
<h2>After-Request Hooks<a class="headerlink" href="#after-request-hooks" title="Link to this heading">¶</a></h2>
|
||||
<p>The complement to before-request hooks. After-request hooks run after the
|
||||
route handler completes but before the response is sent. They’re useful
|
||||
for logging, adding response headers, or any post-processing:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">after_request</span><span class="p">()</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">log_response</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">req</span><span class="o">.</span><span class="n">method</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">req</span><span class="o">.</span><span class="n">full_url</span><span class="si">}</span><span class="s2"> -> </span><span class="si">{</span><span class="n">resp</span><span class="o">.</span><span class="n">status_code</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
|
||||
<span class="nd">@api</span><span class="o">.</span><span class="n">after_request</span><span class="p">()</span>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">add_timing</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">headers</span><span class="p">[</span><span class="s2">"X-Served-By"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"responder"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="websocket-support">
|
||||
<h2>WebSocket Support<a class="headerlink" href="#websocket-support" title="Link to this heading">¶</a></h2>
|
||||
<p>Responder supports WebSockets for real-time, bidirectional communication:</p>
|
||||
<p>HTTP is a request-response protocol — the client asks, the server answers.
|
||||
But some applications need real-time, bidirectional communication: chat
|
||||
apps, live dashboards, multiplayer games, collaborative editors.</p>
|
||||
<p><a class="reference external" href="https://en.wikipedia.org/wiki/WebSocket">WebSockets</a> solve this by
|
||||
upgrading an HTTP connection into a persistent, full-duplex channel where
|
||||
both sides can send messages at any time:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/ws"</span><span class="p">,</span> <span class="n">websocket</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">websocket</span><span class="p">(</span><span class="n">ws</span><span class="p">):</span>
|
||||
<span class="k">await</span> <span class="n">ws</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
|
||||
@@ -187,13 +240,49 @@ and the actual route handler never runs.</p>
|
||||
</div>
|
||||
<p>You can send and receive in multiple formats:</p>
|
||||
<ul class="simple">
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">send_text</span></code> / <code class="docutils literal notranslate"><span class="pre">receive_text</span></code> — plain text</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">send_json</span></code> / <code class="docutils literal notranslate"><span class="pre">receive_json</span></code> — JSON objects</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">send_text</span></code> / <code class="docutils literal notranslate"><span class="pre">receive_text</span></code> — plain text strings</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">send_json</span></code> / <code class="docutils literal notranslate"><span class="pre">receive_json</span></code> — JSON objects (auto-serialized)</p></li>
|
||||
<li><p><code class="docutils literal notranslate"><span class="pre">send_bytes</span></code> / <code class="docutils literal notranslate"><span class="pre">receive_bytes</span></code> — raw binary data</p></li>
|
||||
</ul>
|
||||
<p>WebSocket routes are marked with <code class="docutils literal notranslate"><span class="pre">websocket=True</span></code> in the route decorator.
|
||||
They receive a <code class="docutils literal notranslate"><span class="pre">ws</span></code> object instead of <code class="docutils literal notranslate"><span class="pre">req</span></code> and <code class="docutils literal notranslate"><span class="pre">resp</span></code>.</p>
|
||||
</section>
|
||||
<section id="server-sent-events-sse">
|
||||
<h2>Server-Sent Events (SSE)<a class="headerlink" href="#server-sent-events-sse" title="Link to this heading">¶</a></h2>
|
||||
<p>SSE is a simpler alternative to WebSockets for <em>one-way</em> real-time
|
||||
communication — the server pushes events to the client, but the client
|
||||
can’t send messages back. This is perfect for live feeds, progress bars,
|
||||
notification streams, and AI response streaming.</p>
|
||||
<p>Unlike WebSockets, SSE works over plain HTTP, is automatically reconnected
|
||||
by the browser, and doesn’t require any special client-side libraries:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/events"</span><span class="p">)</span>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">events</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="nd">@resp</span><span class="o">.</span><span class="n">sse</span>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">stream</span><span class="p">():</span>
|
||||
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
|
||||
<span class="k">yield</span> <span class="p">{</span><span class="s2">"data"</span><span class="p">:</span> <span class="sa">f</span><span class="s2">"message </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">"</span><span class="p">}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>On the client side, you consume SSE events with JavaScript’s built-in
|
||||
<code class="docutils literal notranslate"><span class="pre">EventSource</span></code> API:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">const</span> <span class="n">source</span> <span class="o">=</span> <span class="n">new</span> <span class="n">EventSource</span><span class="p">(</span><span class="s2">"/events"</span><span class="p">);</span>
|
||||
<span class="n">source</span><span class="o">.</span><span class="n">onmessage</span> <span class="o">=</span> <span class="p">(</span><span class="n">event</span><span class="p">)</span> <span class="o">=></span> <span class="p">{</span>
|
||||
<span class="n">console</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="n">event</span><span class="o">.</span><span class="n">data</span><span class="p">);</span>
|
||||
<span class="p">};</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Each yielded value can be a string (treated as data) or a dict with the
|
||||
standard SSE fields:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">yield</span> <span class="p">{</span><span class="s2">"event"</span><span class="p">:</span> <span class="s2">"update"</span><span class="p">,</span> <span class="s2">"data"</span><span class="p">:</span> <span class="s2">"hello"</span><span class="p">,</span> <span class="s2">"id"</span><span class="p">:</span> <span class="s2">"1"</span><span class="p">,</span> <span class="s2">"retry"</span><span class="p">:</span> <span class="s2">"5000"</span><span class="p">}</span>
|
||||
<span class="k">yield</span> <span class="s2">"simple string message"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="graphql">
|
||||
<h2>GraphQL<a class="headerlink" href="#graphql" title="Link to this heading">¶</a></h2>
|
||||
<p><a class="reference external" href="https://graphql.org/">GraphQL</a> is a query language for APIs that lets
|
||||
clients request exactly the data they need — no more, no less. Instead of
|
||||
multiple REST endpoints, you define a schema and let clients query it.</p>
|
||||
<p>Responder includes built-in GraphQL support via
|
||||
<a class="reference external" href="https://graphene-python.org/">Graphene</a>. Set up a full GraphQL endpoint
|
||||
with a single method call:</p>
|
||||
@@ -208,16 +297,20 @@ with a single method call:</p>
|
||||
<span class="n">api</span><span class="o">.</span><span class="n">graphql</span><span class="p">(</span><span class="s2">"/graphql"</span><span class="p">,</span> <span class="n">schema</span><span class="o">=</span><span class="n">graphene</span><span class="o">.</span><span class="n">Schema</span><span class="p">(</span><span class="n">query</span><span class="o">=</span><span class="n">Query</span><span class="p">))</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Visiting <code class="docutils literal notranslate"><span class="pre">/graphql</span></code> in a browser renders the GraphiQL interactive IDE,
|
||||
where you can explore your schema and test queries. Programmatic clients
|
||||
can POST JSON queries to the same endpoint.</p>
|
||||
<p>Visiting <code class="docutils literal notranslate"><span class="pre">/graphql</span></code> in a browser renders the
|
||||
<a class="reference external" href="https://github.com/graphql/graphiql">GraphiQL</a> interactive IDE, where
|
||||
you can explore your schema, write queries, and see results in real-time.
|
||||
Programmatic clients can POST JSON queries to the same endpoint.</p>
|
||||
<p>You can access the Responder request and response objects in your resolvers
|
||||
through <code class="docutils literal notranslate"><span class="pre">info.context["request"]</span></code> and <code class="docutils literal notranslate"><span class="pre">info.context["response"]</span></code>.</p>
|
||||
</section>
|
||||
<section id="openapi-documentation">
|
||||
<h2>OpenAPI Documentation<a class="headerlink" href="#openapi-documentation" title="Link to this heading">¶</a></h2>
|
||||
<p>Responder can generate an OpenAPI schema and serve interactive API
|
||||
documentation automatically:</p>
|
||||
<p><a class="reference external" href="https://www.openapis.org/">OpenAPI</a> (formerly Swagger) is the industry
|
||||
standard for describing REST APIs. An OpenAPI specification lets you
|
||||
auto-generate interactive documentation, client libraries, and validation
|
||||
logic.</p>
|
||||
<p>Responder generates OpenAPI specs from your code:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span>
|
||||
<span class="n">title</span><span class="o">=</span><span class="s2">"Pet Store"</span><span class="p">,</span>
|
||||
<span class="n">version</span><span class="o">=</span><span class="s2">"1.0"</span><span class="p">,</span>
|
||||
@@ -232,9 +325,11 @@ documentation automatically:</p>
|
||||
<li><p>Interactive Swagger UI documentation at <code class="docutils literal notranslate"><span class="pre">/docs</span></code></p></li>
|
||||
</ul>
|
||||
<p>There are three ways to document your endpoints.</p>
|
||||
<p><strong>Pydantic models</strong> — the recommended approach for new APIs. Use
|
||||
<code class="docutils literal notranslate"><span class="pre">request_model</span></code> and <code class="docutils literal notranslate"><span class="pre">response_model</span></code> to annotate your routes, and
|
||||
Responder will generate the schema automatically:</p>
|
||||
<p><strong>Pydantic models</strong> — the recommended approach. Use <code class="docutils literal notranslate"><span class="pre">request_model</span></code> and
|
||||
<code class="docutils literal notranslate"><span class="pre">response_model</span></code> to annotate your routes, and Responder generates the
|
||||
schema automatically. When <code class="docutils literal notranslate"><span class="pre">request_model</span></code> is set, request bodies are
|
||||
also validated automatically — invalid inputs get a <code class="docutils literal notranslate"><span class="pre">422</span></code> response with
|
||||
detailed error messages:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">pydantic</span><span class="w"> </span><span class="kn">import</span> <span class="n">BaseModel</span>
|
||||
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">PetIn</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
|
||||
@@ -253,18 +348,10 @@ Responder will generate the schema automatically:</p>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"id"</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span> <span class="o">**</span><span class="n">data</span><span class="p">}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This generates a full OpenAPI path with <code class="docutils literal notranslate"><span class="pre">requestBody</span></code> and <code class="docutils literal notranslate"><span class="pre">responses</span></code>
|
||||
schemas, all linked by <code class="docutils literal notranslate"><span class="pre">$ref</span></code> to your Pydantic models in
|
||||
<code class="docutils literal notranslate"><span class="pre">components/schemas</span></code>.</p>
|
||||
<p>You can also register standalone schemas with the <code class="docutils literal notranslate"><span class="pre">@api.schema</span></code> decorator:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">schema</span><span class="p">(</span><span class="s2">"Pet"</span><span class="p">)</span>
|
||||
<span class="k">class</span><span class="w"> </span><span class="nc">Pet</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
|
||||
<span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
|
||||
<span class="n">age</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">0</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>YAML docstrings</strong> — inline your OpenAPI spec directly in the docstring.
|
||||
This gives you full control over every detail:</p>
|
||||
<p>When <code class="docutils literal notranslate"><span class="pre">response_model</span></code> is set, the response is serialized through the
|
||||
model — extra fields are stripped and types are enforced.</p>
|
||||
<p><strong>YAML docstrings</strong> — for full control, embed OpenAPI YAML in the
|
||||
docstring:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/pets"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">list_pets</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="w"> </span><span class="sd">"""A list of pets.</span>
|
||||
@@ -278,8 +365,7 @@ This gives you full control over every detail:</p>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">[{</span><span class="s2">"name"</span><span class="p">:</span> <span class="s2">"Fido"</span><span class="p">}]</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p><strong>Marshmallow schemas</strong> — if you’re already using marshmallow for
|
||||
validation, Responder integrates with it via the apispec plugin:</p>
|
||||
<p><strong>Marshmallow schemas</strong> — if you’re already using marshmallow:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">marshmallow</span><span class="w"> </span><span class="kn">import</span> <span class="n">Schema</span><span class="p">,</span> <span class="n">fields</span>
|
||||
|
||||
<span class="nd">@api</span><span class="o">.</span><span class="n">schema</span><span class="p">(</span><span class="s2">"Pet"</span><span class="p">)</span>
|
||||
@@ -287,16 +373,39 @@ validation, Responder integrates with it via the apispec plugin:</p>
|
||||
<span class="n">name</span> <span class="o">=</span> <span class="n">fields</span><span class="o">.</span><span class="n">Str</span><span class="p">()</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>All three approaches can be mixed in the same API. Pydantic models,
|
||||
marshmallow schemas, and YAML docstrings all contribute to the same
|
||||
generated OpenAPI specification.</p>
|
||||
<p>You can choose from multiple documentation themes:
|
||||
<code class="docutils literal notranslate"><span class="pre">swagger_ui</span></code> (default), <code class="docutils literal notranslate"><span class="pre">redoc</span></code>, <code class="docutils literal notranslate"><span class="pre">rapidoc</span></code>, or <code class="docutils literal notranslate"><span class="pre">elements</span></code>.</p>
|
||||
<p>All three approaches can be mixed in the same API. You can choose from
|
||||
multiple documentation themes: <code class="docutils literal notranslate"><span class="pre">swagger_ui</span></code> (default), <code class="docutils literal notranslate"><span class="pre">redoc</span></code>,
|
||||
<code class="docutils literal notranslate"><span class="pre">rapidoc</span></code>, or <code class="docutils literal notranslate"><span class="pre">elements</span></code>.</p>
|
||||
</section>
|
||||
<section id="route-groups">
|
||||
<h2>Route Groups<a class="headerlink" href="#route-groups" title="Link to this heading">¶</a></h2>
|
||||
<p>As your application grows, you’ll want to organize routes logically.
|
||||
Route groups let you share a URL prefix across related endpoints — a
|
||||
common pattern for API versioning:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">v1</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="s2">"/v1"</span><span class="p">)</span>
|
||||
|
||||
<span class="nd">@v1</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/users"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">list_users</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
|
||||
<span class="nd">@v1</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/users/{user_id:int}"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">get_user</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">user_id</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"id"</span><span class="p">:</span> <span class="n">user_id</span><span class="p">}</span>
|
||||
|
||||
<span class="n">v2</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="s2">"/v2"</span><span class="p">)</span>
|
||||
|
||||
<span class="nd">@v2</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/users"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">list_users_v2</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"users"</span><span class="p">:</span> <span class="p">[],</span> <span class="s2">"total"</span><span class="p">:</span> <span class="mi">0</span><span class="p">}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This keeps your code organized without affecting the routing logic.</p>
|
||||
</section>
|
||||
<section id="mounting-other-apps">
|
||||
<h2>Mounting Other Apps<a class="headerlink" href="#mounting-other-apps" title="Link to this heading">¶</a></h2>
|
||||
<p>Responder can mount any WSGI or ASGI application at a subroute. This means
|
||||
you can gradually migrate from Flask, or run multiple frameworks side by side:</p>
|
||||
<p>Responder can mount any WSGI or ASGI application at a subroute. This is
|
||||
incredibly useful for gradual migrations — you can run Flask and Responder
|
||||
side by side, moving routes over one at a time:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">flask</span><span class="w"> </span><span class="kn">import</span> <span class="n">Flask</span>
|
||||
|
||||
<span class="n">flask_app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
|
||||
@@ -310,10 +419,14 @@ you can gradually migrate from Flask, or run multiple frameworks side by side:</
|
||||
</div>
|
||||
<p>Requests to <code class="docutils literal notranslate"><span class="pre">/flask/</span></code> will be handled by Flask. Everything else goes
|
||||
through Responder. Both WSGI and ASGI apps are supported — Responder
|
||||
wraps WSGI apps automatically.</p>
|
||||
wraps WSGI apps in an ASGI adapter automatically.</p>
|
||||
</section>
|
||||
<section id="cookies">
|
||||
<h2>Cookies<a class="headerlink" href="#cookies" title="Link to this heading">¶</a></h2>
|
||||
<p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies">Cookies</a> are
|
||||
small pieces of data that the server asks the browser to store and send
|
||||
back with every subsequent request. They’re the foundation of sessions,
|
||||
authentication tokens, and user preferences on the web.</p>
|
||||
<p>Reading and writing cookies is straightforward:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Read cookies from the request</span>
|
||||
<span class="n">session_id</span> <span class="o">=</span> <span class="n">req</span><span class="o">.</span><span class="n">cookies</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"session_id"</span><span class="p">)</span>
|
||||
@@ -322,24 +435,27 @@ wraps WSGI apps automatically.</p>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">cookies</span><span class="p">[</span><span class="s2">"hello"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"world"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>For more control over cookie directives, use <code class="docutils literal notranslate"><span class="pre">set_cookie</span></code>:</p>
|
||||
<p>For production use, you’ll want to set security directives. The
|
||||
<code class="docutils literal notranslate"><span class="pre">httponly</span></code> flag prevents JavaScript from reading the cookie (defending
|
||||
against XSS attacks), and <code class="docutils literal notranslate"><span class="pre">secure</span></code> ensures it’s only sent over HTTPS:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">resp</span><span class="o">.</span><span class="n">set_cookie</span><span class="p">(</span>
|
||||
<span class="s2">"token"</span><span class="p">,</span>
|
||||
<span class="n">value</span><span class="o">=</span><span class="s2">"abc123"</span><span class="p">,</span>
|
||||
<span class="n">max_age</span><span class="o">=</span><span class="mi">3600</span><span class="p">,</span>
|
||||
<span class="n">secure</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
||||
<span class="n">httponly</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span>
|
||||
<span class="n">max_age</span><span class="o">=</span><span class="mi">3600</span><span class="p">,</span> <span class="c1"># expires in 1 hour</span>
|
||||
<span class="n">secure</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="c1"># HTTPS only</span>
|
||||
<span class="n">httponly</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="c1"># no JavaScript access</span>
|
||||
<span class="n">path</span><span class="o">=</span><span class="s2">"/"</span><span class="p">,</span>
|
||||
<span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Supported directives: <code class="docutils literal notranslate"><span class="pre">key</span></code>, <code class="docutils literal notranslate"><span class="pre">value</span></code>, <code class="docutils literal notranslate"><span class="pre">expires</span></code>, <code class="docutils literal notranslate"><span class="pre">max_age</span></code>,
|
||||
<code class="docutils literal notranslate"><span class="pre">domain</span></code>, <code class="docutils literal notranslate"><span class="pre">path</span></code>, <code class="docutils literal notranslate"><span class="pre">secure</span></code>, <code class="docutils literal notranslate"><span class="pre">httponly</span></code>.</p>
|
||||
</section>
|
||||
<section id="cookie-based-sessions">
|
||||
<h2>Cookie-Based Sessions<a class="headerlink" href="#cookie-based-sessions" title="Link to this heading">¶</a></h2>
|
||||
<p>Responder has built-in support for signed, cookie-based sessions. Just
|
||||
read from and write to the <code class="docutils literal notranslate"><span class="pre">session</span></code> dictionary:</p>
|
||||
<p>Sessions let you store per-user data across multiple requests. Responder’s
|
||||
built-in sessions are cookie-based — the session data is serialized, signed
|
||||
with your secret key, and stored in a cookie. The signature prevents
|
||||
tampering: if someone modifies the cookie, the signature won’t match and
|
||||
the data will be rejected:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/login"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">login</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">session</span><span class="p">[</span><span class="s2">"username"</span><span class="p">]</span> <span class="o">=</span> <span class="s2">"alice"</span>
|
||||
@@ -349,12 +465,9 @@ read from and write to the <code class="docutils literal notranslate"><span clas
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"user"</span><span class="p">:</span> <span class="n">req</span><span class="o">.</span><span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"username"</span><span class="p">)}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>The session data is stored in a cookie called <code class="docutils literal notranslate"><span class="pre">Responder-Session</span></code>. It’s
|
||||
signed for tamper protection, so you can trust that the data originated
|
||||
from your server.</p>
|
||||
<div class="admonition warning">
|
||||
<p class="admonition-title">Warning</p>
|
||||
<p>For production use, always set a secret key:</p>
|
||||
<p>Always set a secret key in production. The default key is not secret:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">secret_key</span><span class="o">=</span><span class="s2">"your-secret-key-here"</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
@@ -362,27 +475,30 @@ from your server.</p>
|
||||
</section>
|
||||
<section id="static-files">
|
||||
<h2>Static Files<a class="headerlink" href="#static-files" title="Link to this heading">¶</a></h2>
|
||||
<p>Static files are served from the <code class="docutils literal notranslate"><span class="pre">static/</span></code> directory by default:</p>
|
||||
<p>Most web applications serve static assets — CSS stylesheets, JavaScript
|
||||
files, images, fonts. Responder serves these from the <code class="docutils literal notranslate"><span class="pre">static/</span></code> directory
|
||||
by default:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">static_dir</span><span class="o">=</span><span class="s2">"static"</span><span class="p">,</span> <span class="n">static_route</span><span class="o">=</span><span class="s2">"/static"</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Place your CSS, JavaScript, images, and other assets in the <code class="docutils literal notranslate"><span class="pre">static/</span></code>
|
||||
directory and they’ll be served automatically.</p>
|
||||
<p>For single-page applications, you can serve <code class="docutils literal notranslate"><span class="pre">index.html</span></code> as the default
|
||||
response for all unmatched routes:</p>
|
||||
<p>Place your assets in the <code class="docutils literal notranslate"><span class="pre">static/</span></code> directory and they’ll be served
|
||||
automatically at <code class="docutils literal notranslate"><span class="pre">/static/style.css</span></code>, <code class="docutils literal notranslate"><span class="pre">/static/app.js</span></code>, etc.</p>
|
||||
<p>For single-page applications (React, Vue, Angular), you can serve
|
||||
<code class="docutils literal notranslate"><span class="pre">index.html</span></code> as the default response for all unmatched routes:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span><span class="o">.</span><span class="n">add_route</span><span class="p">(</span><span class="s2">"/"</span><span class="p">,</span> <span class="n">static</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>You can add additional static directories at runtime:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span><span class="o">.</span><span class="n">static_app</span><span class="o">.</span><span class="n">add_directory</span><span class="p">(</span><span class="s2">"extra_assets"</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="cors">
|
||||
<h2>CORS<a class="headerlink" href="#cors" title="Link to this heading">¶</a></h2>
|
||||
<p>Enable Cross-Origin Resource Sharing for your API:</p>
|
||||
<p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS">CORS</a> (Cross-
|
||||
Origin Resource Sharing) is a security mechanism that controls which
|
||||
websites can make requests to your API. Browsers enforce this — if your
|
||||
API is at <code class="docutils literal notranslate"><span class="pre">api.example.com</span></code> and your frontend is at <code class="docutils literal notranslate"><span class="pre">app.example.com</span></code>,
|
||||
the browser will block requests unless your API explicitly allows it.</p>
|
||||
<p>Enable CORS and configure which origins are allowed:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">cors</span><span class="o">=</span><span class="kc">True</span><span class="p">,</span> <span class="n">cors_params</span><span class="o">=</span><span class="p">{</span>
|
||||
<span class="s2">"allow_origins"</span><span class="p">:</span> <span class="p">[</span><span class="s2">"https://example.com"</span><span class="p">],</span>
|
||||
<span class="s2">"allow_origins"</span><span class="p">:</span> <span class="p">[</span><span class="s2">"https://app.example.com"</span><span class="p">],</span>
|
||||
<span class="s2">"allow_methods"</span><span class="p">:</span> <span class="p">[</span><span class="s2">"GET"</span><span class="p">,</span> <span class="s2">"POST"</span><span class="p">],</span>
|
||||
<span class="s2">"allow_headers"</span><span class="p">:</span> <span class="p">[</span><span class="s2">"*"</span><span class="p">],</span>
|
||||
<span class="s2">"allow_credentials"</span><span class="p">:</span> <span class="kc">True</span><span class="p">,</span>
|
||||
@@ -390,98 +506,54 @@ response for all unmatched routes:</p>
|
||||
<span class="p">})</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>The default CORS policy is restrictive — you must explicitly enable the
|
||||
origins, methods, and headers your frontend needs.</p>
|
||||
<p>The default policy is restrictive — you must explicitly allow each origin.
|
||||
Using <code class="docutils literal notranslate"><span class="pre">["*"]</span></code> for allow_origins permits any website to call your API,
|
||||
which is fine for public APIs but not for private ones.</p>
|
||||
</section>
|
||||
<section id="hsts">
|
||||
<h2>HSTS<a class="headerlink" href="#hsts" title="Link to this heading">¶</a></h2>
|
||||
<p>Force all traffic to HTTPS with a single flag:</p>
|
||||
<p><a class="reference external" href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security">HSTS</a>
|
||||
(HTTP Strict Transport Security) tells browsers to always use HTTPS when
|
||||
communicating with your server. Once a browser sees the HSTS header, it
|
||||
will refuse to connect over plain HTTP, even if the user types <code class="docutils literal notranslate"><span class="pre">http://</span></code>
|
||||
in the address bar:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">enable_hsts</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>This adds the <code class="docutils literal notranslate"><span class="pre">Strict-Transport-Security</span></code> header and redirects HTTP
|
||||
requests to HTTPS.</p>
|
||||
</section>
|
||||
<section id="trusted-hosts">
|
||||
<h2>Trusted Hosts<a class="headerlink" href="#trusted-hosts" title="Link to this heading">¶</a></h2>
|
||||
<p>Protect against HTTP Host header attacks by restricting which hostnames
|
||||
your application will respond to:</p>
|
||||
<p>The <code class="docutils literal notranslate"><span class="pre">Host</span></code> header in an HTTP request tells the server which domain name
|
||||
the client used. Attackers can forge this header to trick your application
|
||||
into generating URLs to malicious domains (a class of attack called <em>Host
|
||||
header injection</em>).</p>
|
||||
<p>Restrict which hostnames your application accepts:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">allowed_hosts</span><span class="o">=</span><span class="p">[</span><span class="s2">"example.com"</span><span class="p">,</span> <span class="s2">"*.example.com"</span><span class="p">])</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Requests with a <code class="docutils literal notranslate"><span class="pre">Host</span></code> header that doesn’t match any of the patterns
|
||||
will receive a 400 Bad Request response. Wildcard domains are supported.</p>
|
||||
<p>By default, all hostnames are allowed.</p>
|
||||
</section>
|
||||
<section id="server-sent-events-sse">
|
||||
<h2>Server-Sent Events (SSE)<a class="headerlink" href="#server-sent-events-sse" title="Link to this heading">¶</a></h2>
|
||||
<p>Stream real-time updates to the client using Server-Sent Events. This is
|
||||
great for live feeds, progress updates, and AI streaming responses:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/events"</span><span class="p">)</span>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">events</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="nd">@resp</span><span class="o">.</span><span class="n">sse</span>
|
||||
<span class="k">async</span> <span class="k">def</span><span class="w"> </span><span class="nf">stream</span><span class="p">():</span>
|
||||
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
|
||||
<span class="k">yield</span> <span class="p">{</span><span class="s2">"data"</span><span class="p">:</span> <span class="sa">f</span><span class="s2">"message </span><span class="si">{</span><span class="n">i</span><span class="si">}</span><span class="s2">"</span><span class="p">}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Each yielded value can be a string (treated as data) or a dict with
|
||||
<code class="docutils literal notranslate"><span class="pre">data</span></code>, <code class="docutils literal notranslate"><span class="pre">event</span></code>, <code class="docutils literal notranslate"><span class="pre">id</span></code>, and <code class="docutils literal notranslate"><span class="pre">retry</span></code> fields:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">yield</span> <span class="p">{</span><span class="s2">"event"</span><span class="p">:</span> <span class="s2">"update"</span><span class="p">,</span> <span class="s2">"data"</span><span class="p">:</span> <span class="s2">"hello"</span><span class="p">,</span> <span class="s2">"id"</span><span class="p">:</span> <span class="s2">"1"</span><span class="p">}</span>
|
||||
<span class="k">yield</span> <span class="s2">"simple string message"</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="streaming-files">
|
||||
<h2>Streaming Files<a class="headerlink" href="#streaming-files" title="Link to this heading">¶</a></h2>
|
||||
<p>For large files, use <code class="docutils literal notranslate"><span class="pre">resp.stream_file()</span></code> to stream the content without
|
||||
loading the entire file into memory:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/download"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">download</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">stream_file</span><span class="p">(</span><span class="s2">"large-dataset.csv"</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>For small files where memory isn’t a concern, <code class="docutils literal notranslate"><span class="pre">resp.file()</span></code> loads the
|
||||
entire file at once — simpler but less efficient for large files.</p>
|
||||
</section>
|
||||
<section id="after-request-hooks">
|
||||
<h2>After-Request Hooks<a class="headerlink" href="#after-request-hooks" title="Link to this heading">¶</a></h2>
|
||||
<p>Run code after every request, useful for logging, adding headers, or
|
||||
cleanup:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nd">@api</span><span class="o">.</span><span class="n">after_request</span><span class="p">()</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">log_response</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"</span><span class="si">{</span><span class="n">req</span><span class="o">.</span><span class="n">method</span><span class="si">}</span><span class="s2"> </span><span class="si">{</span><span class="n">req</span><span class="o">.</span><span class="n">full_url</span><span class="si">}</span><span class="s2"> -> </span><span class="si">{</span><span class="n">resp</span><span class="o">.</span><span class="n">status_code</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
</section>
|
||||
<section id="route-groups">
|
||||
<h2>Route Groups<a class="headerlink" href="#route-groups" title="Link to this heading">¶</a></h2>
|
||||
<p>Organize related routes with a shared URL prefix. Useful for API versioning
|
||||
and logical grouping:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">v1</span> <span class="o">=</span> <span class="n">api</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="s2">"/v1"</span><span class="p">)</span>
|
||||
|
||||
<span class="nd">@v1</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/users"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">list_users</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">[]</span>
|
||||
|
||||
<span class="nd">@v1</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/users/{user_id:int}"</span><span class="p">)</span>
|
||||
<span class="k">def</span><span class="w"> </span><span class="nf">get_user</span><span class="p">(</span><span class="n">req</span><span class="p">,</span> <span class="n">resp</span><span class="p">,</span> <span class="o">*</span><span class="p">,</span> <span class="n">user_id</span><span class="p">):</span>
|
||||
<span class="n">resp</span><span class="o">.</span><span class="n">media</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"id"</span><span class="p">:</span> <span class="n">user_id</span><span class="p">}</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Requests with unrecognized hosts get a <code class="docutils literal notranslate"><span class="pre">400</span> <span class="pre">Bad</span> <span class="pre">Request</span></code>. Wildcard
|
||||
patterns are supported. By default, all hostnames are allowed.</p>
|
||||
</section>
|
||||
<section id="request-id">
|
||||
<h2>Request ID<a class="headerlink" href="#request-id" title="Link to this heading">¶</a></h2>
|
||||
<p>Auto-generate unique request IDs for tracing and debugging. If the client
|
||||
sends an <code class="docutils literal notranslate"><span class="pre">X-Request-ID</span></code> header, it’s forwarded; otherwise a new UUID is
|
||||
generated:</p>
|
||||
<p>In distributed systems, tracing a single request across multiple services
|
||||
is essential for debugging. Request IDs are unique identifiers attached to
|
||||
each request — if something goes wrong, you can search your logs for that
|
||||
ID and find every related event.</p>
|
||||
<p>Responder can auto-generate request IDs. If the client sends an
|
||||
<code class="docutils literal notranslate"><span class="pre">X-Request-ID</span></code> header (common in microservice architectures), it’s
|
||||
forwarded. Otherwise, a new UUID is generated:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">api</span> <span class="o">=</span> <span class="n">responder</span><span class="o">.</span><span class="n">API</span><span class="p">(</span><span class="n">request_id</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>The ID appears in the <code class="docutils literal notranslate"><span class="pre">X-Request-ID</span></code> response header.</p>
|
||||
</section>
|
||||
<section id="rate-limiting">
|
||||
<h2>Rate Limiting<a class="headerlink" href="#rate-limiting" title="Link to this heading">¶</a></h2>
|
||||
<p>Built-in token bucket rate limiter:</p>
|
||||
<p>Rate limiting prevents individual clients from overwhelming your API with
|
||||
too many requests. It’s essential for public APIs, and good practice even
|
||||
for internal ones.</p>
|
||||
<p>Responder includes a built-in token bucket rate limiter:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span><span class="w"> </span><span class="nn">responder.ext.ratelimit</span><span class="w"> </span><span class="kn">import</span> <span class="n">RateLimiter</span>
|
||||
|
||||
<span class="n">limiter</span> <span class="o">=</span> <span class="n">RateLimiter</span><span class="p">(</span><span class="n">requests</span><span class="o">=</span><span class="mi">100</span><span class="p">,</span> <span class="n">period</span><span class="o">=</span><span class="mi">60</span><span class="p">)</span> <span class="c1"># 100 req/min</span>
|
||||
@@ -489,19 +561,24 @@ generated:</p>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>When the limit is exceeded, clients receive a <code class="docutils literal notranslate"><span class="pre">429</span> <span class="pre">Too</span> <span class="pre">Many</span> <span class="pre">Requests</span></code>
|
||||
response with <code class="docutils literal notranslate"><span class="pre">Retry-After</span></code> and <code class="docutils literal notranslate"><span class="pre">X-RateLimit-Remaining</span></code> headers.</p>
|
||||
response with a <code class="docutils literal notranslate"><span class="pre">Retry-After</span></code> header. Every response includes
|
||||
<code class="docutils literal notranslate"><span class="pre">X-RateLimit-Limit</span></code> and <code class="docutils literal notranslate"><span class="pre">X-RateLimit-Remaining</span></code> headers so clients
|
||||
can pace themselves.</p>
|
||||
<p>The rate limiter is per-client, keyed by IP address.</p>
|
||||
</section>
|
||||
<section id="messagepack">
|
||||
<h2>MessagePack<a class="headerlink" href="#messagepack" title="Link to this heading">¶</a></h2>
|
||||
<p>In addition to JSON and YAML, Responder supports MessagePack for efficient
|
||||
binary serialization:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Decode MessagePack request body</span>
|
||||
<p><a class="reference external" href="https://msgpack.org/">MessagePack</a> is a binary serialization format
|
||||
that’s more compact and faster to parse than JSON. It’s useful for
|
||||
high-throughput APIs, IoT devices, and anywhere bandwidth matters.</p>
|
||||
<p>Responder supports MessagePack alongside JSON and YAML:</p>
|
||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># Decode a MessagePack request body</span>
|
||||
<span class="n">data</span> <span class="o">=</span> <span class="k">await</span> <span class="n">req</span><span class="o">.</span><span class="n">media</span><span class="p">(</span><span class="s2">"msgpack"</span><span class="p">)</span>
|
||||
|
||||
<span class="c1"># Content negotiation also works — clients can send</span>
|
||||
<span class="c1"># Accept: application/x-msgpack to receive MessagePack responses.</span>
|
||||
</pre></div>
|
||||
</div>
|
||||
<p>Content negotiation works too — clients can send
|
||||
<code class="docutils literal notranslate"><span class="pre">Accept:</span> <span class="pre">application/x-msgpack</span></code> to receive MessagePack responses
|
||||
instead of JSON.</p>
|
||||
</section>
|
||||
</section>
|
||||
|
||||
@@ -535,9 +612,12 @@ binary serialization:</p>
|
||||
<li><a class="reference internal" href="#serving-files">Serving Files</a></li>
|
||||
<li><a class="reference internal" href="#custom-error-handling">Custom Error Handling</a></li>
|
||||
<li><a class="reference internal" href="#before-request-hooks">Before-Request Hooks</a></li>
|
||||
<li><a class="reference internal" href="#after-request-hooks">After-Request Hooks</a></li>
|
||||
<li><a class="reference internal" href="#websocket-support">WebSocket Support</a></li>
|
||||
<li><a class="reference internal" href="#server-sent-events-sse">Server-Sent Events (SSE)</a></li>
|
||||
<li><a class="reference internal" href="#graphql">GraphQL</a></li>
|
||||
<li><a class="reference internal" href="#openapi-documentation">OpenAPI Documentation</a></li>
|
||||
<li><a class="reference internal" href="#route-groups">Route Groups</a></li>
|
||||
<li><a class="reference internal" href="#mounting-other-apps">Mounting Other Apps</a></li>
|
||||
<li><a class="reference internal" href="#cookies">Cookies</a></li>
|
||||
<li><a class="reference internal" href="#cookie-based-sessions">Cookie-Based Sessions</a></li>
|
||||
@@ -545,10 +625,6 @@ binary serialization:</p>
|
||||
<li><a class="reference internal" href="#cors">CORS</a></li>
|
||||
<li><a class="reference internal" href="#hsts">HSTS</a></li>
|
||||
<li><a class="reference internal" href="#trusted-hosts">Trusted Hosts</a></li>
|
||||
<li><a class="reference internal" href="#server-sent-events-sse">Server-Sent Events (SSE)</a></li>
|
||||
<li><a class="reference internal" href="#streaming-files">Streaming Files</a></li>
|
||||
<li><a class="reference internal" href="#after-request-hooks">After-Request Hooks</a></li>
|
||||
<li><a class="reference internal" href="#route-groups">Route Groups</a></li>
|
||||
<li><a class="reference internal" href="#request-id">Request ID</a></li>
|
||||
<li><a class="reference internal" href="#rate-limiting">Rate Limiting</a></li>
|
||||
<li><a class="reference internal" href="#messagepack">MessagePack</a></li>
|
||||
|
||||
Reference in New Issue
Block a user