Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Access /docs endpoint in FastAPI function apps #1083

Open
tonybaloney opened this issue Aug 9, 2022 · 15 comments
Open

Access /docs endpoint in FastAPI function apps #1083

tonybaloney opened this issue Aug 9, 2022 · 15 comments

Comments

@tonybaloney
Copy link
Contributor

When deploying FastAPI using the ASGI worker,

The /docs endpoint only seems to work for "anonymous" functions. Is there anything I can do to use it with authlevel "function"?

Question asked by @pietz

@hcs-bernardo-rufino
Copy link

Any news on this issue?

@tonybaloney
Copy link
Contributor Author

tonybaloney commented Sep 9, 2022

screenshot 2022-09-09 at 15 35 29

I've written a test app to recreate this. The main issue is that the docs site doesn't forward the code key to the openapi.json request.

There are a few solutions I can think of (the first is the most practical)

  1. Changing the auth to anonymous and using an authentication provider in FastAPI.

https://fastapi.tiangolo.com/tutorial/security/simple-oauth2/

  1. Writing a swagger UI extension to forward the key https://fastapi.tiangolo.com/advanced/extending-openapi/#configuring-swagger-ui

This would only be the path if (1) is not possible.

@maurycyblaszczak-tj
Copy link

maurycyblaszczak-tj commented Sep 22, 2022

Try to allow Cross Origin Requests, see example
here

See also this for documentation on auth levels for functions.

@pietz
Copy link

pietz commented Sep 23, 2022

thank you for your help.

@tonybaloney the auth option in fastapi is not something id want to do because i like the fact that azure is taking care of the authentication. its part of the service id like to use. the second suggested option is not something i was able to figure out.

@maurycyblaszczak-tj this looked most promising and i tried to implement it but failed. are you sure this works with functions? theres a good chance i was just too stupid to succeed.

@maurycyblaszczak-tj
Copy link

maurycyblaszczak-tj commented Sep 23, 2022

Have not tested it but something like this:

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI(openapi_prefix="/api")  # adjust if you host at a different root

app.add_middleware(
    CORSMiddleware,
    allow_origins=['http://localhost:80'],  # protocol/address/port may be wrong, adjust if needed
    allow_methods=['GET'],
    allow_headers=['x-functions-key'],
 )

In order to work, this requires x-functions-key with the function key value to be send when you GET /docs.

@pietz
Copy link

pietz commented Sep 26, 2022

@maurycyblaszczak-tj Thanks again but setting the x-functions-key in the header instead of setting code in the url query is not really an option given that I want to open the /docs route in the browser.

I also found this thread but I fail to apply the changes to my Azure Function use case.

@pietz
Copy link

pietz commented Sep 26, 2022

I found a workaround that at least brings me to the docs page. however, trying out the API doesn't work this way for obvious reasons.

from fastapi.openapi.docs import get_swagger_ui_html

app = FastAPI(docs_url=None)

@app.get("/docs", include_in_schema=False)
async def get_docs(code: str):
    openapi_url = "/openapi.json?code=" + code
    return get_swagger_ui_html(openapi_url=openapi_url, title="docs")

@Thyholt
Copy link

Thyholt commented Oct 14, 2022

Thanks @pietz. Was able to make "try it out "work by including the code as a query argument in all the other endpoints as well. Didn't put effort into reducing the repeated and unused code query argument, but it works. And the documentation tells the user to provide it. Example:

@app.get("/")
def root(code: str = Query(..., description="Azure Function App key")):
    return {"message": "Hello World"}

@pamelafox
Copy link
Member

Hm, if the "try it out" isn't working, could the optional servers argument be useful here?

Discussion of it in the FastAPI docs:
https://fastapi.tiangolo.com/advanced/behind-a-proxy/#additional-servers

@pamelafox
Copy link
Member

FYI, I've created a sample that puts an Azure API Management Policy in front of an authenticated Azure Function, passing along the function-key as part of the policy. I've also set it up such that the API calls require a subscription-key but the docs do not (by creating two "APIs" in APIM with two different policies).

Sample here:
https://github.com/pamelafox/fastapi-azure-function-apim

I know this issue is about doing it entirely within FastAPI, but I think that does require more digging into FastAPI internals as you all have suggested, so I like this approach, plus APIM has some really neat policies you can add on top of the function (rate-limiting, e.g.)

@pamelafox
Copy link
Member

Update: I also wrote a blog post describing my approach from that sample:
http://blog.pamelafox.org/2022/11/fastapi-on-azure-functions-with-azure.html

@decadance-dance
Copy link

decadance-dance commented Jul 21, 2023

I recently faced a similar problem. I use V2 model.
I have already tried all the tips from this thread, but none of them helped. I just can't find the fast api application routes.

My app:

import logging
import httpx
from httpx import AsyncClient
from fastapi import FastAPI, Request, UploadFile, status
from fastapi.responses import JSONResponse, StreamingResponse
from fastapi.middleware.cors import CORSMiddleware
from starlette.background import BackgroundTask
from config import get_config


config = get_config()
log = logging.getLogger(__name__)

HTTP_SERVER = AsyncClient(base_url=config.api_url)

api = FastAPI(
    servers=[{"url": "/api", "description": "API"}],
    root_path="/public",
    root_path_in_servers=False,
)

api.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"]
)

async def _reverse_proxy(request: Request):
    url = httpx.URL(path=request.url.path, query=request.url.query.encode("utf-8"))
    rp_req = HTTP_SERVER.build_request(
        request.method, url, headers=request.headers.raw, content=await request.body(),
        timeout=None
    )
    rp_resp = await HTTP_SERVER.send(rp_req, stream=True)
    return StreamingResponse(
        rp_resp.aiter_raw(),
        status_code=rp_resp.status_code,
        headers=rp_resp.headers,
        background=BackgroundTask(rp_resp.aclose),
    )

api.add_route("/db", _reverse_proxy, ["GET"])

@api.get("/db")
def get_info():
    ...

host.json

{
  "version": "2.0",
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "excludedTypes": "Request"
      }
    }
  },
  "extensionBundle": {
    "id": "Microsoft.Azure.Functions.ExtensionBundle",
    "version": "[3.3.0, 3.9.0)"
  },
  "extensions": {
    "http": {
        "routePrefix": ""
    }
  }
}

I see it
image

but every time I try to call I get "Not Found" for any route.
What URL should I use to reach the function?

@pamelafox
Copy link
Member

Is it the JSON "Not Found" response? That sounds like FastAPI's default response for an undefined route. Typically I get that if I hit up the root of my app and I haven't defined a "/" route. Are you also trying /db, /openapi.json, /docs, etc?

@decadance-dance
Copy link

@pamelafox I am getting a 404 code when I try to call any route.

@warreee
Copy link

warreee commented Nov 26, 2023

I found a workaround that at least brings me to the docs page. however, trying out the API doesn't work this way for obvious reasons.

from fastapi.openapi.docs import get_swagger_ui_html

app = FastAPI(docs_url=None)

@app.get("/docs", include_in_schema=False)
async def get_docs(code: str):
    openapi_url = "/openapi.json?code=" + code
    return get_swagger_ui_html(openapi_url=openapi_url, title="docs")

I added this suggestion from @pietz and then I use something like modheader to do the api calls at the docs page, this way I don't need to add the code parameter to every call as in @Thyholt suggestion.

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

No branches or pull requests

9 participants