Skip to content

Commit

Permalink
Fix ReverseProxied middleware and add docs (spec-first#1873)
Browse files Browse the repository at this point in the history
Starlette introduced some new changes to how the `root_path` is handled,
which broke our `ReverseProxied` example and test.

This PR fixes both and adds documentation on how to run behind a proxy.
  • Loading branch information
RobbeSneyders authored and mjp4 committed Feb 21, 2024
1 parent 97f12d4 commit cddb128
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 9 deletions.
29 changes: 29 additions & 0 deletions docs/cookbook.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,32 @@ Starlette. You can add it to your application, ideally in front of the ``Routing
:noindex:

.. _CORSMiddleware: https://www.starlette.io/middleware/#corsmiddleware

Reverse Proxy
-------------

When running behind a reverse proxy with stripped path prefix, you need to configure your
application to properly handle this.

Single known path prefix
''''''''''''''''''''''''

If there is only a single known prefix your application will be running behind, you can simply
pass this path prefix as the `root_path` to your ASGI server:

.. code-block:: bash
$ uvicorn run:app --root-path <root_path>
.. code-block:: bash
$ gunicorn -k uvicorn.workers.UvicornWorker run:app --root-path <root_path>
Dynamic path prefix
'''''''''''''''''''

If you are running behind multiple proxies, or the path is not known, you can wrap your
application in a `ReverseProxied` middleware as shown in `this example`_.

.. _this example: https://github.com/spec-first/connexion/tree/main/examples/reverseproxy
8 changes: 4 additions & 4 deletions examples/reverseproxy/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,10 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send):
root_path = value.decode()
break
if root_path:
scope["root_path"] = "/" + root_path.strip("/")
path_info = scope.get("PATH_INFO", scope.get("path"))
if path_info.startswith(root_path):
scope["PATH_INFO"] = path_info[len(root_path) :]
root_path = "/" + root_path.strip("/")
scope["root_path"] = root_path
scope["path"] = root_path + scope.get("path", "")
scope["raw_path"] = root_path.encode() + scope.get("raw_path", "")

scope["scheme"] = scope.get("scheme") or self.scheme
scope["server"] = scope.get("server") or (self.server, None)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Jinja2 = ">= 3.0.0"
python-multipart = ">= 0.0.5"
PyYAML = ">= 5.1"
requests = ">= 2.27"
starlette = ">= 0.27"
starlette = ">= 0.35"
typing-extensions = ">= 4"
werkzeug = ">= 2.2.1"

Expand Down
8 changes: 4 additions & 4 deletions tests/api/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,10 +73,10 @@ async def __call__(self, scope: Scope, receive: Receive, send: Send):
root_path = value.decode()
break
if root_path:
scope["root_path"] = "/" + root_path.strip("/")
path_info = scope.get("PATH_INFO", scope.get("path"))
if path_info.startswith(root_path):
scope["PATH_INFO"] = path_info[len(root_path) :]
root_path = "/" + root_path.strip("/")
scope["root_path"] = root_path
scope["path"] = root_path + scope.get("path", "")
scope["raw_path"] = root_path.encode() + scope.get("raw_path", "")

scope["scheme"] = scope.get("scheme") or self.scheme
scope["server"] = scope.get("server") or (self.server, None)
Expand Down

0 comments on commit cddb128

Please sign in to comment.