问题描述
我正在使用 FastAPI+sqlalchemy+MysqL 构建一个应用程序,当我进行压力测试时,我收到一个 Asgi 异常错误:
[ERROR] Exception in Asgi application,RecursionError: maximum recursion depth exceeded while calling a Python object.
我绝对可以:
我的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,"msg": "type error","errinfo_type":detail}
)
else:
return JSONResponse(
status_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,"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","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","msg": "token error"}
NowStamp = datetime.Now().timestamp()
expired_timeStamp = res[0]['expired_time'].timestamp()
if NowStamp > expired_timeStamp:
return {"status":"Failed","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","msg": "token is valid"}
session.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,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,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,_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,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,send)
File "/root/.cache/pypoetry/virtualenvs/facli-zsB1kvQc-py3.8/lib/python3.8/site-packages/starlette/middleware/cors.py",request_headers=headers)
..........
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)
,但我认为这应该不是这类问题的最佳解决方案
解决方法
我想我已经找到答案了,那是因为添加跨域造成的,使用中间件的方式添加跨域会导致最大递归错误,添加单独的请求头即可。
这是不好的方式:
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,)