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.
Metadata
Release files for ASGIMiddleware 0.1.9
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| asgimiddleware-0.1.9.tar.gz | 18.2 kB | With provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| asgimiddleware-0.1.9-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 33.2 kB
Release files / asgimiddleware-0.1.9.tar.gz
| Download URL | asgimiddleware-0.1.9.tar.gz |
|---|---|
| Size | 18.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bcd909537b722e3e067c583200ab672eae4145f43d114b4136651083b20e906a
|
|
BLAKE2b-256 checksum How to use checksums |
68bbc68d511a9d821cc05aa86823a5d800bde5606ed3f1a5507a9e86244dbd8f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 21, 2026.
Transparency logRelease files / asgimiddleware-0.1.9-py3-none-any.whl
| Download URL | asgimiddleware-0.1.9-py3-none-any.whl |
|---|---|
| Size | 14.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
a600b1de3e8bee57220c4594d8cb418be7365e543b48c508a4746fbdb2bd278f
|
|
BLAKE2b-256 checksum How to use checksums |
d8c446536584b08b05388efb7035bb9c44ddebf20e8c008be3dd77ed39f2bb42
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 21, 2026.
Transparency logSecurity for ASGIMiddleware 0.1.9
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for asgimiddleware-0.1.9.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jan 21, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.