🫠

Mac Monterey 以降で Flask など ポート 5000 を使う場合は注意

2023/02/12に公開

TL;DR

  • Mac Montrey 以降では Mac のポート 5000 で Air Play のサーバーが起動している
  • Flask など開発サーバーでポート 5000 を使おうとすると競合するので、Air Play を無効化するかポート 5000 以外を使うと良い
  • 参考: curl -i http://localhost:5000 I get a 403 Forbidden error.

何が起きたか

私は普段 Mac 直接ではなく、Ubuntu VM を立てて、VSCode の Remote SSH などを使ってそこで開発をしています。

VSCode は自動でいい感じにポートフォワードしてくれるので、いつもは VM で起動した開発サーバーにローカルの Mac のブラウザなどから問題なくアクセスできるのですが、今回バックエンドに Flask (ポート 5000) を使う構成で開発を進めていたところ、Mac ローカルのブラウザからリクエストを送るところでエラーになりました。

Chrome の Console を見ると、CORS のエラーが出力されていました (${PATH} は任意のパス)。

Access to fetch at 'http://localhost:5000/${PATH}' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

しかし、Flask は Flask-CORS を使い、開発環境では --host=0.0.0.0 を指定しており、CORS でのエラーが起きる設定ではありません。

また切り分けを進める中で、VM で直接ブラウザを開いてリクエストを送ると、CORS のエラーが起きないことがわかりました。

原因はなんだったか

VM からは問題なく動作するということで、Mac と VM それぞれからリクエストした時の HTTP レスポンスを詳細に見ました。

  • Mac から curl 実行[1]

    user_on_mac@mac: $ curl -I -v localhost:5000/${PATH}
    *   Trying localhost:5000...
    * Connected to localhost (127.0.0.1) port 5000 (#0)
    > HEAD /${PATH} HTTP/1.1
    > Host: localhost:5000
    > User-Agent: curl/7.85.0
    > Accept: */*
    >
    * Mark bundle as not supporting multiuse
    < HTTP/1.1 501 Not Implemented
    HTTP/1.1 501 Not Implemented
    < Content-Length: 0
    Content-Length: 0
    < Server: AirTunes/670.6.2
    Server: AirTunes/670.6.2
    
    <
    * Connection #0 to host localhost left intact
    user_on_mac@mac
    
  • VM から curl 実行

    user_on_vm@ubuntu20:~$ curl -I -v localhost:5000/${PATH}
    *   Trying ::1:5000...
    * TCP_NODELAY set
    * Connected to localhost (::1) port 5000 (#0)
    > HEAD /${PATH} HTTP/1.1
    > Host: localhost:5000
    > User-Agent: curl/7.68.0
    > Accept: */*
    >
    * Mark bundle as not supporting multiuse
    < HTTP/1.1 405 METHOD NOT ALLOWED
    HTTP/1.1 405 METHOD NOT ALLOWED
    < Server: Werkzeug/2.2.2 Python/3.9.13
    Server: Werkzeug/2.2.2 Python/3.9.13
    < Date: Sun, 12 Feb 2023 05:19:25 GMT
    Date: Sun, 12 Feb 2023 05:19:25 GMT
    < Content-Type: text/html; charset=utf-8
    Content-Type: text/html; charset=utf-8
    < Allow: POST, OPTIONS
    Allow: POST, OPTIONS
    < Content-Length: 153
    Content-Length: 153
    < Access-Control-Allow-Origin: *
    Access-Control-Allow-Origin: *
    < X-Request-Id: 5ecf47a7-152c-4a10-80dc-54c08225621e
    X-Request-Id: 5ecf47a7-152c-4a10-80dc-54c08225621e
    < Connection: close
    Connection: close
    
    <
    * Closing connection 0
    user_on_vm@ubuntu20:~$
    

IPv4 と IPv6 の違いがあったり細かい点もあるのですが、よく見ると Server が違う ことに気づきました (HTTP Status Code は脚注1 の事情で置いておく) 。
Ubuntu VM では Flask の開発サーバーそのままなので期待通り Werkzeug なのですが、Mac では AirTunes/670.6.2 という見慣れないものになっています。

これを手がかりに調べたところ、以下の Apple Developer Forums の投稿が見つかりました。

curl -i http://localhost:5000 I get a 403 Forbidden error.

I figured this out. Monterrey has Airplay Receiver listening on Port 5000. You can disable it in System Preferences => Sharing => AirPlay Receiver.

なんと、Mac Monterey 以降ではポート 5000 を Airplay Reciever が使用しているため、同じポートを Flask が使えなかった、というだけだったようです。

どう解消したか

Airplay は使わないので、Mac の設定から Airplay を無効化し、VSCode でポート 5000 をポートフォワードしたところ、Mac ローカルのブラウザから Ubnutu VM の Flask に無事リクエストを送ることができました。

終わりに

エラーメッセージを見ると CORS のエラーだったので、CORS 周りの設定がおかしいのではと思ってひたすら時間を無駄に溶かしました。
実際はリクエスト送り先が異なるという話でした、だからエラーメッセージもあながちおかしいわけではない (けどもっとわかりやすく出てほしい……)。

多分ローカルでポート競合が起きたらわかりやすいエラーが出たと思うのですが、ポートフォワードだと、ポート競合がすぐ起きるわけでもないからか、すぐに気づけないですね。

私は CORS で検索をかけても日本語・英語でもこの問題を見つけられなかったので、同じように苦しんでいる・苦しんだ人がきっといると思います。
この記事が助けになれば嬉しいです。

脚注
  1. これは後から再現させたログなので HTTP Status Code は 501 になっていますが、私の記憶だと参考記事にもあるように最初は 403 Forbidden だったような気がします ↩︎

Discussion