Connexion-based ASGI Middleware collection
Project description
Connexion-based ASGI Middleware Collection
This repository contains Connexion-based ASGI middleware collection for custom logging, exception handling, tracing and observability as well as enhanced routing.
This project was developed in Red Hat Inc. as an effort to add observability support for Connexion 3.
Quick Start
Simple usage example can be shown in the following snippet.
from connexion import AsyncApp
from connexion.middleware import ConnexionMiddleware, MiddlewarePosition
from connexion.middleware.exceptions import ExceptionMiddleware
from connexion.middleware.routing import RoutingMiddleware
from opentelemetry.instrumentation.asgi import OpenTelemetryMiddleware
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.resources import Resource
from asgimiddlewares import *
middleware_stack = ConnexionMiddleware.default_middlewares
replace_middleware(middleware_stack, ExceptionMiddleware, CustomExceptionMiddleware)
replace_middleware(middleware_stack, RoutingMiddleware, CustomRoutingMiddleware)
provider = TracerProvider(resource=Resource.create())
your_app = AsyncApp("your_service", middlewares=middleware_stack)
your_app.add_middleware(
OpenTelemetryMiddleware,
tracer_provider=provider,
server_request_hook=server_request_hook,
position=CustomMiddlewarePosition.BEFORE_CUSTOM_EXCEPTION,
)
your_app.add_middleware(
CustomHeaderMiddleware, position=CustomMiddlewarePosition.BEFORE_CUSTOM_EXCEPTION
)
# Security is normally right after routing, PathID needs to be after CustomRoutingMiddleware
your_app.add_middleware(PathIdMiddleware, position=MiddlewarePosition.BEFORE_SECURITY)
your_app.add_api(
{
"info": {"title": "sample", "version": "1"},
"openapi": "3.0.0",
"paths": {
"/ping": {
"get": {
"operationId": "ping.ping", # File ping.py contains function ping that returns sample string
"responses": {"200": {"description": "ok"}},
}
}
},
}
)
your_app.run()
As written in the snippet, you also need a file ping.py containing the endpoint logic like so:
def ping():
return "pong"
Now you can test your sample application by running:
curl -v http://localhost:8000/ping
You will see that additional headers are added to the response message.
Middlewares
This project intends to work with middlewares present in Connexion. The default Connexion stack looks like this:
- ServerErrorMiddleware
- ExceptionMiddleware
- SwaggerUIMiddleware
- RoutingMiddleware
- SecurityMiddleware
- RequestValidationMiddleware
- ResponseValidationMiddleware
- LifespanMiddleware
- ContextMiddleware
If you wish to use all middlewares contained in this repository, the recommended output state will look like the following:
- ServerErrorMiddleware
- OpenTelemetryMiddleware
- CustomHeaderMiddleware
- ExtendedLoggingMiddleware
- PrometheusMiddleware
- RequestTimeMiddleware
- CustomExceptionMiddleware
- SwaggerUIMiddleware
- CustomRoutingMiddleware
- SecurityMiddleware
- RequestValidationMiddleware
- ResponseValidationMiddleware
- LifespanMiddleware
- PathIdMiddleware
- ContextMiddleware
CustomExceptionMiddleware
This middleware adds a handler for internal server errors. It sends a log message of level
error and adds trace_id to the error details so customers are able to point maintainers
to an exact request.
NOTE: trace_id is taken from context variable which is filled in by ExtendedLoggingMiddleware.
NOTE: Make sure to allow the logger asgimiddlewares to log error messages if you want to utilize
this middleware.
Usage
This middleware is intended to replace Connexion's ExceptionMiddleware like so:
middleware_stack = ConnexionMiddleware.default_middlewares
replace_middleware(middleware_stack, ExceptionMiddleware, CustomExceptionMiddleware)
your_app = AsyncApp("your_service", middlewares=middleware_stack)
CustomHeaderMiddleware
This middleware adds additional fields to response headers. These fields are added:
trace_idfor observabilityContent-Security-Policyfor XSS mitigation (disables all script sources as API is expected to return no scripts)
You can also disable the CSP header on some part of your API by specifying csp_disable argument.
This approach is good for using Swagger.
NOTE: trace_id requires 3rd party middleware, called
OpenTelemetryMiddleware.
Usage
This middleware should also add headers before exceptions are handled so that observability is possible even when errors occur.
The following snippet works if you use CustomExceptionMiddleware in your stack.
your_app.add_middleware(
CustomHeaderMiddleware,
position=CustomMiddlewarePosition.BEFORE_CUSTOM_EXCEPTION,
csp_disable=("/v1/ui", "/v2/ui", "/ui"),
)
Otherwise use:
your_app.add_middleware(
CustomHeaderMiddleware,
position=MiddlewarePosition.BEFORE_EXCEPTION,
csp_disable=("/v1/ui", "/v2/ui", "/ui"),
)
ExtendedLoggingMiddleware
This middleware allows you to extract more metadata from requests and responses and use them in logging.
All output from this middleware is stored in context variable
logging_ctx_var.
For example you can define your own log formatter that uses this variable.
By default, this middleware adds all possible fields, which are:
- method
- path
- path_id (requires
PathIdMiddleware) - protocol
- query
- referer
- remote_address
- response_length
- status
- username (to be implemented)
- user_agent
- trace_id (requires
OpenTelemetryMiddleware) - True-Client-IP
- X-Akamai-RH-Edge-Id (specific for Red Hat use)
- X-Forwarded-For
- X-Forwarded-Proto
- X-Forwarded-Port
- X-Forwarded-Host
Usage
connexion_app.add_middleware(
ExtendedLoggingMiddleware,
position=CustomMiddlewarePosition.BEFORE_CUSTOM_EXCEPTION,
fields=("method", "path", "protocol", "response_length", ...)
)
All the stored data can then be accessed by logging_ctx_var.get(). This variable is a dictionary
with keys being strings from the list above.
PathIdMiddleware
NOTE: This middleware requires CustomRoutingMiddleware to be present in your
middleware stack.
This middleware adds variable path_id to scope, to be used by other middlewares
(ExtendedLoggingMiddleware, PrometheusMiddleware). Path ID is argument-agnostic
endpoint path.
For example if we register endoint /v1/foo/{bar} and it receives a request (pointed
to http://samplehost/v1/foo/spam), scope gets another field, called path_id with
value "/v1/foo/{bar}". Without this middleware, only field path with value "/v1/foo/spam"
would be present, which prevents effective log filtering.
If no match is found, this middleware fills the path_id as an empty stríng.
Usage
This middleware needs to be positioned after CustomRoutingMiddleware, but it does not need
to be immediately after it.
your_app.add_middleware(PathIdMiddleware)
PrometheusMiddleware
This middleware brings Prometheus metrics support for observability of your application. It exposes the selected port to export metrics about endpoint usage.
Usage
This middleware requires PathIdMiddleware.
To exclude paths from being tracked, pass them to the excluded_paths parameter.
your_app.add_middleware(
position=CustomMiddlewarePosition.BEFORE_CUSTOM_EXCEPTION,
PrometheusMiddleware,
service_name="cool_service",
port=8000,
excluded_paths=("", "/v1/ping", "/v1/ignore_me/{foo}"),
)
RequestTimeMiddleware
This middleware measures the time to process a request. Its output is present
in request_time_ctx_vat context variable and can be used for your needs.
For example you can define your own log formatter that uses this variable.
Usage
your_app.add_middleware(
RequestTimeMiddleware,
position=CustomMiddlewarePosition.BEFORE_CUSTOM_EXCEPTION,
)
CustomRoutingMiddleware
This middleware is intended to replace Connexion's RoutingMiddleware. It solves
an open issue in Connexion
and adds a router object to the scope to be used by PathIdMiddleware.
Usage
replace_middleware(middleware_stack, functools.RoutingMiddleware, CustomRoutingMiddleware)
Context variables
Some of the provided middlewares provide context variables to store the information gathered for later use. The provided variables are:
logging_ctx_var: ContextVar[dict[str, typing.Any]], variable used for logging all selected fields fromExtendedLoggingMiddlewarerequest_time_ctx_var: ContextVar[float | None], variable used for keeping track of the request duration.
Recommended way of using the values of these variables is declaring your own log formatter which uses these variables like so:
class MyFormatter(logging.Formatter):
def format(self, record):
...
record["request_time"] = request_time_ctx_var.get()
record["path_id"] = logging_ctx_var.get().get("path_id", "")
...
return str(record)
For formatter reference, follow this documentation.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file asgimiddleware-0.1.9.tar.gz.
File metadata
- Download URL: asgimiddleware-0.1.9.tar.gz
- Upload date:
- Size: 18.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bcd909537b722e3e067c583200ab672eae4145f43d114b4136651083b20e906a
|
|
| MD5 |
c14bfd3d5e5d1618efc3130d52843c7d
|
|
| BLAKE2b-256 |
68bbc68d511a9d821cc05aa86823a5d800bde5606ed3f1a5507a9e86244dbd8f
|
Provenance
The following attestation bundles were made for asgimiddleware-0.1.9.tar.gz:
Publisher:
release.yml on release-engineering/ASGI-Middlewares
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
asgimiddleware-0.1.9.tar.gz -
Subject digest:
bcd909537b722e3e067c583200ab672eae4145f43d114b4136651083b20e906a - Sigstore transparency entry: 842935874
- Sigstore integration time:
-
Permalink:
release-engineering/ASGI-Middlewares@46f1c48147ab30638449e755b38a273f52c5c3bd -
Branch / Tag:
refs/tags/v0.1.9 - Owner: https://github.com/release-engineering
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@46f1c48147ab30638449e755b38a273f52c5c3bd -
Trigger Event:
push
-
Statement type:
File details
Details for the file asgimiddleware-0.1.9-py3-none-any.whl.
File metadata
- Download URL: asgimiddleware-0.1.9-py3-none-any.whl
- Upload date:
- Size: 14.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a600b1de3e8bee57220c4594d8cb418be7365e543b48c508a4746fbdb2bd278f
|
|
| MD5 |
64b6ed5627cb03638c4ac722878d12d2
|
|
| BLAKE2b-256 |
d8c446536584b08b05388efb7035bb9c44ddebf20e8c008be3dd77ed39f2bb42
|
Provenance
The following attestation bundles were made for asgimiddleware-0.1.9-py3-none-any.whl:
Publisher:
release.yml on release-engineering/ASGI-Middlewares
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
asgimiddleware-0.1.9-py3-none-any.whl -
Subject digest:
a600b1de3e8bee57220c4594d8cb418be7365e543b48c508a4746fbdb2bd278f - Sigstore transparency entry: 842935876
- Sigstore integration time:
-
Permalink:
release-engineering/ASGI-Middlewares@46f1c48147ab30638449e755b38a273f52c5c3bd -
Branch / Tag:
refs/tags/v0.1.9 - Owner: https://github.com/release-engineering
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@46f1c48147ab30638449e755b38a273f52c5c3bd -
Trigger Event:
push
-
Statement type: