2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

船井総研デジタルのよもぎたです。

FastAPIで開発しているときにUvicornでたてたローカルサーバーに http://127.0.0.1:8000/ でアクセスするとページが表示されるのに、 http://localhost:8000/ でアクセスすると接続すらできないという状況に遭遇しました。原因と調べて分かったことを共有いたします。

原因と結論

原因

  1. Uvicorn はデフォルトでは 127.0.0.1:8000 をListenする。::1:8000 (IPv6ループバックアドレスのTCP8000番ポート)はListenしない。
  2. おそらくOSの動作(仕様)だと思うが、 localhost::1 に名前解決されていた。
  3. そこで、ブラウザは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
  • 私が確認したのは、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しませんでした。
  • --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でのやりとりを見る限りでは、HypercornGunicorn をつかうと実現できるようです。
FastAPIはASGIでの運用が想定されているようで、上記の2つのうちそれに対応しているのは Hypercorn の方ですので、そちらを使うのが良さそうです。
また、UvicornのまえにNGINXを置くという解決方法もあるようですが、ローカル開発には過剰なような気もします。

まとめ

今回はたまたまFastAPI+Uvicornの環境で発生しましたが、ローカル開発では様々なケース(開発ツール等)で起こりうるシナリオだと思います。本件を頭の片隅に置いておいていただくと、似たケースかも、と問題解決に役立つときが来るかもしれません。もちろん、そもそも問題が起きなければそれが一番ですが。

最後までお読みいただきありがとうございました。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?