こんにちは。kamimiです。🌞
Vapor で API を実装中です。ようやく Google Cloud Run(以下GCR) にデプロイする段階にきて、イメージをビルドしたときにとあるエラーが発生しました。
ローカルでビルドしている時は気づかなかったので、今後の自分や他の人のために記事に残しておこうと思います。
発生したエラー
ようやく API を実装し終わったので、GCR にデプロイしようと思い、以下のコマンドを実行しました。
gcloud builds submit --tag gcr.io/iosappreviewstatuschecker/checker --timeout=20m
その時に以下のようなエラーが発生しました。
「この型は、FoundationNetworking モジュールに移動しました。使用するには、そのモジュールをインポートしてください。」
/build/Sources/App/Infrastructure/API/Session.swift:19:26: error: 'URLSession' is unavailable: This type has moved to the FoundationNetworking module. Import that module to use it.
private let session: URLSession
^~~~~~~~~~
対処方法
調べたところ、そもそも本当にURLSession
を使う必要があるのか?を考えた方が良さそうです。理由は後ほど記述します。その上で使いたい場合は後述の処理をしましょう。
本当に URLSession
を使う必要があるか?
私の場合、最終的に URLSession
を使用しないという選択肢をとりました。機能を削ったわけではなく、別の方法で実装しました。
みなさんもこの代替方法が適用可能かを考えた上で、URLSession
を使用するべきか考えてみてください。
結論、以下の理由から私は使用しませんでした。
- そもそも
URLSession
を使用しなくても済むように、Vapor が API を提供しており、それで事足りた - Vapor では Linux のための Foundation の非同期 API が使用できない(
data
メソッドなどの基本的な API が使用できない) - デプロイ時のイメージサイズが大きくなる
理由1. そもそも URLSession
を使用しなくても済むように、Vapor が API を提供しており、それで事足りた
私の場合、URLSession
を使用したい理由は、外部の API を呼びたいからでした。後述する「やっぱり URLSession
を使いたい場合」の章に記載した通りに対処すれば、イメージビルド時にエラーは発生しなくなりました。
ただドキュメントを見ていたところ、URLSession
を使用しなくても済むように、Vapor が API を提供してくれていることがわかりました。
サーバーサイドの実装に疎いせいか、iOS アプリの実装と同じ感覚で実装していたせいか、 Vapor がそういった API を用意していることに気づかないまま、 いつもの感覚で URLSession
を使用していました。
ちなみに以下のように使用することができます。(ドキュメントからそのまま引用)
let response = try await req.client.post("https://httpbin.org/status/200") { req in
// Encode query string to the request URL.
try req.query.encode(["q": "test"])
// Encode JSON to the request body.
try req.content.encode(["hello": "world"])
// Add auth header to the request
let auth = BasicAuthorization(username: "something", password: "somethingelse")
req.headers.basicAuthorization = auth
}
// Handle the response.
例えば自分で API のレスポンスをもっとカスタマイズしたい場合などは、カスタマイズの仕方によってこの API は使用しないという判断になる可能性もあると思います。
カスタマイズ性については調べていないので詳しくはわかりませんが、Vapor が便利な API を提供してくれているので、この API を使用した方がコードが複雑にならず可読性が上がって良いのではと思いました。
理由2. Vapor では Linux のための Foundation の非同期 API が使用できない
Vapor では Linux のための Foundation の非同期 API が使用できません。 ローカルでビルドして localhost で動作確認しているときには問題なく動くので、気づかなかったです。。😇
実際、私は以下のように実装していたのですが、イメージのビルド時にエラーになってしまいました・・・
let (data, response) = try await URLSession.shared.data(for: urlRequest)
以下の stack overflow で Linux のための Foundation の非同期 API が使用できないことを知りました。回答者が Vapor Core Team のお一人である 0xTim さんなので、かなり信用度の高い情報だと思っています。
The async APIs for Foundation on Linux aren't available yet (they're different from the actual language concurrency features).
For a Vapor app you shouldn't really be using URLSession to make requests, it doesn't fit with how the framework works. Use Vapor's client instead (which has async APIs)
回答にも、Vapor のクライアント API を代わりに使用してください、とありますね。
フレームワークの動作に合わないから、とのこと。
理由3. デプロイ時のイメージサイズが大きくなる
どうしても URLSession
を使用したい場合、libcurl4
というライブラリをインストールする必要があります。ですがそうするとイメージのサイズが大きくなってしまいます。
どのくらいサイズが大きくなるのかは、以下の Issue コメントで調べてくれた人がいました。🙏🏻
イメージが大きくなると、デプロイする時に時間がかかったり、ストレージを消費してしまったりするので、小さくできるならばした方が良いようです。
理由1 でも書いたように、Vapor が API を提供してくれているので、それを使えば良いかと思います。
やっぱり URLSession
を使いたい場合
それでも URLSession
を使いたい場合、以下を実施すればビルドが成功するようになります。
-
libcurl4
をインストール -
FoundationNetworking
をインポートする
手順1. libcurl4
をインストール
Dockerfileの RUN
にあるlibcurl4
のコメントアウトを外します。
これでうまく動くようになります。簡単ですね。
# Make sure all system packages are up to date, and install only essential packages.
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
&& apt-get -q update \
&& apt-get -q dist-upgrade -y \
&& apt-get -q install -y \
ca-certificates \
tzdata \
# If your app or its dependencies import FoundationNetworking, also install `libcurl4`.
libcurl4 \ # ここのコメントアウトを外す
# If your app or its dependencies import FoundationXML, also install `libxml2`.
# libxml2 \
&& rm -r /var/lib/apt/lists/*
コメントに FoundationNetworking をインポートする場合は libcurl4
をインストールしてね、と書いており親切だなと思いました。
この親切なコメントが入ったのは以下の Issue がきっかけのようです。感謝。
手順2. FoundationNetworking
をインポートする
次に FoundationNetworking
をインポートする必要があります。
URLSession
を使用するソースコードのファイルで、以下のように書きます。
Linux では URLSession
は FoundationNetworking
というモジュールに存在しているためです。
#if canImport(FoundationNetworking)
import FoundationNetworking
#endif
おわりに
URLSession
を使うのは避けるべきだということに気づいたのが、本番デプロイをするときになってしまったために、書き直すハメになってしまいました。。。が勉強にはなりましたし、この記事を書くきっかけになったので結果オーライだと思うことにしました!笑