おつかれさまです。すぺきよです。
今回はFlutterというかDartに関する小ネタです。
Dartでアプリからサーバー上のWeb APIを呼び出すときに、httpパッケージ(https://pub.dev/packages/http)を利用できます。
実際、自分で仕事上で開発しているアプリもhttpパッケージを利用して、Web APIとやり取りしています。
ところが、このhttpパッケージそのままだとUser Agentに少し問題があり、出力する内容を変更しなければあまり有効な情報になりません。
その際にUser Agentの設定方法を調べたのですがなかなか分からず、調べるのに意外と苦労したので、まとめます。
User Agentとは
この記事を読まれている方には、釈迦に説法だと思いますが、簡単に「User Agent」について解説します。
WebにおいてUser Agentというのは、ブラウザやアプリがWebサーバーやWeb APIにアクセスする際に送信する情報です。
例えば「私はWindows上で動いているChromeでアクセスしています。使っているバージョンは~です」といったような利用者の環境に関する情報が含まれていることが多いです。
この情報は、主に以下の目的で使われます:
- システム上で環境依存の問題が発生した際のトラブルシューティング
- リクエスト元がどのようなサービスか(例えば、GoogleのクローラーなどのBOTであるかどうか)の判定
実は、このUser Agentには厳密な書式の規定がなく、どんな文字列でも設定できます。例えば、自作のアプリでChromeを装ったり、iOSデバイスのふりをしてアクセスすることも可能です。
ただし、一般的な利用の範囲で嘘のUser Agentを設定してもメリットはなく、改変する人は少ないでしょう。
また、User Agentはユーザー側から送られてくるもので、必ずしも信用できる値ではありません。
そのため、システム運営者はこの値を過信したり、システムの動作切り替えの判断に使ってはいけません。
あくまでも参考程度にとどめるべきというのが私の考えです。
より詳細に知りたい方はMDN(https://developer.mozilla.org/ja/docs/Glossary/User_agent)やRFC 7231(https://datatracker.ietf.org/doc/html/rfc7231#section-5.5.3)をご覧ください。
環境
Dart
Dart SDK version: 3.3.3 (stable)
httpパッケージ
http 1.2.1
なぜUser Agentを変更する必要があるのか
なぜUser Agentを変更する必要があるかは、実際に出力される値を見ればわかっていただけると思います。
Dartでhttpパッケージをそのまま利用してアプリからWeb APIにアクセスすると、User Agentの値は以下のようになります。
Dart/3.3 (dart:io)
このように、出力されているのは動作しているDartのバージョンだけで、アプリのバージョンや、iOSやAndroidといった動作環境の情報は含まれていません。
この状態では、システム上で問題が発生してログを確認しても、User Agentからはほとんど役立つ情報を得ることができないでしょう。
そのため、動作しているOSやデバイス名などの情報をUser Agentに含めるには、カスタマイズが必要です。
どうやってカスタマイズするのか
では、肝心のUser Agentの変更方法です。
httpパッケージでgetやpostするときにヘッダーを明示的に指定し、User-Agentに対して直接文字列を指定します。
await http.get(
Uri.parse('https://domain/example/list'),
headers: <String, String>{'User-Agent' : 'set user agent here'}
);
この例では、http.get
関数の引数として、headers
にMap
を渡し、キー'User-Agent'
に送信したいUser Agentの値をセットしています。
User Agentはリクエストヘッダーの一部であり、他のヘッダーと同様にリクエスト時に指定できるため、簡単にカスタマイズ可能です。
カスタマイズ例
User Agentに出力するためにアプリやデバイスの情報を取得する必要があるため以下の便利な2パッケージを追加で利用します。
package_info_plus – アプリケーションのパッケージ情報を取得
device_info_plus – デバイスの詳細情報を取得
次に、これらのパッケージを利用してUser Agentを構築する関数を追加します。
この関数は、アプリのどこからでもアクセスできるようにグローバルな場所に配置するのが良いでしょう。
Future<String> buildAppUserAgentStr() async {
final PackageInfo packageInfo = await PackageInfo.fromPlatform();
final String packageName = packageInfo.packageName;
final String version = packageInfo.version;
final DeviceInfoPlugin deviceInfo = DeviceInfoPlugin();
String deviceInfoStr = Platform.operatingSystem;
if (Platform.isAndroid) {
final AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo;
deviceInfoStr += '(' + androidInfo.version.release;
deviceInfoStr += ';' + androidInfo.brand;
deviceInfoStr += ';' + androidInfo.model;
deviceInfoStr += ';' + androidInfo.device;
deviceInfoStr += ';)';
}
else if(Platform.isIOS) {
final IosDeviceInfo iOSInfo = await deviceInfo.iosInfo;
deviceInfoStr += '(' + iOSInfo.systemVersion;
deviceInfoStr += ';Apple';
deviceInfoStr += ';' + iOSInfo.systemName;
deviceInfoStr += ';' + iOSInfo.model;
deviceInfoStr += ';)';
}
return '$packageName ($version); $deviceInfoStr;';
}
この関数では、アプリのパッケージ情報とデバイスの情報を組み合わせて、カスタムのUser Agent文字列を作成します。
次に、http
パッケージのリクエスト時に、この構築したUser AgentをUser-Agent
ヘッダーに追加します。
await http.get(
Uri.parse('https://domain/example/list'),
headers: <String, String>{'User-Agent' : buildAppUserAgentStr()}
);
毎回関数を呼び出すのは非効率かもしれないので、アプリ起動直後にUser Agent文字列を作成し、グローバル変数に保存しておき、必要に応じて参照するようにするのも良いでしょう。
このカスタマイズにより、実際に出力されるUser Agentの例は次のようになります。
jp.app.example (1.0.0.1); android(13;samsung;SM-xxx;xxxxx;);
まとめ
今回はDartのhttpパッケージを使って、User Agentを任意の値に書き換える方法をご紹介しました。
この方法は基本的な内容ではありますが、公式ドキュメント(httpパッケージ)や他のリソースには具体的なサンプルが見つかりにくいため、調べるのに少し苦労しました。
この記事が、同じようにUser Agentをカスタマイズしたい方のお役に立てれば嬉しいです。