请向我提出一个几乎不可能创建可重现示例的问题。
我使用 Docker、无服务器和 FastAPI 设置了 API,并部署在 AWS API Gateway 上。讨论的所有路由都受到传递到标头中的 api 密钥的保护 (
x-api-key
)。
我正在尝试使用
fastapi.responses.RedirectResponse
完成从一条路线到另一条路线的简单重定向。重定向在本地工作得很好(不过,这是没有 api-key 的),并且当部署在 AWS 上并直接连接时,两条路由都工作得很好,但是有些东西阻止了从路由一 (abc/item
) 到路由二 ( xyz/item
)当我部署到 AWS 时。我不确定可能是什么问题,因为 CloudWatch 中的日志没有给我太多可处理的信息。
为了说明我的问题,假设我们的路线
abc/item
看起来像这样:
@router.get("/abc/item")
async def get_item(item_id: int, request: Request, db: Session = Depends(get_db)):
if False:
redirect_url = f"/xyz/item?item_id={item_id}"
logging.info(f"Redirecting to {redirect_url}")
return RedirectResponse(redirect_url, headers=request.headers)
else:
execution = db.execute(text(items_query))
return convert_to_json(execution)
因此,我们检查某个值是否为 True/False,如果为 False,我们使用
abc/item
从 xyz/item
重定向到 RedirectResponse()
。我们传递redirect_url,它只是包含查询参数的xyz/item
路线,我们传递request.headers
(按照建议的here和here),因为我认为我们需要将x-api-key
传递到新路线。在第二条路线中,我们再次尝试在不同的表中进行查询(other_items
)并返回一些值。
我还尝试将
status_code=status.HTTP_303_SEE_OTHER
和 status_code=status.HTTP_307_TEMPORARY_REDIRECT
传递给 RedirectResponse()
,正如我在 StackOverflow 和 FastAPI 讨论 上发现的一些与切线相关的问题所建议的,但这也没有帮助。
@router.get("/xyz/item")
async def get_item(item_id: int, db: Session = Depends(get_db)):
execution = db.execute(text(other_items_query))
return convert_to_json(execution)
就像我说的,部署后我可以成功地直接连接到
abc/item
并获得返回值,如果True
并且我也可以直接连接到xyz/item
并从中获得正确的值,但是当我传递一个值时到abc/item
,即False
(因此它应该重定向)我得到{"message": "Forbidden"}
。
如果它有任何帮助,我尝试使用“curl”工具进行调试,并且我返回的标头提供以下信息:
Content-Type: application/json
Content-Length: 23
Connection: keep-alive
Date: Wed, 27 Jul 2022 08:43:06 GMT
x-amzn-RequestId: XXXXXXXXXXXXXXXXXXXX
x-amzn-ErrorType: ForbiddenException
x-amz-apigw-id: XXXXXXXXXXXXXXXX
X-Cache: Error from cloudfront
Via: 1.1 XXXXXXXXXXXXXXXXXXXXXXXXX.cloudfront.net (CloudFront)
X-Amz-Cf-Pop: XXXXX
X-Amz-Cf-Id: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
因此,这暗示存在 CloudFront 错误。不幸的是,当我查看 AWS 上的 CloudFront 仪表板时,我没有看到任何东西稍微暗示这个 API,那里实际上什么也没有(尽管我确实有查看内容的权限......)
CloudWatch 中的 API 日志如下所示:
2022-07-27T03:43:06.495-05:00 Redirecting to /xyz/item?item_id=1234...
2022-07-27T03:43:06.495-05:00 [INFO] 2022-07-27T08:43:06.495Z Redirecting to /xyz/item?item_id=1234...
2022-07-27T03:43:06.496-05:00 2022-07-27 08:43:06,496 INFO sqlalchemy.engine.Engine ROLLBACK
2022-07-27T03:43:06.496-05:00 [INFO] 2022-07-27T08:43:06.496Z ROLLBACK
2022-07-27T03:43:06.499-05:00 END RequestId: 6f449762-6a60189e4314
2022-07-27T03:43:06.499-05:00 REPORT RequestId: 6f449762-6a60189e4314 Duration: 85.62 ms Billed Duration: 86 ms Memory Size: 256 MB Max Memory Used: 204 MB
我一直想知道我的问题是否可能与我需要添加到我的
serverless.yml
中的某处(也许是 functions:
部分)中的某些内容有关。目前这两条路线看起来像这样:
events:
- http:
path: abc/item
method: get
cors: true
private: true
request:
parameters:
querystrings:
item_id: true
- http:
path: xyz/item
method: get
cors: true
private: true
request:
parameters:
querystrings:
item_id: true
最后,值得注意的是,我已经向 FastAPI 添加了自定义中间件来处理连接到
other_items
和 items
表所需的两个不同的数据库连接,尽管我不确定这有多么相关,考虑到这一点本地重定向时功能正常。为此,我实施了here找到的解决方案。这个自定义中间件首先是重定向的原因(我们根据该中间件的路由更改连接 URI),所以我认为分享这些信息也很好。
谢谢!
如here和here所述,不可能重定向到设置了自定义标题的页面。
HTTP
协议中的重定向不支持向目标位置添加任何标头。它本身基本上只是一个标头,并且只允许 URL(重定向响应,但如果需要,也可以包含正文内容 - 请参阅这个答案)。当您将 authorization
标头添加到 RedirectResponse
时,您只需将该标头发送回客户端。
建议here,您可以使用
set-cookie
HTTP 响应标头:
HTTP 响应标头用于从 服务器发送给用户代理(客户端),以便用户代理可以将其发送回 稍后服务器。Set-Cookie
在 FastAPI 中,可以在 here 和 here 找到文档 - 可以按如下方式完成:
from fastapi import Request
from fastapi.responses import RedirectResponse
@app.get("/abc/item")
def get_item(request: Request):
redirect_url = request.url_for('your_endpoints_function_name') #e.g., 'get_item'
response = RedirectResponse(redirect_url)
response.set_cookie(key="fakesession", value="fake-cookie-session-value", httponly=True)
return response
在将用户重定向到的另一个端点内,您可以提取该
cookie
来对用户进行身份验证。该 cookie 可以在 request.cookies
中找到 - 例如,它应该返回 {'fakesession': 'fake-cookie-session-value-MANUAL'}
- 并且您可以使用 request.cookies.get('fakesession')
检索它,如这个答案中所示。
另一方面,
request.url_for()
函数仅接受path
参数,而不接受query
参数(例如item_id
和/abc/item
端点中的/xyz/item
)。因此,您可以按照已有的方式创建 URL,也可以使用 CustomURLProcessor
建议的 here、here 和 here,这允许您传递 path
和 query
参数。
如果重定向是从一个域到另一个域(例如,从
abc.com
到xyz.com
),请查看这个答案。