1

我正在使用 FastAPI+Sqlalchemy+Mysql 构建应用程序,当我进行压力测试时,出现 Asgi 异常错误:

[ERROR] Exception in ASGI application, 
RecursionError: maximum recursion depth exceeded while calling a Python object.

我绝对可以拥有:

  1. 我没有调用任何递归函数
  2. 我已经尝试过 alchemy 和 mysql_connector 来查询数据库,但错误仍然存​​在。

代码在这里:

我的 main.py 如下

def create_app()->FastAPI:
    app = FastAPI(debug = False)

    register_cors(app)

    app.include_router(admin_router)

    register_exception(app)
   
    register_token_validate(app)

    return app


***def register_cors(app:FastAPI):
    @app.middleware("http")
    async def cors(request:Request, call_next):
        app.add_middleware(
            CORSMiddleware,
            allow_origins=["*"],
            allow_credentials=True,
            allow_methods=["*"],
            allow_headers=["*"],      
        )
        response = await call_next(request)
        response.headers["access-control-allow-credentials"] = "true"
        response.headers["access-control-allow-origin"] = "*"
        response.headers["access-control-allow-methods"] = "*"
        return response***

def register_exception(app:FastAPI):

    @app.exception_handler(RequestValidationError)
    async def validation_exception_handler(request: Request, exc: RequestValidationError):
        errType = (exc.errors())[0]["type"]
        detail = (exc.errors())[0]
        type = errType.split(".")
        if type[0] == "value_error":
            return JSONResponse(
                status_code=422,
                content={"code": 422,"msg": "value error", "errinfo_type":detail}
            )
        elif type[0] == "type_error":
            return JSONResponse(
                status_code=422,
                content={"code": 422,"msg": "type error", "errinfo_type":detail}
            )
        else:          
            return JSONResponse(
                status_code=422,
                content={"code": 422,"msg": "server error", "errinfo_type":detail}
            )


    @app.exception_handler(Exception)
    async def all_exception_handler(request: Request, exc: Exception):
        return JSONResponse(
            status_code=500,
            content={"code": 500,"msg": "server error", "errinfo_type":None}
        )

def register_token_validate(app: FastAPI):
    @app.middleware("http")
    async def token_validate(request: Request, call_next):
        excludeUrlL = [
            "/v1/login",
            "/docs",
            "/redoc"
        ]

        if (request.url.path not in excludeUrlL) and (request.method != "OPTIONS"): 
            res = security.validate_token(request.headers.get("token"), request.headers.get("request_time"))   
            if res['status'] == "success": 
                response = await call_next(request)
                response.headers["access-control-allow-credentials"] = "true"
                response.headers["access-control-allow-origin"] = "*"
                response.headers["access-control-allow-methods"] = "*"
                return response
            elif res['status'] == "failed": 
                return JSONResponse(
                    status_code=401,
                    content={"code": 401,"msg": "token expired"}
                ) 
            elif res['status'] == "updated": 
                response = await call_next(request)
                response.headers["token"] = res['payload']
                response.headers["access-control-allow-credentials"] = "true"
                response.headers["access-control-allow-origin"] = "*"
                response.headers["access-control-expose-headers"] = "token"
                response.headers["access-control-allow-methods"] = "*"
                return response
        else:
            response = await call_next(request)
            response.headers["access-control-allow-credentials"] = "true"
            response.headers["access-control-allow-origin"] = "*"
            response.headers["access-control-allow-methods"] = "*"
            return response

app = create_app()

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app="main:app", host="0.0.0.0", port=8000, reload=True, debug=False)

安全.py


#!/usr/bin/env python
# -*- coding: utf-8 -*-

from datetime import datetime, timedelta, tzinfo
from db.DbSession import session
from sqlalchemy import text

from config import config
from core.logger import logger
from core import util
import pytz


def validate_token(token, request_time):
    if not token:
        return {"status":"failed", "payload":None, "msg": "token is required"}

    if not request_time:
        return {"status":"failed", "payload":None, "msg": "request_time is required"}

    sql = "select id, mobile, token, expired_time,  last_request_time from my_user_table where token = :token limit 1"
    rp = session.execute(text(sql), {"token":token})
    res = [dict(r.items()) for r in rp]

    if not res:
        return {"status":"failed", "payload":None, "msg": "token error"}

    nowStamp =  datetime.now().timestamp()
    expired_timeStamp = res[0]['expired_time'].timestamp()

    if nowStamp > expired_timeStamp:
        return {"status":"failed", "payload":None, "msg": "token is expired"}

    if (int(request_time) - int(res[0]['last_request_time'])) > 600:
        return {"status":"updated", "payload":token, "msg": "token is updated"}
    else:
        return {"status":"success", "payload":None, "msg": "token is valid"}


会话.py

from sqlalchemy import create_engine
from sqlalchemy.orm import Session
from sqlalchemy.pool import NullPool

engine = create_engine(config.DbUrl, echo = False, poolclass=NullPool)
session = Session(engine)

api.py

