mirror of https://github.com/tiangolo/fastapi.git
Update the part of "Behind a proxy" section related to `root_path`
This commit is contained in:
parent
78c94c3f56
commit
f23fba1ead
|
|
@ -97,19 +97,52 @@ These headers preserve information about the original request that would otherwi
|
|||
|
||||
When **FastAPI CLI** is configured with `--forwarded-allow-ips`, it trusts these headers and uses them, for example to generate the correct URLs in redirects.
|
||||
|
||||
## Proxy with a stripped path prefix { #proxy-with-a-stripped-path-prefix }
|
||||
## Serve the app under a path prefix
|
||||
|
||||
You could have a proxy that adds a path prefix to your application.
|
||||
In production, you might want to serve your FastAPI application under a URL prefix, such as:
|
||||
|
||||
In these cases you can use `root_path` to configure your application.
|
||||
```
|
||||
https://example.com/api/v1
|
||||
```
|
||||
|
||||
The `root_path` is a mechanism provided by the ASGI specification (that FastAPI is built on, through Starlette).
|
||||
instead of serving it directly at the root of the domain.
|
||||
|
||||
The `root_path` is used to handle these specific cases.
|
||||
### Why serve under a prefix?
|
||||
|
||||
And it's also used internally when mounting sub-applications.
|
||||
This is common when:
|
||||
|
||||
Having a proxy with a stripped path prefix, in this case, means that you could declare a path at `/app` in your code, but then, you add a layer on top (the proxy) that would put your **FastAPI** application under a path like `/api/v1`.
|
||||
* You host several applications on the same host and port - for example, an API, a dashboard, and an admin panel, all behind the same domain.
|
||||
Serving them under different prefixes (like `/api`, `/dashboard`, and `/admin`) helps you avoid setting up multiple domains or ports and prevents cross-origin (CORS) issues.
|
||||
* You deploy multiple API versions (e.g. `/v1`, `/v2`) side by side to ensure backward compatibility while rolling out new features.
|
||||
|
||||
Hosting under a prefix keeps everything accessible under a single base URL (like `https://example.com`), simplifying proxy, SSL, and frontend configuration.
|
||||
|
||||
### Routing requests through a reverse proxy
|
||||
|
||||
In these setups, you typically run your FastAPI app behind a reverse proxy such as Traefik, Nginx, Caddy, or an API Gateway.
|
||||
The proxy is configured to route requests under a specific path prefix.
|
||||
|
||||
For example, using a Traefik router:
|
||||
|
||||
```TOML hl_lines="8"
|
||||
[http]
|
||||
|
||||
[http.routers]
|
||||
|
||||
[http.routers.app-http]
|
||||
entryPoints = ["http"]
|
||||
service = "app"
|
||||
rule = "PathPrefix(`/api/v1`)"
|
||||
|
||||
[http.services]
|
||||
|
||||
[http.services.app]
|
||||
[http.services.app.loadBalancer]
|
||||
[[http.services.app.loadBalancer.servers]]
|
||||
url = "http://127.0.0.1:8000"
|
||||
```
|
||||
|
||||
This router config tells Traefik to forward all requests starting with `/api/v1` to your FastAPI service.
|
||||
|
||||
In this case, the original path `/app` would actually be served at `/api/v1/app`.
|
||||
|
||||
|
|
@ -117,15 +150,56 @@ Even though all your code is written assuming there's just `/app`.
|
|||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial001.py hl[6] *}
|
||||
|
||||
And the proxy would be **"stripping"** the **path prefix** on the fly before transmitting the request to the app server (probably Uvicorn via FastAPI CLI), keeping your application convinced that it is being served at `/app`, so that you don't have to update all your code to include the prefix `/api/v1`.
|
||||
...but your app only knows routes like `/app` and has no idea about the `/api/v1` prefix.
|
||||
|
||||
Up to here, everything would work as normally.
|
||||
As a result, the app won't be able to match the routes correctly, and clients will get `404 Not Found` errors 😱.
|
||||
|
||||
But then, when you open the integrated docs UI (the frontend), it would expect to get the OpenAPI schema at `/openapi.json`, instead of `/api/v1/openapi.json`.
|
||||
### Stripping the prefix
|
||||
|
||||
So, the frontend (that runs in the browser) would try to reach `/openapi.json` and wouldn't be able to get the OpenAPI schema.
|
||||
One simple way to solve the problem described above is to configure the proxy to strip the prefix before forwarding the request to the app.
|
||||
For example, if the client requests `/api/v1/app`, the proxy forwards it as just `/app`.
|
||||
|
||||
Because we have a proxy with a path prefix of `/api/v1` for our app, the frontend needs to fetch the OpenAPI schema at `/api/v1/openapi.json`.
|
||||
```TOML hl_lines="2-5 13"
|
||||
[http]
|
||||
[http.middlewares]
|
||||
|
||||
[http.middlewares.api-stripprefix.stripPrefix]
|
||||
prefixes = ["/api/v1"]
|
||||
|
||||
[http.routers]
|
||||
|
||||
[http.routers.app-http]
|
||||
entryPoints = ["http"]
|
||||
service = "app"
|
||||
rule = "PathPrefix(`/api/v1`)"
|
||||
middlewares = ["api-stripprefix"]
|
||||
|
||||
[http.services]
|
||||
|
||||
[http.services.app]
|
||||
[http.services.app.loadBalancer]
|
||||
[[http.services.app.loadBalancer.servers]]
|
||||
url = "http://127.0.0.1:8000"
|
||||
```
|
||||
|
||||
The proxy would be **"stripping"** the **path prefix** on the fly before transmitting the request to the app server (probably Uvicorn via FastAPI CLI), keeping your application convinced that it is being served at `/app`, so that you don't have to update all your code to include the prefix `/api/v1`.
|
||||
|
||||
Now the app's routing works perfectly - `/app` matches exactly as expected. 🎉
|
||||
|
||||
But... something is still off...
|
||||
|
||||
### When the app doesn't know it's running under a prefix
|
||||
|
||||
While stripping the prefix by the proxy fixes incoming requests, the app still doesn't know that it's being served under `/api/v1`.
|
||||
So any URLs generated inside the app - like those from `url_for()`, URL of `openapi.json` in docs, or redirects - will miss the prefix.
|
||||
Clients will get links like `/app` instead of `/api/v1/app`, breaking navigation and documentation.
|
||||
|
||||
Let's look closer at the problem with URL of `openapi.json` in docs.
|
||||
|
||||
When you open the integrated docs UI (the frontend), it expects to get the OpenAPI schema at `/openapi.json`.
|
||||
But, since your app is served under the `/api/v1` prefix, the correct URL of `openapi.json` would be `/api/v1/openapi.json`.
|
||||
|
||||
So the frontend, running in the browser, will try to reach `/openapi.json` and fail to get the OpenAPI schema.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
|
|
@ -144,28 +218,67 @@ The IP `0.0.0.0` is commonly used to mean that the program listens on all the IP
|
|||
|
||||
///
|
||||
|
||||
The docs UI would also need the OpenAPI schema to declare that this API `server` is located at `/api/v1` (behind the proxy). For example:
|
||||
At this point, it becomes clear that we need to tell the app the path prefix on which it's running.
|
||||
|
||||
```JSON hl_lines="4-8"
|
||||
Luckily, this problem isn't new - and the people who designed the ASGI specification have already thought about it.
|
||||
|
||||
### Understanding `root_path` in ASGI
|
||||
|
||||
ASGI defines two fields that make it possible for applications to know where they are mounted and still handle requests correctly:
|
||||
|
||||
* `path` – the full path requested by the client (including the prefix)
|
||||
* `root_path` – the mount point (the prefix itself) under which the app is served
|
||||
|
||||
With this information, the application always knows both:
|
||||
|
||||
* what the user actually requested (`path`), and
|
||||
* where the app lives in the larger URL structure (`root_path`).
|
||||
|
||||
For example, if the client requests:
|
||||
|
||||
```
|
||||
/api/v1/app
|
||||
```
|
||||
|
||||
the ASGI server should pass this to the app as:
|
||||
|
||||
```
|
||||
{
|
||||
"openapi": "3.1.0",
|
||||
// More stuff here
|
||||
"servers": [
|
||||
{
|
||||
"url": "/api/v1"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
// More stuff here
|
||||
}
|
||||
"path": "/api/v1/app",
|
||||
"root_path": "/api/v1",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
In this example, the "Proxy" could be something like **Traefik**. And the server would be something like FastAPI CLI with **Uvicorn**, running your FastAPI application.
|
||||
This allows the app to:
|
||||
|
||||
### Providing the `root_path` { #providing-the-root-path }
|
||||
* Match routes correctly (`/app` inside the app).
|
||||
* Generate proper URLs and redirects that include the prefix (`/api/v1/app`).
|
||||
|
||||
To achieve this, you can use the command line option `--root-path` like:
|
||||
This is the elegant mechanism that makes it possible for ASGI applications - including FastAPI - to work smoothly behind reverse proxies or under nested paths without needing to rewrite routes manually.
|
||||
|
||||
### Providing the `root_path`
|
||||
|
||||
So, the ASGI scope needs to contain the correct `path` and `root_path`.
|
||||
But... who is actually responsible for setting them? 🤔
|
||||
|
||||
As you may recall, there are three main components involved in serving your FastAPI application:
|
||||
|
||||
* **Reverse proxy** (like Traefik or Nginx) - receives client requests first and passes them to the ASGI server.
|
||||
* **ASGI server** (like Uvicorn or Hypercorn) - runs your FastAPI application and manages the ASGI lifecycle.
|
||||
* **ASGI app** itself (the app you're building with FastAPI, your favorite framework) - handles routing, generates URLs, and processes requests.
|
||||
|
||||
There are a few common ways these components can work together to ensure `root_path` is set correctly:
|
||||
|
||||
1. The proxy strips the prefix, and the ASGI server (e.g., Uvicorn) adds it back and sets `root_path` in the ASGI `scope`.
|
||||
2. The proxy keeps the prefix and forwards requests as-is, while the ASGI app is started with the correct `root_path` parameter.
|
||||
3. The proxy keeps the prefix but also sends an `X-Forwarded-Prefix` header, and a middleware in the app uses that header to dynamically set `root_path`.
|
||||
|
||||
Let's look at each of these approaches in detail.
|
||||
|
||||
#### Uvicorn `--root-path` (proxy strips prefix) { #uvicorn-root-path-proxy-strips-prefix }
|
||||
|
||||
If your proxy removes the prefix before forwarding requests, you should use the `--root-path` option of your ASGI server:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
|
|
@ -179,23 +292,28 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
|
|||
|
||||
If you use Hypercorn, it also has the option `--root-path`.
|
||||
|
||||
/// note | Technical Details
|
||||
Here's what happens in this setup:
|
||||
|
||||
The ASGI specification defines a `root_path` for this use case.
|
||||
* The proxy sends `/app` to the ASGI server (Uvicorn).
|
||||
* Uvicorn adds the prefix to `path` and sets `root_path` to `/api/v1` in the ASGI scope.
|
||||
* Your FastAPI app receives:
|
||||
|
||||
And the `--root-path` command line option provides that `root_path`.
|
||||
```
|
||||
{
|
||||
"path": "/api/v1/app",
|
||||
"root_path": "/api/v1",
|
||||
}
|
||||
```
|
||||
|
||||
///
|
||||
✅ This is fully compliant with the ASGI specification.
|
||||
|
||||
### Checking the current `root_path` { #checking-the-current-root-path }
|
||||
FastAPI automatically respects `root_path` in the scope when matching routes or generating URLs.
|
||||
|
||||
You can get the current `root_path` used by your application for each request, it is part of the `scope` dictionary (that's part of the ASGI spec).
|
||||
|
||||
Here we are including it in the message just for demonstration purposes.
|
||||
Run the following example app:
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial001.py hl[8] *}
|
||||
|
||||
Then, if you start Uvicorn with:
|
||||
with the command:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
|
|
@ -207,7 +325,7 @@ $ fastapi run main.py --forwarded-allow-ips="*" --root-path /api/v1
|
|||
|
||||
</div>
|
||||
|
||||
The response would be something like:
|
||||
The response will look something like this:
|
||||
|
||||
```JSON
|
||||
{
|
||||
|
|
@ -216,38 +334,187 @@ The response would be something like:
|
|||
}
|
||||
```
|
||||
|
||||
### Setting the `root_path` in the FastAPI app { #setting-the-root-path-in-the-fastapi-app }
|
||||
/// warning | Attention
|
||||
|
||||
Alternatively, if you don't have a way to provide a command line option like `--root-path` or equivalent, you can set the `root_path` parameter when creating your FastAPI app:
|
||||
Don't forget - this setup assumes your proxy **removes** the `/api/v1` prefix before forwarding requests to the app.
|
||||
|
||||
If the proxy **doesn't strip** the prefix, you will end up with duplicated paths like `/api/v1/api/v1/app`.
|
||||
|
||||
And if you run the server **without a proxy at all**, your app will still handle requests to URLs like `/app`,
|
||||
but URL generation, redirects, and the interactive docs will break due to the missing prefix configuration.
|
||||
|
||||
///
|
||||
|
||||
/// tip
|
||||
|
||||
This is the most straightforward and common approach.
|
||||
You should probably use it unless you have a specific reason to do otherwise.
|
||||
|
||||
///
|
||||
|
||||
Unfortunately, not all ASGI servers support a `--root-path` option or automatically adjust the `path` in the ASGI scope.
|
||||
If your server doesn't, you can use one of the alternative approaches described below.
|
||||
|
||||
#### Passing `root_path` as an argument to FastAPI (proxy keeps prefix)
|
||||
|
||||
If the proxy keeps the prefix in the forwarded request:
|
||||
|
||||
```TOML
|
||||
[http]
|
||||
|
||||
[http.routers]
|
||||
|
||||
[http.routers.app-http]
|
||||
entryPoints = ["http"]
|
||||
service = "app"
|
||||
rule = "PathPrefix(`/api/v1`)"
|
||||
|
||||
[http.services]
|
||||
|
||||
[http.services.app]
|
||||
[http.services.app.loadBalancer]
|
||||
[[http.services.app.loadBalancer.servers]]
|
||||
url = "http://127.0.0.1:8000"
|
||||
```
|
||||
|
||||
you can configure FastAPI like this:
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial002.py hl[3] *}
|
||||
|
||||
Passing the `root_path` to `FastAPI` would be the equivalent of passing the `--root-path` command line option to Uvicorn or Hypercorn.
|
||||
In this setup:
|
||||
|
||||
### About `root_path` { #about-root-path }
|
||||
The app receives requests with `/api/v1` included in the path.
|
||||
|
||||
Keep in mind that the server (Uvicorn) won't use that `root_path` for anything else than passing it to the app.
|
||||
FastAPI uses the `root_path` parameter to adjust the ASGI scope so that routing, redirects, and URL generation work correctly.
|
||||
|
||||
But if you go with your browser to <a href="http://127.0.0.1:8000" class="external-link" target="_blank">http://127.0.0.1:8000/app</a> you will see the normal response:
|
||||
Without the `root_path` parameter, the incoming scope from the ASGI server looks like this:
|
||||
|
||||
```JSON
|
||||
```
|
||||
{
|
||||
"message": "Hello World",
|
||||
"root_path": "/api/v1"
|
||||
"path": "/api/v1/app",
|
||||
"root_path": "",
|
||||
}
|
||||
```
|
||||
|
||||
So, it won't expect to be accessed at `http://127.0.0.1:8000/api/v1/app`.
|
||||
This scope is not compliant with the ASGI specification.
|
||||
|
||||
Uvicorn will expect the proxy to access Uvicorn at `http://127.0.0.1:8000/app`, and then it would be the proxy's responsibility to add the extra `/api/v1` prefix on top.
|
||||
But thanks to the `root_path` parameter, FastAPI corrects the scope to:
|
||||
|
||||
## About proxies with a stripped path prefix { #about-proxies-with-a-stripped-path-prefix }
|
||||
```
|
||||
{
|
||||
"path": "/api/v1/app",
|
||||
"root_path": "/api/v1",
|
||||
}
|
||||
```
|
||||
|
||||
Keep in mind that a proxy with stripped path prefix is only one of the ways to configure it.
|
||||
/// warning | Attention
|
||||
|
||||
Probably in many cases the default will be that the proxy doesn't have a stripped path prefix.
|
||||
Don't forget - this setup assumes your app runs behind a proxy that **keeps (adds)** the `/api/v1` prefix when forwarding requests.
|
||||
|
||||
In a case like that (without a stripped path prefix), the proxy would listen on something like `https://myawesomeapp.com`, and then if the browser goes to `https://myawesomeapp.com/api/v1/app` and your server (e.g. Uvicorn) listens on `http://127.0.0.1:8000` the proxy (without a stripped path prefix) would access Uvicorn at the same path: `http://127.0.0.1:8000/api/v1/app`.
|
||||
If the proxy **strips the prefix** or **doesn't add it at all**, your app might seem to work for some routes, but features like interactive docs, mounted sub-applications, and redirects will fail.
|
||||
|
||||
If you encounter strange issues with this configuration, double-check your proxy settings.
|
||||
|
||||
///
|
||||
|
||||
/// note
|
||||
|
||||
Use this approach when your ASGI server doesn't support a `--root-path` option, or if you need to configure your reverse proxy to keep the prefix in the path.
|
||||
|
||||
Otherwise, prefer using the `--root-path` approach [described above](#uvicorn-root-path-proxy-strips-prefix){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
#### Using `X-Forwarded-Prefix` header
|
||||
|
||||
This is another common approach. It's the most flexible, but requires a more initial configuration.
|
||||
|
||||
/// warning
|
||||
|
||||
This is a more advanced approach. In most cases, you should use the `--root-path` option [described above](#uvicorn-root-path-proxy-strips-prefix){.internal-link target=_blank}.
|
||||
|
||||
///
|
||||
|
||||
Imagine the prefix you use to serve the app might change over time. With the `--root-path` approach, you would need to update both the proxy configuration and the ASGI server command each time the prefix changes.
|
||||
|
||||
Or, suppose you want to serve your app under multiple prefixes, such as `/api/v1` and `/backend/v1`. You would then need multiple instances of your app configured with different `--root-path` values - not ideal.
|
||||
|
||||
There is a better solution for this! 💡
|
||||
Configure your reverse proxy to send the mount prefix in a header, e.g.:
|
||||
|
||||
```
|
||||
X-Forwarded-Prefix: /api/v1
|
||||
```
|
||||
|
||||
Here is an example Traefik configuration:
|
||||
|
||||
```TOML
|
||||
[http]
|
||||
|
||||
[http.routers]
|
||||
|
||||
[http.routers.app-api-v1]
|
||||
entryPoints = ["http"]
|
||||
service = "app"
|
||||
rule = "PathPrefix(`/api/v1`)"
|
||||
middlewares = ["prefix-api-v1"]
|
||||
|
||||
[http.routers.app-backend-v1]
|
||||
entryPoints = ["http"]
|
||||
service = "app"
|
||||
rule = "PathPrefix(`/backend/v1`)"
|
||||
middlewares = ["prefix-backend-v1"]
|
||||
|
||||
[http.services]
|
||||
|
||||
[http.services.app.loadBalancer]
|
||||
[[http.services.app.loadBalancer.servers]]
|
||||
url = "http://127.0.0.1:8000"
|
||||
|
||||
[http.middlewares]
|
||||
|
||||
[http.middlewares.prefix-api-v1.headers.customRequestHeaders]
|
||||
X-Forwarded-Prefix = "/api/v1"
|
||||
|
||||
[http.middlewares.prefix-backend-v1.headers.customRequestHeaders]
|
||||
X-Forwarded-Prefix = "/backend/v1"
|
||||
```
|
||||
|
||||
/// note
|
||||
|
||||
`X-Forwarded-Prefix` is not a part of any standard, but it is widely recognized for informing an app of its URL path prefix.
|
||||
|
||||
///
|
||||
|
||||
You can then use middleware to read this header and dynamically set the correct `root_path`:
|
||||
|
||||
{* ../../docs_src/behind_a_proxy/tutorial005.py *}
|
||||
|
||||
This allows a single FastAPI instance to handle requests under multiple prefixes, with `root_path` correctly set for each request.
|
||||
|
||||
Run the server:
|
||||
|
||||
<div class="termy">
|
||||
|
||||
```console
|
||||
$ fastapi run main.py --forwarded-allow-ips="*"
|
||||
|
||||
<span style="color: green;">INFO</span>: Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
Test with `curl`:
|
||||
|
||||
```
|
||||
curl -H "x-forwarded-prefix: /api/v1" http://127.0.0.1:8000/api/v1/app
|
||||
# {"message":"Hello World","root_path":"/api/v1"}
|
||||
|
||||
curl -H "x-forwarded-prefix: /backend/v1" http://127.0.0.1:8000/backend/v1/app
|
||||
# {"message":"Hello World","root_path":"/backend/v1"}
|
||||
```
|
||||
|
||||
The same FastAPI instance now handles requests for multiple prefixes, using the correct `root_path` each time.
|
||||
|
||||
## Testing locally with Traefik { #testing-locally-with-traefik }
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
from fastapi import FastAPI, Request
|
||||
from starlette.types import ASGIApp, Receive, Scope, Send
|
||||
|
||||
|
||||
class ForwardedPrefixMiddleware:
|
||||
def __init__(self, app: ASGIApp):
|
||||
self.app = app
|
||||
|
||||
async def __call__(self, scope: Scope, receive: Receive, send: Send):
|
||||
if scope["type"] in ("http", "websocket"):
|
||||
scope_headers: list[tuple[bytes, bytes]] = scope.get("headers", [])
|
||||
headers = {
|
||||
k.decode("latin-1"): v.decode("latin-1") for k, v in scope_headers
|
||||
}
|
||||
prefix = headers.get("x-forwarded-prefix", "").rstrip("/")
|
||||
if prefix:
|
||||
scope["root_path"] = prefix
|
||||
await self.app(scope, receive, send)
|
||||
|
||||
|
||||
app = FastAPI()
|
||||
app.add_middleware(ForwardedPrefixMiddleware)
|
||||
|
||||
|
||||
@app.get("/app")
|
||||
def read_main(request: Request):
|
||||
return {"message": "Hello World", "root_path": request.scope.get("root_path")}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from docs_src.behind_a_proxy.tutorial005 import app
|
||||
|
||||
client = TestClient(app)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"root_path, requested_path, expected_root_path",
|
||||
[
|
||||
("/api/v1", "/api/v1/app", "/api/v1"),
|
||||
("/backend/v1", "/backend/v1/app", "/backend/v1"),
|
||||
("/backend/v1/", "/backend/v1/app", "/backend/v1"),
|
||||
(None, "/app", ""),
|
||||
],
|
||||
)
|
||||
def test_forwarded_prefix_middleware(
|
||||
root_path: str,
|
||||
requested_path: str,
|
||||
expected_root_path: str,
|
||||
):
|
||||
client = TestClient(app)
|
||||
headers = {}
|
||||
if root_path:
|
||||
headers["x-forwarded-prefix"] = root_path
|
||||
response = client.get(requested_path, headers=headers)
|
||||
assert response.status_code == 200
|
||||
assert response.json()["root_path"] == expected_root_path
|
||||
|
||||
|
||||
def test_openapi_servers():
|
||||
client = TestClient(app)
|
||||
headers = {"x-forwarded-prefix": "/api/v1"}
|
||||
response = client.get("/api/v1/openapi.json", headers=headers)
|
||||
assert response.status_code == 200
|
||||
openapi_data = response.json()
|
||||
assert "servers" in openapi_data
|
||||
assert openapi_data["servers"] == [{"url": "/api/v1"}]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"root_path, requested_path, expected_openapi_url",
|
||||
[
|
||||
("/api/v1", "/api/v1/docs", "/api/v1/openapi.json"),
|
||||
(None, "/docs", "/openapi.json"),
|
||||
],
|
||||
)
|
||||
def test_swagger_docs_openapi_url(
|
||||
root_path: str, requested_path: str, expected_openapi_url: str
|
||||
):
|
||||
client = TestClient(app)
|
||||
headers = {}
|
||||
if root_path:
|
||||
headers["x-forwarded-prefix"] = root_path
|
||||
response = client.get(requested_path, headers=headers)
|
||||
assert response.status_code == 200
|
||||
assert expected_openapi_url in response.text
|
||||
Loading…
Reference in New Issue