【Flutter】2つのHTTPクライアントパッケージの比較
HTTP パッケージ
アプリから HTTP リクエストを送る際の選択肢には、以下の2つのパッケージが候補に挙がります。
あまり真っ向から比較した記事が見当たらなかったので、本記事ではいくつかの項目で比較していきたいと思います。
結論から言うと、どちらも優れたパッケージで互いに大きな差異はありません。
おそらく多くの人が想像する通り、http はパッケージとしての信頼性の面で軍配が上がり、dio は便利さで少々軍配が上がります。
dio にできて http にできないことは、ほぼないと言って良いので、http のみで機能を実現したい場合でも十分におすすめできると考えています。
開発元の信頼性
dio
元々、個人が開発していたものが開発困難になったため、flutter.cn に移行されました。
公式ではなかったり、Flutter Favorite ではないのが少し気にはなりますが、一度開発困難になったものが移行された背景があることを考えれば、十分と言えるでしょう。
http
公式の dart.dev チームがメンテナンスを行なっています。
さらに公式のドキュメントによると、公式チームの中でも最上位の優先度にあると明言されているので、パッケージとしての安心感で言えば最高峰です。
機能の重要性を考えると、開発がストップすることはまずないでしょう。
結論
圧倒的に http の信頼性が高いですが、dio も十分と言えます。
CancelToken
HTTP通信中に、たとえばウィジェットが破棄されたりした時に、リクエストを中断できる機能です。
dio
クラスでしっかりと機能が用意されています。
class _HogeState extends State<Hoge> {
final _cancelToken = CancelToken();
late final _future = Future(() async {
return Dio().get(
'hoge.com',
cancelToken: _cancelToken,
);
});
void dispose() {
// _future 実行中にウィジェットが dispose されれば、HTTP リクエストが中断される。
_cancelToken.cancel();
}
}
http
CancelToken
はありませんが、リクエスト時に生成する Client
クラスに close
が用意されているので、そちらを使えば実現可能です。
class _HogeState extends State<Hoge> {
Client? _httpClient;
late final _future = Future(() async {
_httpClient = Client();
return _httpClient!.get(
'hoge.com',
);
});
void dispose() {
// _future 実行中にウィジェットが dispose されれば、HTTP リクエストが中断される。
_httpClient?.close();
}
}
ですが、若干取り回しづらいので、もし dio のように、CancelToken
を渡す形に変えたい場合は自前でクラスをつくる必要があります。
しかし、CancelToken
の構造自体は、Completer
さえ知っていれば理解できるシンプルな構造なので、そこまで難易度は高くないように思います。
もしくは、関数で渡せるようにするかです。
Future<Response> get({
required Uri uri,
void Function(Client client)? onClientCreated,
}) async {
final client = Client();
onClientCreated?.call(client);
return client.get(uri);
}
Clinet? _httpClient;
final response = await get(
uri,
(client) { _httpClient = clinent; },
);
結論
どちらでも実現可能だが、dio の方が取り回しがしやすい構造になっている。
http でもそこまで実現は難しくない。
Interceptor
リクエストの実行前や、レスポンスの受け取り直後に処理を挟み、値の加工やヘッダの追加などを行える機構。
dio
Dio
クラス自体に用意されています。
自前で実装できるのはもちろん、ログを表示させるものなど、いくつかすでに用意されているものもあります。
final dio = Dio()
..intercepters.addAll([
LogInterceptor(),
MyAuthInterceptor(token: token),
]);
class MyAuthInterceptor extends Interceptor {
MyAuthInterceptor({required this.token});
final String? token;
void onRequest(
RequestOptions options,
RequestInterceptorHandler handler,
) {
if (token != null) {
options.headers['Authorization'] = 'Bearer ${token}';
}
super.onRequest(options, handler);
}
}
加えて、dio には、クッキーにブラウザと同等の動きをさせる拡張パッケージが存在します。
dio_cookie_manager
しかし、クッキーの保存先に path_provider が推奨されていたりするので、使うかどうかは慎重になった方がいいかもしれません。
http
こちらで Intercepter を利用する場合は、後述する、自動生成パッケージの chopper を使用する必要があります。
final client = ChopperClient(
// ...略
intercepters: [
HttpLoggingInterceptor(),
MyAuthInterceptor(token: token),
],
);
class MyAuthInterceptor implements Interceptor {
MyAuthInterceptor({required this.token});
final String? token;
FutureOr<Response<BodyType>> intercept<BodyType>(Chain<BodyType> chain) {
final request = applyHeader(chain.request, 'Authorization', 'Bearer $token');
return chain.proceed(request);
}
}
chopper にも、dio 同様ログを表示するものなどは用意されています。
結論
自動生成パッケージを採用しない場合は、dio でないと実現できない。
採用する場合、Intercepter は実現可能。
dio_cookie_manager を使いたい場合は、dio を採用する。
自動生成パッケージ
REST API のメソッド、パス、クエリパラメータ、ボディなどをコード生成によって最小限の記述で実現するためのパッケージ。
dio
retrofit という有名なパッケージが存在します。
多く記事が落ちてると思うので、使い方などは割愛します。
http
こちらにも chopper という Flutter Favorite のパッケージが存在します。
JsonConverter や Form リクエストへの対応など、retrofit にできることは可能で、特有の問題にぶち当たったことは自分あはりません。
retrofit ほどメジャーじゃない感覚があるので、記事はそこまで多くないように思います。
retrofit + Intercepter などの dio 特有の機能というイメージです。
郵便番号APIを叩いている実装例を置いておきます。
結論
どちらでも実現は可能。
retrofit(dio) の方がメジャーな印象があるので、チームのキャッチアップの面やAIのコード生成には強いかもしれない。
preserveHeaderCase
パラメータ
前提として、HTTP のヘッダのキーには、case-insensitivity(大文字小文字を区別しない)という設計思想があります。
逆に言えば、API の設計者は、ヘッダーの大文字小文字は、両方ともカバーするように実装しないといけないということです(ほとんどの場合は、SDK がそのようにやってくれている)。
そして、Dart では、ヘッダのキーが全て小文字に変換されます。
つまり、Authorzation
でも authorization
でも autorization
に変換されます。
もし、叩く予定の Web API の実装者が case-insensitivity を守れておらず、大文字でしか受け入れていない場合は、この機能は邪魔になってしまいます。
dio
preserveHeaderCase
パラメータで、勝手に小文字に変換される機能をオフにできます。
http
パッケージでは意図的に preserveHeaderCase
を取り入れられていません。
Issue にて議論は少しだけ行われていますが、オープンのままです。
Add support for
preserveHeaderCase
· Issue #609 · dart-lang/http
結論
dio では実現可能だが、http では完全に実現できない。
ただし、HTTP の思想に則ればそもそもこの壁にぶつかる可能性は低い(自分は1度ぶち当たったことありますが…)。
リトライ
使う人は少ないと思いますが、処理が失敗した時に何度か再実行する機構です。
dio
dio_smart_retry というサードパーティのパッケージが存在します。
http
google 製のパッケージが存在しますが、2年ほど更新が止まっていそうです。
結論
どちらもパッケージは存在する。
エラーの扱い
dio
200 台のステータス以外はエラーを throw して処理を中断する仕様が、デフォルトで入っています。
bool _defaultValidateStatus(int? status) {
return status != null && status >= 200 && status < 300;
}
これを防ぎたい場合は、Dio
インスタンス生成時に、validateStatus
を上書きすることもできます。
final options = BaseOptions(
// どんなステータスコードが来ても、「成功」とみなす条件式
validateStatus: (_) => true,
);
final dio = Dio(options);
http
validateStatus
みたいな機能はなく、ステータスコードによって勝手に throw されることはありません。
結論
扱いは微妙に違うが、大きな差はない。
まとめ
preserveHeaderCase
という特殊な例を除けば、どちらでも十分に開発は可能だと思います。
ただ、http で実装する場合は少しだけ工夫することがあるので、スピードやパッケージの安定性を重視しない場合は、dio が良さそうです。
ですが、http x chopper もかなり信頼がおける組み合わせで、機能も十分に揃っています。
長く続くプロダクトで、安定性を重視する場合、キャッチアップの多少のコストを重視しない場合は、かなりいいチョイスだと思います。
Discussion