@router.get("/v1/getCompany", summary="get company information")
async def getCompany(id:int):
    sql = "select * from my_company_table c where c.id = :id"    
    rp = session.execute(text(sql), {"id":id})
    res = [dict(r.items()) for r in rp]   
    session.commit() 
    return util.resp_200(200, 'get company information success', res)

/v1/getCompany当我连续调用这个接口时,我得到了上面的错误

[ERROR] Exception in ASGI application
Traceback (most recent call last):
  File "/root/.cache/pypoetry/virtualenvs/facli-zsB1kvQc-py3.8/lib/python3.8/site-packages/uvicorn/protocols/http/h11_impl.py", line 388, in run_asgi
    result = await app(self.scope, self.receive, self.send)
  File "/root/.cache/pypoetry/virtualenvs/facli-zsB1kvQc-py3.8/lib/python3.8/site-packages/uvicorn/middleware/proxy_headers.py", line 45, in __call__
    return await self.app(scope, receive, send)
  File "/root/.cache/pypoetry/virtualenvs/facli-zsB1kvQc-py3.8/lib/python3.8/site-packages/fastapi/applications.py", line 179, in __call__
    await super().__call__(scope, receive, send)
  File "/root/.cache/pypoetry/virtualenvs/facli-zsB1kvQc-py3.8/lib/python3.8/site-packages/starlette/applications.py", line 111, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/root/.cache/pypoetry/virtualenvs/facli-zsB1kvQc-py3.8/lib/python3.8/site-packages/starlette/middleware/errors.py", line 181, in __call__
    raise exc from None
  File "/root/.cache/pypoetry/virtualenvs/facli-zsB1kvQc-py3.8/lib/python3.8/site-packages/starlette/middleware/errors.py", line 159, in __call__
    await self.app(scope, receive, _send)
  File "/root/.cache/pypoetry/virtualenvs/facli-zsB1kvQc-py3.8/lib/python3.8/site-packages/starlette/middleware/cors.py", line 86, in __call__
    await self.simple_response(scope, receive, send, request_headers=headers)
  File "/root/.cache/pypoetry/virtualenvs/facli-zsB1kvQc-py3.8/lib/python3.8/site-packages/starlette/middleware/cors.py", line 142, in simple_response
    await self.app(scope, receive, send)
  File "/root/.cache/pypoetry/virtualenvs/facli-zsB1kvQc-py3.8/lib/python3.8/site-packages/starlette/middleware/cors.py", line 86, in __call__
    await self.simple_response(scope, receive, send, request_headers=headers)
  File "/root/.cache/pypoetry/virtualenvs/facli-zsB1kvQc-py3.8/lib/python3.8/site-packages/starlette/middleware/cors.py", line 142, in simple_response
    await self.app(scope, receive, send)
  File "/root/.cache/pypoetry/virtualenvs/facli-zsB1kvQc-py3.8/lib/python3.8/site-packages/starlette/middleware/cors.py", line 86, in __call__
    await self.simple_response(scope, receive, send, request_headers=headers)

   ..........


  File "/root/.cache/pypoetry/virtualenvs/facli-zsB1kvQc-py3.8/lib/python3.8/site-packages/starlette/middleware/cors.py", line 86, in __call__
    await self.simple_response(scope, receive, send, request_headers=headers)
  File "/root/.cache/pypoetry/virtualenvs/facli-zsB1kvQc-py3.8/lib/python3.8/site-packages/starlette/middleware/cors.py", line 142, in simple_response
    await self.app(scope, receive, send)
  File "/root/.cache/pypoetry/virtualenvs/facli-zsB1kvQc-py3.8/lib/python3.8/site-packages/starlette/middleware/cors.py", line 74, in __call__
    headers = Headers(scope=scope)
  File "/usr/local/lib/python3.8/typing.py", line 873, in __new__
    obj = super().__new__(cls)
RecursionError: maximum recursion depth exceeded while calling a Python object

相关的问题我都查过了,可惜没有找到答案,现在只能怀疑是FastAPI或者Starlette的问题,谁能给我解决问题,非常感谢。

经过测试,我可以这样解决这个问题:sys.setrecursionlimit(9000000),但我认为这不应该是这种问题的最佳解决方案

4

1 回答 1

0

我想我找到了答案,就是因为添加跨域造成的,使用中间件的方式添加跨域会导致最大递归数错误,单独添加请求头即可。

这是不好的方法:

def register_cors(app:FastAPI):
    @app.middleware("http")
    async def cors(request:Request, call_next):
        app.add_middleware(
            CORSMiddleware,
            allow_origins=["*"],
            allow_credentials=True,
            allow_methods=["*"],
            allow_headers=["*"],      
        )

        response = await call_next(request)
        response.headers["access-control-allow-credentials"] = "true"
        response.headers["access-control-allow-origin"] = "*"
        response.headers["access-control-allow-methods"] = "*"
        return response

这是更好的方法:

def register_cors(app:FastAPI):
    app.add_middleware(
        CORSMiddleware,
        allow_origins=["*"],
        allow_credentials=True,
        allow_methods=["*"],
        allow_headers=["*"],      
    )
于 2021-04-12T12:35:46.687 回答