FastAPIでX-Forwarded-Hostを用いたリダイレクト
ソースを見てみたい人向け
https://gist.github.com/attakei/6b308a2a7949746a8027fc0258f1de1c
なにこれ
FastAPI
用にX-Forwarded-Host
ヘッダーがFastAPI
用とはStarlette
だけなので、Starlette
派生なら
何ができるの?
FastAPI
が
コードを書いてみた背景とか
Firebase + Cloud Run の挙動
このFirebase Hosting
+ Cloud Run
のCloud Run
サービスから
Cloud Run
はFirebase
からCloud Run
へのFirebase
のX-Forwarded-Host
ヘッダー経由で
FastAPIの Slashes redirection
FastAPI
のAPIRouter
クラスがAPIRouter
のredirect_slashes
が
これは、True
なら/
と/
末尾が/
スラッシュを
このHost
ヘッダーを
組み合わさるとどうなるか
上記2点がHost
ヘッダーはCloud Run
自身の
「Firebaseの
と
「なんの
中身的には、
# flake8: noqa
class Router:
"""starlette.routing.Router から必要最低限のとこだけ抜粋"""
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
# 前略
if scope["type"] == "http" and self.redirect_slashes and scope["path"] != "/":
# リクエストパスが / で終わらない時に、リダイレクト可能性があるので / 付きのデータを用意
redirect_scope = dict(scope)
if scope["path"].endswith("/"):
redirect_scope["path"] = redirect_scope["path"].rstrip("/")
else:
redirect_scope["path"] = redirect_scope["path"] + "/"
# ルーティング情報からリダイレクト先とマッチする情報があるか探し、
# 見つかったら、リダイレクトレスポンスを返してしまうs
for route in self.routes:
match, child_scope = route.matches(redirect_scope)
if match != Match.NONE:
redirect_url = URL(scope=redirect_scope) # <= この中身がhostしかみない
response = RedirectResponse(url=str(redirect_url))
await response(scope, receive, send)
return
# 後略
どう対処したか
https://gist.github.com/attakei/6b308a2a7949746a8027fc0258f1de1c
ミドルウェアをX-Forwarded-Host
ヘッダーがHost
ヘッダーの
1class ForwardedHostMiddleware:
2 def remap_headers(self, src: Headers, before: bytes, after: bytes) -> Headers:
3 """元のヘッダーリストから、
4 - 無関係なものはそのままコピーして
5 - 置換対象は一旦退避して
6 - 結果に応じて再追記する
7 """
8 remapped = []
9 before_value = None
10 after_value = None
11 for header in src:
12 k, v = header
13 if k == before:
14 before_value = v
15 continue
16 elif k == after:
17 after_value = v
18 continue
19 remapped.append(header)
20 if after_value:
21 remapped.append((before, after_value))
22 elif before_value:
23 remapped.append((before, before_value))
24 return remapped
弊害はあるか?
他にHost
ヘッダを
ただHost
ヘッダーに