はじめに
AWS SAMで1日半ハマり激萎え、そして解決。その備忘録です。
結構ピンポイントの問題に関する記事ですが、同じハマり方をする人は結構居そうだと思ったので投稿します。
対象読者
- AWS SAM(Serverless Application Model)を使ってAPIを開発している方
- XcodeでiOS (watchOS, visionOS) 開発をしている方
- 上記のiOSなどの実機から、ローカルのSAMのAPIにリクエストを送る設計のアプリをつくっt
環境
ツール | バージョン |
---|---|
MacOS | 15.0.1 |
Xcode | 16.1 |
AWS SAM CLI(Runtime: Java21) | 1.135.0 |
Python | 3.13 |
Colima | 0.7.5 |
筆者のアプリの構造
認証などは端折って書いていますが、おおまかにこのような構造です
ローカル開発は、このように実施します
- クライアント:iPhone実機
- API:AWS SAMでAPI GatewayとLambdaをローカル再現
- DB: Dynamodb-localのimageを使ってコンテナを立ち上げ
起こった現象
状況を再現します。
1. SAMの起動
下記コマンドでSAMを起動しました
※docker containerで動いているdynamoDBに接続するために、docker netowrkを指定していますが、今回はあまり気にしなくていいです
sam local start-api --host 0.0.0.0 --docker-network dynamo-network
起動すると下記のようなログが出力されます
# ~~ 一部省略 ~~
Containers Initialization is done.
Mounting GetCurrentVersion at http://127.0.0.1:3000/api/version [GET, OPTIONS]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. If you used sam build before
running local commands, you will need to re-run sam build for the changes to be picked up. You only need to restart SAM CLI if you update your AWS SAM template
* * Tip: There are .env or .flaskenv files present. Do "pip install python-dotenv" to use them.
2025-04-06 14:41:24 WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:3000
* Running on http://xxx.xxx.xxx.xxx:3000
2025-04-06 14:41:24 Press CTRL+C to quit
http://127.0.0.1:3000
、http://xxx.xxx.xxx.xxx:3000
でapiがホストされました
/api/version
というGETのエンドポイントを一つ実装している前提です。
SAMの起動で重要なところ
--host 0.0.0.0
このオプションは、SAM CLI を使って iPhone 実機や他の端末からアクセスするために 必須です。
- SAM CLIは、デフォルトではlocalhost(=127.0.0.1)だけにバインドされます
- つまり、Mac上のアプリやブラウザからしかアクセスできません
- 同じWi-Fiの iPhone 実機や別PCからのリクエストは届きません
これを忘れると、実機iPhoneからのリクエストは、SAMに一生届きません。
【ログの比較】
# ❌ --host オプションなしで起動(デフォルト)
sam local start-api
# localhost のみバインド
> * Running on http://127.0.0.1:3000
# ✅ --host 0.0.0.0 を指定して起動
sam local start-api --host 0.0.0.0
# 全てのインターフェースにバインドされる
> * Running on all addresses (0.0.0.0)
> * Running on http://127.0.0.1:3000
> * Running on http://xxx.xxx.xxx.xxx:3000 ← MacのローカルIPもOK!
2. ブラウザからAPIを叩いてみる
http://localhost:3000/api/version
ブラウザから叩いてみると、正常にレスポンスが返ってきます✅
順調、順調。
3. iOS実機からAPIを叩いてみる
以下のような実装で、iOSからSAMにリクエストを送ります
かなり端折っていますが、要は該当API /api/version
にGETリクエストを送っています。
let urlRequest = URLRequest(url: URL("http://xxx.xxx.xxx.xxx:3000/api/version")!)
try await URLSession.shared.data(for: urlRequest)
iOSの実装で重要なところ
urlをhttp://localhost:3000
とせずに、
http://xxx.xxx.xxx.xxx:3000
としているのは、
iPhone実機からlocalhost
にリクエストを送ると、MacではなくiPhone自体のlocalhostを参照してしまうからです。
xxx.xxx.xxx.xxx
に入る値は、
MacがWi-Fiルーターから取得したプライベートIPアドレスです。
iPhone実機などと同じWi-Fiに接続していれば、LAN内通信に使えるIPとなります。
下記コマンドで取得できます。
ipconfig getifaddr en0
> xxx.xxx.xxx.xxx # MacのプライベートIP
出力されたエラー
iOSからのリクエストが失敗しました。❌
iOS側
in handle error: Error Domain=NSURLErrorDomain Code=-1005 "The network connection was lost." UserInfo={_kCFStreamErrorCodeKey=-4, NSUnderlyingError=0x303535b60 {Error Domain=kCFErrorDomainCFNetwork Code=-1005 "(null)" UserInfo={NSErrorPeerAddressKey=<CFData 0x3018090e0 [0x1ffb73830]>{length = 16, capacity = 16, bytes = 0x10020bb8a9fef85f0000000000000000}, _kCFStreamErrorCodeKey=-4, _kCFStreamErrorDomainKey=4}}, _NSURLErrorFailingURLSessionTaskErrorKey=LocalDataTask <171B3E0B-1287-405E-8255-25E16D8A3EF0>.<1>, _NSURLErrorRelatedURLSessionTaskErrorKey=(
"LocalDataTask <171B3E0B-1287-405E-8255-25E16D8A3EF0>.<1>"
), NSLocalizedDescription=The network connection was lost., NSErrorFailingURLStringKey=http://xxx.xxx.xxx.xxx:3000/api/version, NSErrorFailingURLKey=http://xxx.xxx.xxx.xxx:3000/api/version, _kCFStreamErrorDomainKey=4}
SAM側
----------------------------------------
Exception occurred during processing of request from ('xxx.xxx.xxx.xxx', 64319)
----------------------------------------
Exception occurred during processing of request from ('xxx.xxx.xxx.xxx', 64320)
----------------------------------------
Exception occurred during processing of request from ('xxx.xxx.xxx.xxx', 64321)
Traceback (most recent call last):
Traceback (most recent call last):
----------------------------------------
Exception occurred during processing of request from ('xxx.xxx.xxx.xxx', 64322)
Traceback (most recent call last):
File "/opt/homebrew/Cellar/python@3.13/3.13.2/Frameworks/Python.framework/Versions/3.13/lib/python3.13/socketserver.py", line 697, in process_request_thread
self.finish_request(request, client_address)
~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/Cellar/python@3.13/3.13.2/Frameworks/Python.framework/Versions/3.13/lib/python3.13/socketserver.py", line 362, in finish_request
self.RequestHandlerClass(request, client_address, self)
~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/homebrew/Cellar/python@3.13/3.13.2/Frameworks/Python.framework/Versions/3.13/lib/python3.13/socketserver.py", line 766, in __init__
self.handle()
~~~~~~~~~~~^^
# ~~ 一部省略 ~~
Traceback (most recent call last):
File "/opt/homebrew/Cellar/python@3.13/3.13.2/Frameworks/Python.framework/Versions/3.13/lib/python3.13/socketserver.py", line 362, in finish_request
self.RequestHandlerClass(request, client_address, self)
~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
----------------------------------------
File "/opt/homebrew/Cellar/python@3.13/3.13.2/Frameworks/Python.framework/Versions/3.13/lib/python3.13/socket.py", line 719, in readinto
return self._sock.recv_into(b)
~~~~~~~~~~~~~~~~~~~~^^^
OSError: [Errno 9] Bad file descriptor
# ~~ 一部省略 ~~
OSError: [Errno 57] Socket is not connected
OSError: [Errno 9] Bad file descriptor
この部分が怪しい。
全体的に、SAMのAPIのPython製エミュレータ(Werkzeug + socketserver)がクラッシュしている感じです。
原因
結論、
SAM CLI v1.135.0(=Python 3.13 + Werkzeug)に含まれる
「開発用HTTPサーバーの不具合」っぽいです。
つまりSAMの新しいバージョンでは、
「SAM CLIが内部で動かしている簡易Webサーバー」が調子悪いみたいです。
SAM CLI ver | Python | Werkzeug | 状態 |
---|---|---|---|
✅ v1.128.0 | 3.11 | 2.2.x〜2.3.x | 安定 |
❌ v1.129.0〜v1.135.0 | 3.12〜3.13 | 2.3.x以降 | 不安定(Bad file descriptor) |
やったこと
SAM CLI のバージョンを 1.135.0
→ 1.128.0
に ダウングレードしました。
1日半あれこれハマりましたが、最終的にはそれだけで解消しました。それだけで。
おわりに
こういう依存関係でハマるのが一番しんどいですね。
iOS × SAM で開発されている方はご注意を!