FastAPI(Starlette)搭配NGINX代理:Request对象URL协议异常问题
核心原因
Starlette默认不会自动读取X-Forwarded-Proto这类代理头来重构请求URL,它直接使用与NGINX之间的通信协议(通常为HTTP)生成request.url,所以即便客户端用HTTPS请求NGINX,Starlette端仍会显示HTTP协议。
1. Starlette如何在NGINX代理场景下设置Request.url?
Starlette官方提供了ForwardedHeadersMiddleware专门处理代理场景的URL重构,无需手动修改私有属性。只需启用该中间件并配置信任的代理IP,Starlette就会自动读取NGINX传递的代理头,生成包含正确HTTPS协议的request.url。
2. NGINX层面如何配置才能让Starlette识别正确URL?
NGINX无法直接修改Starlette的request.url,但可以通过传递正确的代理头让Starlette自行重构。你的现有配置基本正确,但需确保X-Forwarded-Proto传递的是客户端实际使用的协议(HTTPS),同时配合Starlette的信任代理配置生效。
3. 为什么自定义中间件修改request.url无效?
Starlette的Request对象设计为不可变结构,直接修改私有属性_url不会被后续请求处理逻辑正确识别。必须通过修改请求的scope上下文或使用官方中间件来实现URL重构。
具体解决方案
方案1:使用Starlette官方ForwardedHeadersMiddleware(推荐)
在FastAPI应用中添加该中间件,指定信任的代理IP(比如NGINX所在服务器的IP):
from fastapi import FastAPI from starlette.middleware.forwardedheaders import ForwardedHeadersMiddleware app = FastAPI() # 配置信任的代理IP,本地代理填["127.0.0.1"],生产环境填NGINX服务器的实际IP/子网 app.add_middleware(ForwardedHeadersMiddleware, trusted_hosts=["127.0.0.1", "::1"])
方案2:修正NGINX代理配置
确保NGINX正确传递代理头,特别是X-Forwarded-Proto:
location / { proxy_pass http://your-fastapi-service:8000; # 替换为你的FastAPI服务地址 proxy_set_header Upgrade $http_upgrade; proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 传递客户端请求的协议(HTTPS) proxy_set_header X-Forwarded-Host $host; }
方案3:自定义中间件的正确写法(不推荐,仅作参考)
如果一定要用自定义中间件,需要修改请求的scope上下文并重新生成URL:
from fastapi import Request from starlette.datastructures import URL @app.middleware("http") async def fix_url_scheme(request: Request, call_next): # 修改scope中的scheme字段 request.scope["scheme"] = "https" # 重新构建URL对象 request._url = URL( scheme=request.scope["scheme"], netloc=f"{request.scope['server'][0]}:{request.scope['server'][1]}" if request.scope.get("server") else request.scope["host"], path=request.scope["path"], query_string=request.scope["query_string"] ) response = await call_next(request) return response
验证
配置完成后重新启动NGINX和FastAPI服务,访问https://www.example.com/test,返回的request.url应显示为https://www.example.com/test,同时request['headers']中会包含x-forwarded-proto: https。
内容的提问来源于stack exchange,提问作者physicus

