[BUG] routePrefix cannot be non-empty for FastAPI to work with ASGI Function on Python v2
Investigative information
Please provide the following:
- Timestamp: 2023-08-28T22:49:33+00:00
- Function App name:
- Function name(s) (as appropriate):
- Core Tools version: 4.0.5274
Repro steps
Provide the steps required to reproduce the problem:
- Start a new Azure Function app with the following command:
func init <> --worker-runtime python --model V2 - In
function_app.py, create a new ASGI Function App with the following code:
import azure.functions as func
import logging
from fastapi import FastAPI
api = FastAPI()
@api.get("/")
def root():
return {"message": "Hello World"}
@api.get("/healthcheck")
def healthcheck():
return 1
app = func.AsgiFunctionApp(
app=api,
http_auth_level=func.AuthLevel.ANONYMOUS
)
- Add
fastapitorequirements.txt(currently on 0.103.0) - Add the following to
host.json:
"extensions": {
"http": {
"routePrefix": "api"
}
}
- Run
func startusing core tools from cli
Expected behavior
Provide a description of the expected behavior.
Should be able to access http://localhost:7071/api with no issue.
Actual behavior
Provide a description of the actual behavior observed.
Following error is received:
❯ func start
Found Python version 3.10.11 (py).
Azure Functions Core Tools
Core Tools Version: 4.0.5274 Commit hash: N/A (64-bit)
Function Runtime Version: 4.23.0.20886
[2023-08-28T23:02:24.316Z] Worker process started and initialized.
[2023-08-28T23:02:24.385Z] A host error has occurred during startup operation 'a87fe4da-9c2c-4dd7-958f-c9b73021ea2c'.
[2023-08-28T23:02:24.387Z] Microsoft.AspNetCore.Routing: An error occurred while creating the route with name 'http_app_func' and template 'api//{*route}'. Microsoft.AspNetCore.Routing: The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value. (Parameter 'routeTemplate'). Microsoft.AspNetCore.Routing: The route template separator character '/' cannot appear consecutively. It must be separated by either a parameter or a literal value.
[2023-08-28T23:02:24.399Z] Failed to stop host instance '4b8b0863-dcb1-4a41-9b70-0beaad496108'.
[2023-08-28T23:02:24.401Z] Microsoft.Azure.WebJobs.Host: The host has not yet started.
Value cannot be null. (Parameter 'provider')
[2023-08-28T23:02:24.409Z] Host startup operation has been canceled
Known workarounds
Provide a description of any known workarounds.
Only workaround is to make the routePrefix in host.json an empty string.
{
"version": "2.0",
"logging": {
"applicationInsights": {
"samplingSettings": {
"isEnabled": true,
"excludedTypes": "Request"
}
}
},
"extensionBundle": {
"id": "Microsoft.Azure.Functions.ExtensionBundle",
"version": "[4.*, 5.0.0)"
},
"extensions": {
"http": {
"routePrefix": ""
}
}
}
Contents of the requirements.txt file:
Provide the requirements.txt file to help us find out module related issues.
azure-functions
fastapi==0.103.0
Related information
Provide any related information
- Links to source
- Bindings used
Source
# __init__.py
import azure.functions as func
import logging
from fastapi import FastAPI
api = FastAPI()
@api.get("/")
def root():
return {"message": "Hello World"}
@api.get("/healthcheck")
def healthcheck():
return 1
app = func.AsgiFunctionApp(
app=api,
http_auth_level=func.AuthLevel.ANONYMOUS
)
azure-functions
fastapi==0.103.0
Can confirm this behaviour. Only works with an empty string for routePrefix.
However, if routePrefix is empty in the host.json, then the GitHub action static-web-apps-deploy@v1 will throw:
Error in processing api build artifacts: the host.json file cannot specify a http.routePrefix value other than 'api'.
Because all endpoints should be defined with api/.
In other words:
- Azure Functions + fastAPI + v2 Python model + Azure Static Web App does currently not work.
- Azure Functions + v2 Python model + Azure Static Web App works.
Not sure this helps anyone but if you deploy the backend as a separate function app, you can set the prefix to an empty string and make everything work.
I think the easiest way to fix this would be to remove the thrown error from the GitHub workflow. As far as I understand it should be a warning anyway. Simply rely on users setting all api paths under /api. All other routes will simply not work.
@pietz Could you share how to remove the thrown error? After removing, will it work after static-web-apps-deploy@v1?
Can this issue get more attention please? Not being able to lift & shift your fastAPI (or any other?) application is such an bummer..
Confirm, Error is still present.
Azure Functions + fastAPI + v2 Python model + Azure Static Web App
Wanted to deploy a demo Angular + FastAPI & GenAI.
Any update on this...?
For the time being I manually patched this by removing the excess "/" in route="/{*route}" in http_app_func's decorator under the AsgiFunctionApp class. It's a terrible solution. I hope this is fixed soon.
@bhagyshricompany Any updates? This ought to be fixed sooner than later.
I just ignore routePrefix in host.json, and use 'route_path' in FastAPI or 'prefix' in APIRouter hope this helps
@eddison-shyi
Could you elaborate on how you 'ignore routePrefix' and what you do in FastAPI using config file / code examples?
@eddison-shyi
Could you elaborate on how you 'ignore routePrefix' and what you do in FastAPI using config file / code examples?
Sure, it's my pleasure~ @jparta
Let's say, we want to have 1 or more path prefix, like /api or something else. To achieve this, keep host.json "extension" with only following:
"extensions": {
"http": {
"routePrefix": ""
}
},
and use APIRouter with argument: prefix (or declare "root_path" when FastAPI init if you need to make it global), instead of using "routePrefix"
from fastapi import APIRouter, FastAPI
app = FastAPI(lifespan=lifespan, title="LaLaLa")
router = APIRouter(prefix="/api", tags=["HmmIMightBeAPI"])
then use "router" in decorator (will get same effect we want), do not forget to include_router
@router.get("/my-identity", name="Show you who I am")
async def show_my_identity(request: Request):
return RedirectResponse(url="https://www.youtube.com", status_code=302)
app.include_router(router)
now, you can access this api through "https://{base}/api/my-identity"
@eddison-shyi
Are you using CI/CD such as github actions or Azure Pipelines? In which way?
The problem is that the deployment task doesn't allow "routePrefix": "", as explained above.
@eddison-shyi
The issue is that "routePrefix" is used for ingress configuration for your App Services. It means you can deploy several App Services under the same hostname, e.g.: your FastAPI backend available under https://<host>/api ("routePrefix": "api"), and second static content server (frontend) under https://<host>/.
My workaround is to monkey patch the AsgiFunctionApp like that:
# asgi_function_app.py
from typing import Union
import azure.functions as func
from azure.functions._abc import Context
from azure.functions._http_asgi import AsgiMiddleware
from azure.functions._http_wsgi import WsgiMiddleware
from azure.functions.decorators.http import HttpMethod
from azure.functions.http import HttpRequest
class AsgiFunctionApp(func.AsgiFunctionApp):
def _add_http_app(
self, http_middleware: Union[AsgiMiddleware, WsgiMiddleware]
) -> None:
"""Add an Asgi app integrated http function.
:param http_middleware: :class:`WsgiMiddleware`
or class:`AsgiMiddleware` instance.
:return: None
"""
if not isinstance(http_middleware, AsgiMiddleware):
raise TypeError("Please pass AsgiMiddleware instance" " as parameter.")
asgi_middleware: AsgiMiddleware = http_middleware
@self.http_type(http_type="asgi")
@self.route(
methods=(method for method in HttpMethod),
auth_level=self.auth_level,
route="{*route}",
)
async def http_app_func(req: HttpRequest, context: Context):
if not self.startup_task_done:
success = await asgi_middleware.notify_startup()
if not success:
raise RuntimeError("ASGI middleware startup failed.")
self.startup_task_done = True
return await asgi_middleware.handle_async(req, context)
The code is copy-pasted from AsgiFunctionApp._add_http_app and I just changed the route="{/*route}" to route="{*route}".
Use this class to create your function app:
# function_app.py
import azure.functions as func
from fastapi import APIRouter, FastAPI
from asgi_function_app import AsgiFunctionApp
fast_api = FastAPI(
docs_url="/api/docs",
openapi_url="/api/openapi.json",
)
api_router = APIRouter(prefix="/api")
@api_router.get("/")
def index():
return "Hello world!"
fast_api.include_router(api_router)
app = AsgiFunctionApp(app=fast_api, http_auth_level=func.AuthLevel.ANONYMOUS)
I'm seeing this same issue with all ASGI Functions Apps (not just FastAPI).
If I don't change routePrefix in host.json, I get the error:
An error occurred while creating the route with name 'http_app_func' and template 'api//{*route}'.
Microsoft.AspNetCore.Routing: The route template separator character '/' cannot appear consecutively.
I don't know where the extra slash is coming from, it seems it has something to do with the route template in the _add_http_app as @gawronA and @seidnerj pointed out. However I think that's just a side-effect.
Note, the workaround to change routePrefix to empty string "" does work, however I see this in the verbose logs:
Functions:
http_app_func: [GET,POST,DELETE,HEAD,PATCH,PUT,OPTIONS] http://localhost:7071//{*route}
Notice the double slash // is still there. This causes problems when integrating the the API into Azure API Manager (APIM), because APIM can query the HTTP triggers from the Function App, it gets these triggers with the double slash and it auto-generates broken APIM routes.
Running into the same issue currently and it's blocking my SWA setup as well (see issue mentioned above).
I'm seeing this same issue with all ASGI Functions Apps (not just FastAPI).
If I don't change
routePrefixinhost.json, I get the error:An error occurred while creating the route with name 'http_app_func' and template 'api//{*route}'.
Microsoft.AspNetCore.Routing: The route template separator character '/' cannot appear consecutively.I don't know where the extra slash is coming from, it seems it has something to do with the route template in the
_add_http_appas @gawronA and @seidnerj pointed out. However I think that's just a side-effect.Note, the workaround to change
routePrefixto empty string""does work, however I see this in the verbose logs:Functions: http_app_func: [GET,POST,DELETE,HEAD,PATCH,PUT,OPTIONS] http://localhost:7071//{*route}
Notice the double slash
//is still there. This causes problems when integrating the the API into Azure API Manager (APIM), because APIM can query the HTTP triggers from the Function App, it gets these triggers with the double slash and it auto-generates broken APIM routes.
Seeing the same issue. Following...
Moved from https://github.com/Azure/azure-functions-python-worker/issues/1614:
I would like to do the following: Azure Functions + fastAPI + v2 Python model + Azure Static Web App
For static webapp to deploy along with an API the host.json needs routePrefix api otherwise static-web-apps-deploy@v1 will throw:
Error in processing api build artifacts: the host.json file cannot specify a http.routePrefix value other than 'api'.
But if you do add api to the host file you'll get the following as FastAPI prepends with a slash.
*An error occurred while creating the route with name 'http_app_func' and template 'api//{route}'.
...so, is there a workaround or are ASGI Functions completely incompatible with SWA?
Kind of blown away that this has been sitting on the shelf since 2023, and is clearly broken. Only solution atm appears to be to deploy your function app separately with prefix set to ""... this is a hack, and is clearly not by design.