サーバサイドで User Agent Client Hints(UA-CH)
User Agent のパーサを書いていたりするので、そろそろ一回 User Agent Client Hints(UA-CH) にキャッチアップしておかねばと。
主にサーバサイドでの話です。
結論
2022/02時点で UA-CH に対応しているブラウザは Google Chrome と Microsoft Edge で、Safari や Firefox は対応していない。そしてしばらくは対応してこなさそう。なので、広くブラウザを限定せずに公開されているWebサイトにおいては、UA-CH に対応することで User Agent ヘッダを打ち捨てられるかというと、そんなことはなさそうです。とはいえ、Chrome あたりはアグレッシブに切り替えていく姿勢を打ち出して動いているので、UA-CHの仕組みを受け入れる体制は整えていくべきフェーズにあると思われます。
User Agent Client Hints(UA-CH)とは何か
というわけで User Agent Client Hints(UA-CH)とは何かですが、Webクライアントにおける、みんな大好き User Agent HTTP ヘッダをより洗練して、よりユーザのプライバシーが配慮され、より情報をハンドリングしやすくしようする仕組みのことです。先の結論でも書きましたが、いまのところ Google Chrome と Microsoft Edge が対応していて、Safari や Firefox は対応しておらず、対応することがあったとしてもしばらく時間がかかりそうというステータス。
Google Chrome でみる UA-CH の実体
百聞は一見に如かずということで Google Chrome で DevTools を開き、Network タブで何かしらのリクエストを見ると、Headers の項目に以下のような sec-ch-ua
ではじまるリクエストヘッダ群を見ることができます。
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="98", "Google Chrome";v="98"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
これらが User Agent Client Hints (UA-CH) の実体になります。
ちなみに、いまのところ、UA-CHヘッダが送られる時も、user-agent ヘッダは送られています。今後、user-agent string から情報の削減が予定されているようですが UA-CH が送られる時も、user-agentヘッダが削られることは無く送られ続けると FAQ にも書かれています。安心ですね。いまのところは、ですけど。
詳細情報をもらうためには accept-ch ヘッダ
さて、UA-CHに対応している Chrome や Edge は上記のような3行のヘッダでクライアントの情報を送ってきますが、さすがにこれだけで処理をどうこうするには情報不足ですよね? Windows OS で、モバイルではないことと、バージョンくらいしかわからない。
これは、UA-CH ではさらなる詳細情報は一度レスポンスヘッダ(accept-ch)で指定されないと、リクエストに含まれてこないことになっているのです。クライアント情報をいつも詳細に渡していると、フィンガープリントに組み込まれて追跡されやすくなったりするのを避けるためらしいです。でも、結局手に入っちゃうんですけど。
というわけで、もっと詳細なクライアントの情報が欲しい場合は以下のように accept-ch ヘッダを返します。
accept-ch: Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Model, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version
accept-chヘッダを渡すと、次のレスポンスには指定したヘッダの情報込みで返ってくるというからくりです。
sec-ch-ua: " Not A;Brand";v="99", "Chromium";v="98", "Google Chrome";v="98"
sec-ch-ua-arch: "x86"
sec-ch-ua-bitness: "64"
sec-ch-ua-full-version: "98.0.4758.82"
sec-ch-ua-full-version-list: " Not A;Brand";v="99.0.0.0", "Chromium";v="98.0.4758.82", "Google Chrome";v="98.0.4758.82"
sec-ch-ua-mobile: ?0
sec-ch-ua-model
sec-ch-ua-platform: "Windows"
sec-ch-ua-platform-version: "10.0.0"
ちなみに上記の accept-ch と sec-ch-ua群は youtube.com with 私のchrome での実例です(google.comはもちっと控えめ)。
UA-CHの実装
UA-CHの大枠がわかったところで、accept-ch を返すサーバを用意して、ざっと Chrome で試してみました。
accept-ch ヘッダをつけてレスポンスした後、そのページで実行される同一ドメイン内のXHRのリクエストに対しては accept-ch ヘッダをふたたび返す必要は無いようですが、ページが読み込みなおされる場合は、accept-chヘッダを都度 返し続ける必要があるようです。セッションクッキーのように永続化されるのかなと予想してましたが、ひとたびXHR以外のいわゆるページ読み込みのリクエストに対して accept-chヘッダを含まないレスポンスを返すと、即座に詳細情報は送ってこなくるようでした。また、script src
や link rel
で読み込むファイルへのリクエストには土台のページで accept-ch を返していても、デフォルト以外の sec-ch-ua ヘッダを返してくることは無いようです。
その後、追試験してみた結果、accept-chヘッダの値は、一度レスポンスに含めれば、セッションクッキーと同じようにブラウザのウィンドウを閉じるまで有効なようです。あたらしい値を持った accept-ch が返ると、次のリクエストから新しい値にそったSec-CH-UA に切り替わる。そしてすべてデフォルトに戻したい場合は、値をもたない accept-ch を返すと良いようです(当初自分が挙動を勘違いしたのは、この値無し accept-chを返してしまっていたっぽい)。
ちょっと細かい話をしてしまいましたが、XHRを受け付ける API 系のエンドポイント以外ではすべて accept-ch ヘッダを含めるように実装してしまうのが雑に良さそうです。
Plackアプリなら便利な middleware がありますね。
use YourApp;
use Plack::Builder;
my $app = YourApp->new;
builder {
enable_if { $_[0]->{CONTENT_TYPE} !~ m!json! } 'Header',
set => [
'accept-ch' => 'Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Model, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version',
],
$app;
};
もっと固定的にやるならたぶん nginx とかミドルウェアでやっちゃう方が効率良さそうではあります。
UA-CH をハンドリングする
というわけで、せっかくここまで調べたので、HTTP::UserAgentClientHints という UA-CH をハンドリングするための Perlモジュールをこさえてみました。いずれ Duadua に組み込んでいく予定。
サーバサイド UA-CH まとめ
サーバサイドで UA-CH の情報を得られるようになるのは比較的簡単でしたが、情報をいかに扱うかについては判断が少々難しい気がします。user-agent 時代はアクセスログにまるっと記録されてきたと思いますが、UA-CHまわりはどこにどう保存するのが良いかは判断が難しいところですね。あと、そもそもの懸念として、結局みんな常に全情報を求めて取得してしまうようになるのではないかというのがあります。なにせまじめに取得する情報を最小限にとどめるように都合をつける実装の方が大変なのですから。そうなると、user-agent stringよりも手間が増えたわりに何が改善されたのか謎な感じですが、まあその辺は行く末をみないとわからないですね。そもそもこのごろはサーバサイドでプリミティブにクライアント情報を利用する場面が減っている気がしたりしますし。
あと、PC向けの主要サイトをちらちら見てみた限り、google.com か youtube.com か msn.com 以外で accept-ch 返してくるサイトは見つかりませんでした。どっこい普及するんでしょうか。