Skip to content

Instantly share code, notes, and snippets.

@gdamjan
Last active July 5, 2023 17:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save gdamjan/597f29eb9d08076396a244df04ffe0f9 to your computer and use it in GitHub Desktop.
Save gdamjan/597f29eb9d08076396a244df04ffe0f9 to your computer and use it in GitHub Desktop.
fastapi and proxies

Quickstart

pdm install

These are equivalent (given the main.py code)

UVICORN_ROOT_PATH=/api/ pdm run uvicorn main:app
FASTAPI_ROOT_PATH=/api/ pdm run uvicorn main:app
pdm run uvicorn main:app --root-path /api

Expected response from all 3 variants:

$ curl http://127.0.0.1:8000/
{"message":"Hello World","root_path":"/api/"}

But

AWS ALB doesn't do url path rewriting, so when we have a Listener Rule that forward all requests that match an /api prefix to our FastAPI backend, it will pass back the whole url, including the prefix. So what we really need to work is this:

$ curl http://127.0.0.1:8000/api/
{"message":"Hello World","root_path":"/api/"}

without changing the whole application.

References

from fastapi import FastAPI, Request
from proxy_middleware import ProxyPrefixMiddleware
import os
root_path = os.environ.get("FASTAPI_ROOT_PATH")
app = FastAPI(root_path=root_path)
if root_path:
app.add_middleware(ProxyPrefixMiddleware)
@app.get("/")
async def root(request: Request):
return {"message": "Hello World", "root_path": request.scope.get("root_path")}
'''
Usage:
if root_path:
app.add_middleware(ProxyPrefixMiddleware)
'''
class ProxyPrefixMiddleware:
def __init__(self, app):
self.app = app
async def __call__(self, scope, receive, send):
if scope["type"] in {"http", "websocket"}:
root_path = scope["root_path"].rstrip('/')
scope["path"] = scope["path"].removeprefix(root_path)
await self.app(scope, receive, send)
[project]
name = "fastapi-proxies"
version = "0.0.1"
description = ""
authors = []
dependencies = [
"fastapi>=0.99.1",
"uvicorn>=0.22.0",
]
requires-python = ">=3.9"
license = {text = "MIT"}
@gdamjan
Copy link
Author

gdamjan commented Jul 5, 2023

also expected (probably)

$ curl -s http://127.0.0.1:8000/openapi.json | jq '.servers[0].url'
"/api"

@gdamjan
Copy link
Author

gdamjan commented Jul 5, 2023

ps.
neither UVICORN_ROOT_PATH nor --root-path work when using the gunicorn app server:

pdm run gunicorn --worker-class uvicorn.workers.UvicornWorker main:app

so it seems, my FASTAPI_ROOT_PATH custom implementation is a better fit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment