azure-functions-python-worker icon indicating copy to clipboard operation
azure-functions-python-worker copied to clipboard

[BUG] routePrefix cannot be non-empty for FastAPI to work with ASGI Function on Python v2

Open lac-anakagawa opened this issue 2 years ago • 19 comments

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:
  1. Start a new Azure Function app with the following command: func init <> --worker-runtime python --model V2
  2. 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
)
  1. Add fastapi to requirements.txt (currently on 0.103.0)
  2. Add the following to host.json:
"extensions": {
    "http": {
      "routePrefix": "api"
    }
  }
  1. Run func start using 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

lac-anakagawa avatar Aug 28 '23 23:08 lac-anakagawa

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.

johschmidt42 avatar Sep 11 '23 12:09 johschmidt42

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 avatar Oct 23 '23 19:10 pietz

@pietz Could you share how to remove the thrown error? After removing, will it work after static-web-apps-deploy@v1?

Edward-Zhou avatar Oct 31 '23 00:10 Edward-Zhou

Can this issue get more attention please? Not being able to lift & shift your fastAPI (or any other?) application is such an bummer..

johschmidt42 avatar Feb 01 '24 14:02 johschmidt42

Confirm, Error is still present.

Azure Functions + fastAPI + v2 Python model + Azure Static Web App

Wanted to deploy a demo Angular + FastAPI & GenAI.

MaxOSchulte avatar Mar 22 '24 21:03 MaxOSchulte

Any update on this...?

seidnerj avatar May 01 '24 00:05 seidnerj

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.

seidnerj avatar May 02 '24 16:05 seidnerj

@bhagyshricompany Any updates? This ought to be fixed sooner than later.

jparta avatar Jul 02 '24 14:07 jparta

I just ignore routePrefix in host.json, and use 'route_path' in FastAPI or 'prefix' in APIRouter hope this helps

eddison-shyi avatar Jul 26 '24 10:07 eddison-shyi

@eddison-shyi

Could you elaborate on how you 'ignore routePrefix' and what you do in FastAPI using config file / code examples?

jparta avatar Aug 11 '24 17:08 jparta

@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 avatar Aug 13 '24 03:08 eddison-shyi

@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.

jparta avatar Aug 13 '24 07:08 jparta

@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)

gawronA avatar Aug 13 '24 08:08 gawronA

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.

ashleysommer avatar Aug 22 '24 07:08 ashleysommer

Running into the same issue currently and it's blocking my SWA setup as well (see issue mentioned above).

davidzenisu avatar Dec 11 '24 12:12 davidzenisu

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.

Seeing the same issue. Following...

kennyishihara avatar Dec 23 '24 02:12 kennyishihara

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}'.

hallvictoria avatar Jan 07 '25 17:01 hallvictoria

...so, is there a workaround or are ASGI Functions completely incompatible with SWA?

cedro-gasque avatar May 20 '25 20:05 cedro-gasque

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.

barrcodes avatar Sep 24 '25 13:09 barrcodes