首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >在中间件中调用call_next (路径操作函数)时FastAPI/Unvicorn请求挂起

在中间件中调用call_next (路径操作函数)时FastAPI/Unvicorn请求挂起
EN

Stack Overflow用户
提问于 2021-11-20 06:08:47
回答 2查看 2.6K关注 0票数 0

我们在EC2上运行的码头容器中有一个机器学习模型。

我们使用Cortex.dev来实现自动刻度GPU。

不确定的是,在call_next中间件中,请求将在FastAPI函数期间挂起。不幸的是,这是不可复制的。

Middleware pre-request打印行将被记录,但是路径操作函数中的第一个print语句永远不会被记录。

我们尝试过的事情:

  • 与1名工人一起运行Uvicorn
  • run函数不带异步地运行
  • bytes作为image而不是UploadFile的参数类型运行

这些更改都不能解决挂起的问题,但这是最具表现力的配置。

  1. 这是否意味着问题在于FastAPI而不是Uvicorn?
  2. 如果是,什么会导致FastAPI挂起?如果没有,问题在哪里,怎样才能解决?

Dockerfile

代码语言:javascript
运行
复制
FROM nvidia/cuda:11.4.0-runtime-ubuntu18.04

WORKDIR /usr/src/app

RUN apt-get -y update && \
    apt-get install -y --fix-missing \
    build-essential \
    cmake \
    python3 \
    python3-pip \
    ffmpeg \
    libsm6 \
    libxext6 \
    && apt-get clean && rm -rf /tmp/* /var/tmp/*

ADD ./requirements.txt ./

# install our dependencies
RUN python3 -m pip install --upgrade pip && python3 -m pip install -r requirements.txt && apt-get clean && rm -rf /tmp/* /var/tmp/*

ADD ./ ./

ENV LC_ALL=C.UTF-8 
ENV LANG=C.UTF-8

EXPOSE 8080

CMD uvicorn api:app --host 0.0.0.0 --port 8080 --workers 2

api.py

代码语言:javascript
运行
复制
from my_predictor import PythonPredictor
from typing import Optional
from datetime import datetime
import time
from starlette.responses import Response

from fastapi import FastAPI, File, UploadFile, Form, Response, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = ["*"]

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


@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    cortex_id = request.headers.get('x-request-id')
    start_time = time.time()
    print("Cortex ID: " + cortex_id + ". > Middleware pre-request. Time stamp: " + str(start_time), flush=True)

    response = await call_next(request)

    process_time = time.time() - start_time
    print("Cortex ID: " + cortex_id + ". > Middleware post-response. Duration: " + str(process_time), flush=True)

    return response



@app.post("/")
async def run(request: Request, image: UploadFile = File(...), renderFactor:Optional[int] = Form(12), requestId:Optional[str] = Form('-1'),include_header:Optional[str] = Form('bin')):

    try:
        cortexId = request.headers.get('x-request-id')
        print("Cortex ID: " + cortexId + ". Request ID: " + requestId + " >>> Request received. Time stamp: " + str(datetime.now()))

        start = time.time()
    
        image = await image.read()

        payload = {}
        payload['image'] = image
        payload['renderFactor'] = renderFactor
        payload['requestId'] = requestId
        payload['include_header'] = include_header
        
        response = pred.predict(payload)

        end = time.time()

        totalTime = round(end - start, 2)

        print("Cortex ID: " + cortexId + ". Request ID: " + requestId + " > Request processed. Duration: " + str(totalTime) + " seconds. Time stamp: " + str(datetime.now()))

        if totalTime > 5:
            print("Long request detected. Duration: " + str(totalTime))

        return response
        
    except Exception as error:
        end = time.time()
        print(str(error))
        print("Cortex ID: " + cortexId + ". Request ID: " + requestId + " > Error. Duration: " + str(round(end - start, 2)) + " seconds . Time stamp: " + str(datetime.now()))

        raise HTTPException(status_code = 500, detail = str(error))

config = {}
pred = PythonPredictor(config)
EN

回答 2

Stack Overflow用户

发布于 2022-04-09 20:05:02

基本信息、根本原因与咆哮

嘿,我花了相当多的时间在这个悬而未决的问题上(在我的组织中有多个自定义MDW的关键应用程序)。这基本上是因为基于@app.middleware("http")的中间件是从Starlette的BaseHTTPMiddleware继承而来的后端产品。因此,通过显式继承BaseHTTPMiddleware编写的MDW也存在这个问题。原因是相当复杂的,这也是我迄今所理解的:

  1. 来自这里(GitHub Starlette杂志)在这里(Github问题):我了解到这个方法使用的是StreamingResponse,这有一些问题
  2. 这里(GitHub Starlette杂志):我了解到挂起的原因之一是:在API中的请求生命周期中只允许等待request.json()一次,而BaseHTTPMiddleware也会自己创建一个请求对象(这会导致挂起问题,因为这是另一个请求)。

最后一个链接还提到,也会导致挂起问题的原因是,由于StreamingResponse;响应的读取在第一次读取时不知怎么地被耗尽了,当涉及到第二次读取时,它一直在无限期地等待它,从而导致挂起。(这里第一次和第二次阅读意味着:在ASGI应用程序中,消息通过各种类型的客户端和应用程序(如http.response.starthttp.response.body等)发送。)

解决方案

所以,不要使用任何与BaseHTTPMiddleware有关的东西。为了解决这一问题,我使用这里提供的ASGI规范编写了我的所有定制中间件

您可以使您的自定义中间件如下所示:

代码语言:javascript
运行
复制
from starlette.types import ASGIApp, Receive, Send, Message

class LogProcessingTime:

    def __init__(self, app: ASGIApp) -> None:
        self.app = app
    
    async def __call__(self, scope: Scope, receive: Receive, send: Send):
        
        start_time = time.time()

        async def send_wrapper(message: Message):
            # This will capture response coming from APP Layer
            # response body will be in message where the type is
            # "http.response.body"
            if message["type"] == "http.response.body":
                process_time = time.time() - start_time
                # you can log this process_time now any way you prefer

            await send(message)
        
        await self.app(scope, receive, send_wrapper)


# you can add this to your app this way:
app.add_middleware(LogProcessingTime)
票数 6
EN

Stack Overflow用户

发布于 2021-11-28 06:51:36

解决方案1

从您的代码中,我可以看到您使用了来自starlette.responses包和FastAPI的响应包的响应,这可能导致了挂起问题。

代码语言:javascript
运行
复制
from starlette.responses import Response

# remove the Response from fastapi
from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Request

解决方案2

如果你的问题仍然存在

FastAPI文档声明从fastapi包导入的Request最好是从Starlette(Starlette请求文档链接)中导入

您也可以使用from starlette.requests import RequestFastAPI为开发人员提供了方便。但它直接来自Starlette

from starlette.requests import Request替换from fastapi import Request

在官方的uvicorn <file>:app. github问题中也有类似的问题,这个应用程序是用FastAPI运行的下面直接用starlette.requests实现的代码块并没有产生挂起的问题,这表明这个问题是FastAPI造成的。

代码语言:javascript
运行
复制
from starlette.applications import Starlette
from starlette.requests import Request
from starlette.responses import JSONResponse

app = Starlette()


@app.middleware("http")
async def func(request: Request, call_next):
    #print(await request.json())
    return await call_next(request)


@app.route('/', methods=["POST"])
def homepage(request):
    return JSONResponse({"Hello": "World"})

确保在代码中使用starlette.requestsstarlette.responses,如下所示

代码语言:javascript
运行
复制
from starlette.responses import Response
from starlette.requests import Request

# Request and Response removed from fastapi as directly referred from starlette
from fastapi import FastAPI, File, UploadFile, Form, HTTPException 
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = ["*"]

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


@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    cortex_id = request.headers.get('x-request-id')
    start_time = time.time()
    print("Cortex ID: " + cortex_id + ". > Middleware pre-request. Time stamp: " + str(start_time), flush=True)

    response = await call_next(request)

    process_time = time.time() - start_time
    print("Cortex ID: " + cortex_id + ". > Middleware post-response. Duration: " + str(process_time), flush=True)

    return response
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/70043665

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档