船井総研デジタルのよもぎたです。
FastAPIで開発しているときにUvicornでたてたローカルサーバーに http://127.0.0.1:8000/ でアクセスするとページが表示されるのに、 http://localhost:8000/ でアクセスすると接続すらできないという状況に遭遇しました。原因と調べて分かったことを共有いたします。
原因と結論
原因
- Uvicorn はデフォルトでは
127.0.0.1:8000をListenする。::1:8000(IPv6ループバックアドレスのTCP8000番ポート)はListenしない。 - おそらくOSの動作(仕様)だと思うが、
localhostは::1に名前解決されていた。 - そこで、ブラウザはListenされていないIPアドレス&ポートの組み合わせにアクセスすることとなり、接続すらできなかった。
結論
Uvicorn がIPv4アドレスでListenしているのに、ブラウザがIPv6アドレスでアクセスして、すれ違いが生じてしまっていた。それを避けるために、特にListenするアドレスを指定せずにUvicornを起動した場合は、 http://127.0.0.1:8000/ と明示的にIPv4アドレスでアクセスするのがよいでしょう。
調べて分かったこと
実際の挙動の観察してみる
- ブラウザが
http://localhost:8000/でアクセスしたときに、IPv6ループバックアドレスのTCP8000番ポート宛にTCP-SYNが送信されているのをWireshakで確認。このときはループバックインターフェースをtcp port 8000でフィルタしてキャプチャしました。 - Uvicornが 127.0.0.1:8000 でListenしているのは、Windows/Linuxそれぞれ次のコマンドで確認できます。
- Windows:
netstat -abn -p tcp(管理者権限で実行するのが確実) - Linux:
sudo ss -lnpt
- Windows:
- 私が確認したのは、Ubuntu 22.04.2 LTS環境でした。
調べなかったこと
localhostをIPv4とIPv6どちらのループバックアドレスに解決するか、いろいろなOSを用意しての調査はしていません。
Uvicornの公式ドキュメントをあたってみた
- Socket Binding のセクション に、デフォルトで 127.0.0.1をListenする と明記されていました。
- ListenするIPアドレスを明示的に指定するには
--host ADDRとすればよいことが分かりました。 - IPv6にも対応していることが明記されていました。
- 複数のIPアドレスとポートの組をListenさせる手順について記載が見つけられませんでした。
ドキュメントに記載がないので実際に試してみて挙動を確認してみた
-
--host ADDRを複数指定してみた ⇒ 後に指定した ADDR だけ をListenした。- 例1)
--host 127.0.0.1 --host ::1とした場合、::1:8000だけをListenして、127.0.0.1はListenしませんでした。 - 例2)
--host fe80::dead:beef --host 192.168.1.100とした場合、 192.168.1.100:8000だけをLisnして、fe80::dead:beefはListenしませんでした。
- 例1)
-
--host ADDR1,ADDR2などいくつか思いついたセパレータでアドレスを区切って2つ指定してみたが、エラーになってUvicornの起動すらできませんでした。
それならソースコードを追うしかないじゃない!
ソースコードは正義です。ジャスティスです。夢も希望も(時には絶望も)あります。
追って行って注目したのは次の2か所です。(2023/06/28 06:30 JSTに確認しました)
https://github.com/encode/uvicorn/blob/master/uvicorn/main.py#L360
https://github.com/encode/uvicorn/blob/master/uvicorn/config.py#L513
ひとつの --host オプションに複数のアドレスが指定されてくることを想定していないし、--hostオプションが複数回指定されることも想定していません。
今回は絶望を引いてしまいました。
複数のIPアドレスとポートの組をListenするにはどうすればよいか
こちらのIssueでのやりとりを見る限りでは、Hypercorn や Gunicorn をつかうと実現できるようです。
FastAPIはASGIでの運用が想定されているようで、上記の2つのうちそれに対応しているのは Hypercorn の方ですので、そちらを使うのが良さそうです。
また、UvicornのまえにNGINXを置くという解決方法もあるようですが、ローカル開発には過剰なような気もします。
まとめ
今回はたまたまFastAPI+Uvicornの環境で発生しましたが、ローカル開発では様々なケース(開発ツール等)で起こりうるシナリオだと思います。本件を頭の片隅に置いておいていただくと、似たケースかも、と問題解決に役立つときが来るかもしれません。もちろん、そもそも問題が起きなければそれが一番ですが。
最後までお読みいただきありがとうございました。