Also raised on https://github.com/tiangolo/fastapi/pull/26#issuecomment-562755647. See also #544.
Write here a clear and concise description of what the bug is.
Replace each part with your own scenario:
from fastapi import FastAPI
app = FastAPI()
@app.get("/app")
def read_root():
return {"Hello": "World"}
uvicorn --root-path="bar" test_app:apphttp://127.0.0.1:8000/docs.GET /app route./app and succeeds.The above test should fail after having called /bar/app, since root_path is supposed to prefix all generated URLs in case the application is served behind a reverse-proxy, among ther things. FastAPI only acknowledges openapi_prefix for the API doc.
A similar issue applies to sub-applications:
from fastapi import FastAPI
app = FastAPI()
@app.get("/app")
def read_main():
return {"message": "Hello World from main app"}
#subapi = FastAPI(openapi_prefix="/subapi")
subapi = FastAPI()
@subapi.get("/sub")
def read_sub(request: Request):
return {
"root_path": request.scope['root_path'],
"raw_path": request.scope['raw_path'],
"path": request.scope['path'],
"app_url_for": app.url_path_for("read_sub"),
"subapp_url_for": subapi.url_path_for("read_sub"),
}
app.mount("/subapi", subapi)
{
"root_path":"bar/subapi",
"raw_path":"/subapi/sub",
"path":"/sub",
"app_url_for":"/subapi/sub",
"subapp_url_for":"/sub"
}
(url_for not being prefixed with root_path is fixed upstream by encode/starlette#699)
Unless openapi_prefix="/subapi" is passed when creating the subapplication, both http://127.0.0.1:8000/docs and http://127.0.0.1:8000/subapi/docs will point towards http://127.0.0.1:8000/openapi.json, which goes against the point of having isolated subapplications.
openapi_prefix should probably just be deprecated and assumed to match root_path if absent.
Faced it when I was using uvicorn-gunicorn-fastapi-docker which was configured as nginx sub app
Example config:
location /fastapi/ {
proxy_pass http://0.0.0.0:8088/;
proxy_set_header Accept-Encoding "";
sub_filter "http://0.0.0.0:8088/" "http://0.0.0.0:8088/fastapi/";
sub_filter_once off;
}
I feel that this is closely related to a question I was about to write, so I will post it here instead to avoid duplication.
--
Hi,
I'm deploying a minimal application written with FastAPI and Mangum as an adapter using AWS SAM. It works like a charm, and I'm in the process off writing an article about it to append in the fastapi documentation.
Yet one thing that boggles my mind is the path of the /openapi.json and I can't wrap my head around how to work it in different situations.
So let's assume I have a little application like this:
from fastapi import FastAPI
from example_app.api.api_v1.api import router as api_router
from example_app.core.config import API_V1_STR, PROJECT_NAME
from mangum import Mangum
app = FastAPI(
title=PROJECT_NAME,
# if not custom domain
# openapi_prefix="/prod"
)
app.include_router(api_router, prefix=API_V1_STR)
@app.get("/ping")
def pong():
return {"ping": "pong!"}
handler = Mangum(app, enable_lifespan=False, )
I deploy it to API Gateway/Lambda to a stage called prod so the resulting url is https://xxxxxxxxxx.execute-api.eu-west-1.amazonaws.com/prod. The API Gateway is set up with {proxy+} integration.
Now how is it, that the /ping endpoint "knows" that the base-url is [...].amazonaws.com/prod, yet openapi.json assumes to be at [...].amazonaws.com/openapi.json?
I know I can change the prefix with openapi_prefix="/prod" but that makes it inconvenient if I wanted to use another stage than prod. After all I don't have to do it for my other endpoints either. So is there a reason it doesn't work the same way as with my other endpoints? Is it a bug, or am I just missing something very obvious?
Running into the same issue as @iwpnd - I am unable to host OpenAPI docs via Lambda because the openapi.json path isn't respecting the base URL prefix.
Running into the same issue as @iwpnd - I am unable to host OpenAPI docs via Lambda because the
openapi.jsonpath isn't respecting the base URL prefix.
you can use it like that
app = FastAPI(
title=PROJECT_NAME,
# if not custom domain
openapi_prefix="/your_stage"
)
but yeah, it's not ideal.
@iwpnd Hah nice response time.. I just dove through the source and started seeing that but haven't tested it yet. Thanks for verifying that
Ah, so I think my issue is that I'm combining FastAPI with Mangum, with the latter's api_gateway_base_path param set to strip away the "v2" prefix fronting all of my FastAPI routes.
As far as my app is concerned, there is no "v2" prefix (because it's a sub-app and I felt that it shouldn't have knowledge of where it lives) but this breaks the "openapi.json" URL link in the HTML docs pages because it doesn't know to append "v2/" to the URL.
Neither the openapi_prefix or openapi_url params for the FastAPI class can fix this, because I need the app to respond to /openapi.json while including the prefix ONLY in the generated HTML. The openapi_prefix is for some reason adding the prefix twice, so I wind up getting "/v2/v2/openapi.json"
same here with AWS Lambda and API Gateway, my standard base path is api.mycompany.com and I have /my_stage (staging / production)
when going to api.mycompany.com/staging/docs it doesn't work
how should we do ?
Adding /staging in openurl_prefix doesn't fix this unfortunately
As raised in #1294, another possibility could be to have the servers part of the OpenAPI document contain a single server with the root_path as its url (no matter how deeply the application happens to be nested), so that the rest of the routing and OpenAPI route generation logic can remain the same, while still letting OpenAPI clients (including the doc) know that all API calls should be prefixed.
openapi_prefix could then be deprecated as an alternate way of specifying the same thing, except passed directly at the application's creation rather than as defined by the ASGI protocol, and unaccounted for when using url_for or url_path_for.
Therefore, using the example app from the initial post:
from fastapi import FastAPI, Request
app = FastAPI()
@app.get("/app")
def read_main():
return {"message": "Hello World from main app"}
#subapi = FastAPI(openapi_prefix="/subapi")
subapi = FastAPI()
@subapi.get("/sub")
def read_sub(request: Request):
return {
"root_path": request.scope['root_path'],
"raw_path": request.scope['raw_path'],
"path": request.scope['path'],
"app_url_for": app.url_path_for("read_sub"),
"subapp_url_for": subapi.url_path_for("read_sub"),
}
app.mount("/subapi", subapi)
We would get:
uvicorn test_submounts:app --root-path="/foo/"
/openapi.json
{
"openapi": "3.0.2",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"servers": [
{
"url": "/foo/"
}
],
"paths": {
"/app": {
"get": {
"summary": "Read Main",
"operationId": "read_main_app_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
}
}
}
/subapi/openapi.json
{
"openapi": "3.0.2",
"info": {
"title": "FastAPI",
"version": "0.1.0"
},
"paths": {
"/sub": {
"get": {
"summary": "Read Sub",
"operationId": "read_sub_sub_get",
"responses": {
"200": {
"description": "Successful Response",
"content": {
"application/json": {
"schema": {}
}
}
}
}
}
}
},
"servers": [
{
"url": "/foo/subapi"
}
]
}
Which does exactly what we need in terms of ensuring the doc acknowledges the reverse proxy, and everything else just falls into place.
Hey all, thanks for the discussion here! :cake:
This was fixed in https://github.com/tiangolo/fastapi/pull/1199 implementing support for root_path :rocket: :tada:
Available in FastAPI 0.56.0.
You can read the new docs at Sub Applications - Mounts, which now :sparkles: just works :sparkles: without any extra parameters.
And the new extensive docs at Behind a Proxy that explain it all in detail, including how to test it all using a local Traefik proxy on top of Uvicorn, to test all the root_path workflow (with the parameter --root-path).
This took me a bit of time to handle as I wanted to first re-read the WSGI and ASGI specs to make sure I wasn't missing anything.
And then to test it all with an actual reverse proxy with path prefix stripping (what the root_path would be used for).
@tiangolo: What's your opinion about prepending the root_path/mount path to every route in the openapi doc (what we're doing now) compared to declaring the root as a server object so the endpoint paths don't change if the server runs with a different root_path (what I suggested above)?
Closing this, now that #1596 has been merged. I much prefer this new approach compared to the other one, since it seems more in line with how the OpenAPI specs intends its definitons to look (the routes known to Starlette actually match what's in openapi.yml).
I'm a little confused. if i'm not behind any proxy, i just want all routes to be prefixed with /api/v1 (e.g. /api/v1/user), root_path option doesn't work, i'm getting the "Not Found /api/v1/openapi.json" error when i go to /docs. So do I need to manually add "/api/v1" to every @app.get entry?
@andreixk: No, root_path is only useful for the application to know what to prefix URL when generating URLs for its own routes, so it only makes sense to use it if you have a proxy that's making it so what your application sees as localhost:8000/hello is actually exposed to the outside world as 192,168,1,123:80/api/v1/hello by a proxy. Without root_path, the application would generate route URLs that look like /hello in the documentation, and clients would understand it as meaning 192,168,1,123:80/hello, which doesn't exist.
If you just want a route mounted at /api/v1/, you probably want to have all of your routes defined on an APIRouter instead of your app directly, and then mount that router on your app at /api/v1, like documented here.
@andreixk when you mount an application it basically looks into the route.path for route in app.routes, by default your root_path is / and app.routes looks like this
[
{'path': '/openapi.json'},
{'path': '/docs'},
{'path': '/docs/oauth2-redirect'},
{'path': '/redoc'},
]
When you mount another application you are just adding that route to the app.routes, for example, let us mount /subapi to our /app
Now it will look like this
[
{'path': '/openapi.json'},
{'path': '/docs'},
{'path': '/docs/oauth2-redirect'},
{'path': '/redoc'},
{'path': '/subapi'},
]
You are just making /subapi accessible from your root_path so when you go to / you can access /subapi since that path is accessible from the root_path
When you go to /subapi it basically looks into the subapi.routes so imagine if you have an endpoint called /dummy and it is declared inside of subapi.routes, you can access it because when you go to /subapi it becomes your route path and after when you go to an endpoint from /subapi/dummy you can access it because it is declared inside subapi.routes
So if you are not behind the proxy you can use prefix which simply does the same thing underneath for example, when you are including a router
app.include_router(api, prefix="/api")
It just adds a prefix when adding the paths that declared inside APIRouter instance, is it still confusing? Let's keep going, imagine you have an instance of APIRouter and you want to include that router to your app
from fastapi import APIRouter
api = APIRouter()
@app.get("/dummy")
...
@app.post("/dummier")
...
@app.delete("/dummiest")
...
From the main application when you are including it
from fastapi import FastAPI
from somewhere import api
app = FastAPI()
app.include_router(api, prefix="/api")
This only adds a prefix when adding paths to the app.routes
So in your case adding a prefix should be enough when including your router.
If you are still getting Not found .... just look into your app.routes like I did above. You will probably find your openapi.json is not prefixed and your other paths are prefixed or the opposite not sure.
@sm-Fifteen @ycd Thanks guys!
That's pretty much what I ended up doing.
I just wanna say the response time is amazing! I was expecting to wait 5-10 days :)
Most helpful comment
I feel that this is closely related to a question I was about to write, so I will post it here instead to avoid duplication.
--
Hi,
I'm deploying a minimal application written with FastAPI and Mangum as an adapter using AWS SAM. It works like a charm, and I'm in the process off writing an article about it to append in the fastapi documentation.
Yet one thing that boggles my mind is the path of the /openapi.json and I can't wrap my head around how to work it in different situations.
So let's assume I have a little application like this:
I deploy it to API Gateway/Lambda to a stage called
prodso the resulting url ishttps://xxxxxxxxxx.execute-api.eu-west-1.amazonaws.com/prod. The API Gateway is set up with{proxy+}integration.Now how is it, that the
/pingendpoint "knows" that the base-url is [...].amazonaws.com/prod, yetopenapi.jsonassumes to be at [...].amazonaws.com/openapi.json?I know I can change the prefix with
openapi_prefix="/prod"but that makes it inconvenient if I wanted to use another stage thanprod. After all I don't have to do it for my other endpoints either. So is there a reason it doesn't work the same way as with my other endpoints? Is it a bug, or am I just missing something very obvious?