SvelteKit对接FastAPI时fetch GET/POST请求长期pending排查
根因分析
以下是匹配所有异常现象的高概率根因,按优先级从高到低排列:
- Chromium内核私有网络访问(PNA)预检拦截:这是最贴合所有故障特征的根因。Chromium从98版本后逐步强制启用PNA安全策略,当页面通过fetch/XHR向比当前上下文私密性更高的网络地址(环回地址localhost属于最高优先级的私密地址段)发起跨域请求时,会先发送OPTIONS预检请求,要求服务端返回
Access-Control-Allow-Private-Network: true响应头,未收到对应头时会将请求挂起等待,不会发送正式业务请求。
完全匹配的现象点:- Firefox目前默认未强制开启PNA校验,因此请求可以正常执行
- curl/Postman/浏览器地址栏直接导航不属于跨域脚本发起的请求范畴,不触发PNA检查,因此全部正常
- 预检请求默认超时时间很长,超时后偶发的浏览器放行逻辑就会出现搁置半小时到1小时才成功的情况
- CORS允许源配置与前端实际端口不匹配:SvelteKit开发服务默认端口为5173,预览服务默认端口为4173,你当前配置的允许源仅为
http://localhost:3000,如果前端实际运行端口不符,Chromium会严格校验Origin头拦截请求;Firefox在本地localhost场景下对CORS校验有宽松策略,可能出现Firefox可通但Chromium拦截的情况。 - 中间件顺序错误导致预检被拦截:FastAPI/Starlette的中间件遵循「后注册先执行」的逻辑,如果在CORS中间件注册之后又添加了自定义中间件、或者路由层拦截了OPTIONS方法请求,会导致CORS中间件无法正常处理预检,请求直接挂起。
- 异步路由阻塞事件循环:如果你的FastAPI async路由中存在未做线程池隔离的同步阻塞操作(比如同步IO、time.sleep、同步数据库调用),会阻塞整个服务事件循环,导致请求排队等待。
排查与修复步骤
按顺序执行以下操作即可定位并解决问题:
- 快速验证PNA问题
打开Chrome/Chromium,访问本地配置地址chrome://flags/#private-network-access-respect-preflight-results,将选项设置为Disabled,重启浏览器后再次测试请求。如果请求恢复正常,即可100%确认是PNA策略导致的问题。
修复代码参考,注意中间件注册顺序:from starlette.middleware.base import BaseHTTPMiddleware # 新增PNA响应头中间件 class PrivateNetworkAllowMiddleware(BaseHTTPMiddleware): async def dispatch(self, request, call_next): response = await call_next(request) response.headers["Access-Control-Allow-Private-Network"] = "true" return response app = FastAPI() # PNA中间件要先于CORS中间件注册 app.add_middleware(PrivateNetworkAllowMiddleware) app.add_middleware( CORSMiddleware, # 不要靠记忆写端口,把前端实际运行的Origin加进去,建议把SvelteKit常用端口都配上 allow_origins=["http://localhost:3000", "http://localhost:5173", "http://localhost:4173"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) app.include_router(router) - 校验实际请求Origin
打开浏览器开发者工具-网络面板,找到pending状态的请求,查看请求头中的Origin字段完整值,把该值原封不动加入CORS的allow_origins配置中,不要自行臆测前端运行端口。 - 检查OPTIONS预检请求状态
在网络面板中勾选显示OPTIONS类型请求,点击按钮后观察是否有OPTIONS请求发出:- 如果OPTIONS请求本身处于pending状态,检查本地安全软件、防火墙是否拦截了环回接口的OPTIONS方法请求
- 如果OPTIONS请求返回响应,检查响应头是否包含完整的
Access-Control-Allow-Origin、Access-Control-Allow-Private-Network字段,缺失则对应补全
- 排除事件循环阻塞问题
在接口入口处添加打印日志,点击按钮时观察后端是否立刻打印日志:- 如果后端很久之后才收到请求日志,说明问题出在浏览器预检/网络层,回到前3步排查
- 如果后端立刻收到日志但很久才返回响应,检查async路由中是否存在同步阻塞逻辑,将同步操作丢到线程池执行即可。
内容的提问来源于stack exchange,提问作者rockfruit
相关产品推荐
相关产品推荐

