Skip to content

Commit

Permalink
Add middleware documentation (#1706)
Browse files Browse the repository at this point in the history
Contributes towards #1531

This PR adds a documentation page on middleware.
  • Loading branch information
RobbeSneyders committed Jun 7, 2023
1 parent a34da31 commit fcd4e66
Show file tree
Hide file tree
Showing 3 changed files with 260 additions and 0 deletions.
54 changes: 54 additions & 0 deletions connexion/middleware/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,66 @@ def replace(self, **changes) -> "_Options":


class MiddlewarePosition(enum.Enum):
"""Positions to insert a middleware"""

BEFORE_SWAGGER = SwaggerUIMiddleware
"""Add before the :class:`SwaggerUIMiddleware`. This is useful if you want your changes to
affect the Swagger UI, such as a path altering middleware that should also alter the paths
exposed by the Swagger UI
Be mindful that security has not yet been applied at this stage.
Since the inserted middleware is positioned before the RoutingMiddleware, you cannot leverage
any routing information yet and should implement your middleware to work globally instead of on
an operation level.
:meta hide-value:
"""
BEFORE_ROUTING = RoutingMiddleware
"""Add before the :class:`RoutingMiddleware`. This is useful if you want your changes to be
applied before hitting the router, such as for path altering or CORS middleware.
Be mindful that security has not yet been applied at this stage.
Since the inserted middleware is positioned before the RoutingMiddleware, you cannot leverage
any routing information yet and should implement your middleware to work globally instead of on
an operation level.
:meta hide-value:
"""
BEFORE_SECURITY = SecurityMiddleware
"""Add before the :class:`SecurityMiddleware`. Insert middleware here that needs to be able to
adapt incoming requests before security is applied.
Be mindful that security has not yet been applied at this stage.
Since the inserted middleware is positioned after the RoutingMiddleware, you can leverage
routing information and implement the middleware to work on an individual operation level.
:meta hide-value:
"""
BEFORE_VALIDATION = RequestValidationMiddleware
"""Add before the :class:`RequestValidationMiddleware`. Insert middleware here that needs to be
able to adapt incoming requests before they are validated.
Since the inserted middleware is positioned after the RoutingMiddleware, you can leverage
routing information and implement the middleware to work on an individual operation level.
:meta hide-value:
"""
BEFORE_CONTEXT = ContextMiddleware
"""Add before the :class:`ContextMiddleware`, near the end of the stack. This is the default
location. The inserted middleware is only followed by the ContextMiddleware, which ensures any
changes to the context are properly exposed to the application.
Since the inserted middleware is positioned after the RoutingMiddleware, you can leverage
routing information and implement the middleware to work on an individual operation level.
Since the inserted middleware is positioned after the ResponseValidationMiddleware,
it can intercept responses coming from the application and alter them before they are validated.
:meta hide-value:
"""


class API:
Expand Down
1 change: 1 addition & 0 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Documentation
:maxdepth: 2

quickstart
middleware
cli
routing
request
Expand Down
205 changes: 205 additions & 0 deletions docs/middleware.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
Middleware
==========

Connexion is built as an ASGI middleware stack wrapping an application. It includes several
middlewares by default that add functionality based on the OpenAPI specification, in the
following order:

.. csv-table::
:widths: 30, 70

**ExceptionMiddleware**, Handles exceptions raised by the middleware stack or application
**SwaggerUIMiddleware**, Adds a Swagger UI to your application
**RoutingMiddleware**, "Routes incoming requests to the right operation defined in the
specification"
**SecurityMiddleware**, "Checks incoming requests against the security defined in the
specification"
**RequestValidationMiddleware**, Validates the incoming requests against the spec
**ResponseValidationMiddleware**, "Validates the returned responses against the spec, if
activated"
**LifespanMiddleware**, "Allows registration of code to run before application start-up or
after shut-down"
**ContextMiddleware**, "Makes several request scoped context variables available to the
application"

Adding middleware
-----------------

You can easily add additional ASGI middleware to the middleware stack with the
:code:`add_middleware` method:

.. tab-set::

.. tab-item:: AsyncApp
:sync: AsyncApp

.. code-block:: python
from connexion import AsyncApp
app = AsyncApp(__name__)
app.add_middleware(MiddlewareClass, **options)
.. dropdown:: View a detailed reference of the :code:`add_middleware` method
:icon: eye

.. automethod:: connexion.AsyncApp.add_middleware
:noindex:

.. tab-item:: FlaskApp
:sync: FlaskApp

.. code-block:: python
from connexion import FlaskApp
app = FlaskApp(__name__)
app.add_middleware(MiddlewareClass, **options)
.. dropdown:: View a detailed reference of the :code:`add_middleware` method
:icon: eye

.. automethod:: connexion.FlaskApp.add_middleware
:noindex:

.. tab-item:: ConnexionMiddleware
:sync: ConnexionMiddleware

.. code-block:: python
from asgi_framework import App
from connexion import ConnexionMiddleware
app = App(__name__)
app = ConnexionMiddleware(app)
app.add_middleware(MiddlewareClass, **options)
.. dropdown:: View a detailed reference of the :code:`add_middleware` method
:icon: eye

.. automethod:: connexion.ConnexionMiddleware.add_middleware
:noindex:

Middleware order
****************

The :code:`add_middleware` method takes a :code:`position` argument to define where in the
middleware stack it should be inserted, which should be an instance of the
:class:`~connexion.middleware.MiddlewarePosition` Enum. The positions below are ordered from
outer to inner, in the order they are hit by incoming requests. Note that responses hit the
middlewares in reversed order.

.. autoclass:: connexion.middleware.MiddlewarePosition
:members:
:member-order: bysource

Customizing the middleware stack
--------------------------------

If you need more flexibility, or want to modify or delete any of the default middlewares, you can
also pass in a customized middleware stack when instantiating your application.

For example, if you would like to remove the :class:`SecurityMiddleware` since you are handling
Security through an API Gateway in front of your application, you can do:

.. tab-set::

.. tab-item:: AsyncApp
:sync: AsyncApp

.. code-block:: python
from connexion import AsyncApp, ConnexionMiddleware
middlewares = [middleware for middleware in ConnexionMiddleware.default_middlewares
if not isinstance(middleware, SecurityMiddleware)]
app = AsyncApp(__name__, middlewares=middlewares)
.. dropdown:: View a detailed reference of the :class:`~connexion.AsyncApp`
:code:`__init__` method
:icon: eye

.. autoclass:: connexion.AsyncApp
:noindex:

.. tab-item:: FlaskApp
:sync: FlaskApp

.. code-block:: python
from connexion import FlaskApp, ConnexionMiddleware
middlewares = [middleware for middleware in ConnexionMiddleware.default_middlewares
if not isinstance(middleware, SecurityMiddleware)]
app = FlaskApp(__name__, middlewares=middlewares)
.. dropdown:: View a detailed reference of the :class:`~connexion.FlaskApp`
:code:`__init__` method
:icon: eye

.. autoclass:: connexion.FlaskApp
:noindex:


.. tab-item:: ConnexionMiddleware
:sync: ConnexionMiddleware

.. code-block:: python
from asgi_framework import App
from connexion import ConnexionMiddleware
middlewares = [middleware for middleware in ConnexionMiddleware.default_middlewares
if not isinstance(middleware, SecurityMiddleware)]
app = App(__name__)
app = ConnexionMiddleware(app, middlewares=middlewares)
.. dropdown:: View a detailed reference of the :class:`~connexion.ConnexionMiddleware`
:code:`__init__` method
:icon: eye

.. autoclass:: connexion.ConnexionMiddleware
:noindex:


Writing custom middleware
-------------------------

You can add any custom middleware as long as it implements the ASGI interface. To learn how to
write pure ASGI middleware, please refer to the `documentation of starlette`_.

List of useful middleware
-------------------------

Starlette provides a bunch of useful middleware such as:

* `CORSMiddleware`_
* `SessionMiddleware`_
* `HTTPSRedirectMiddleware`_
* `TrustedHostMiddleware`_
* `GZipMiddleware`_

Other useful middleware:

* `ProxyHeadersMiddleware`_ by Uvicorn
* `SentryASGIMiddleware`_ by Sentry
* `MetricsMiddleware`_ by Prometheus

For more, check the `asgi-middleware topic`_ on github.

.. _documentation of starlette: https://www.starlette.io/middleware/#writing-pure-asgi-middleware
.. _CORSMiddleware: https://www.starlette.io/middleware/#corsmiddleware
.. _SessionMiddleware: https://www.starlette.io/middleware/#sessionmiddleware
.. _HTTPSRedirectMiddleware: https://www.starlette.io/middleware/#httpsredirectmiddleware
.. _TrustedHostMiddleware: https://www.starlette.io/middleware/#trustedhostmiddleware
.. _GZipMiddleware: https://www.starlette.io/middleware/#gzipmiddleware
.. _ProxyHeadersMiddleware: https://github.com/encode/uvicorn/blob/master/uvicorn/middleware/proxy_headers.py
.. _SentryASGIMiddleware: https://docs.sentry.io/platforms/python/configuration/integrations/asgi/
.. _MetricsMiddleware: https://github.com/claws/aioprometheus/blob/master/src/aioprometheus/asgi/middleware.py
.. _asgi-middleware topic: https://github.com/topics/asgi-middleware

0 comments on commit fcd4e66

Please sign in to comment.