ぼちぼち日記

おそらくプロトコルネタを書いていることが多いんじゃないかと思います。

Chrome Beta for Android で Client Hints を試す

この記事は、HTML5 Advent Calendar 2013の17日目の記事です。

1. デバイスピクセル比の悩み

最近 Retina を始めとした高解像度ディスプレイの普及と進歩は凄いものがあります。私はデザインの才能が無いのでフロントエンドの設計をする機会はほぼないでしょうが、Webデザイナーの方は次々販売される様々なクライアントの高解像度ディスプレイ対応を気にしないといけないのは本当に大変だと思います。

今回初めて知ったのですが、この高解像度ディスプレイの対応をするにあたり「デバイスピクセル比」という値が重要な意味を持つようです。詳しくは、こちらのブログ、「いまさら聞けないRetina対応のための「ピクセル」の話」 を参照していただくか、「デバイスピクセル比」で検索するといっぱい記事が出てきます。

このデバイスピクセル比の対応で大変なのは、

  • バイスピクセル比は機種によって違う(現状1.0〜3.0までバラバラ)。
  • バイスピクセル比をあらかじめ知ることができない(User-Agent名などで調べないといけない)。
  • バイスピクセル比によって画像のサイズなどを変えないとレイアウトが崩れる場合がある。

といったことの様です。(実際に自分でWebのデザインしていないので実感がないのですが、ホント大変そうです。)

この問題を解決するために、W3Cpicture要素srcset属性JavaScriptの window.devicePixelRatio などの検討を進めていますが、まだ一部で実装が始まったばかりでコンテンツの変更も必要な事から、問題解決までにはまだ課題があるようです。

2. Client Hints とは?

Client Hints とは、このデバイスピクセル比等クライアント側のデバイス情報を HTTP のリクエストヘッダに記載してサーバに通知してあげようとの仕組みです。Google の Developer advocate である Ilya Grigorik さんが仕様化を進めています。
HTTP Client-Hints (Internet Draft)
その仕組みは、このブログ 「Automating DPR switching with Client-Hints」 に詳しいですが、ざっと簡単に説明すると、

  1. クライアントデバイスの情報を送るHTTPリクエストヘッダとして、新たに CH- プレフィックスをついたヘッダを使う。
  2. 現状 デバイスピクセル比情報を送る CH-DPR と デバイスリソースの幅(Resource Width)を送る CH-RW の2種類が規定されている。
  3. サーバは、クライアントから受け取った CH-XXX 情報を元に、クライアントデバイスに最適なリソースを送信する。
  4. サーバは、レスポンスヘッダに受け取った情報(CH-DPRなら DRPヘッダにその値)を付与し、キャッシュの影響を受けないよう CH-XXX 情報を Vary ヘッダに追加してクライアントに返す。

ちょうど図に表すと以下のような感じです。

従来このようなコンテンツニゴシエーションの仕組みは、Accept や Accept-Encoding ヘッダ等で行われてきましたが、今回新しく CH-XXX ヘッダを作りクライアントデバイスに特化した情報を、クライアントからサーバに送ろうという提案です(まだドラフトの段階なので今後どうなるかはわかりません)。これを使うと、サーバ側がクライアントのデバイスピクセル比判断してリソースを出しわけてくれるので、クライアント側は意識せずに済むと言ったメリットがあります。サーバ側が動的に画像の生成・変換などするサービスを運用していれば非常に有効な方法です。

3. Chrome Beta for Android で Client Hints を試す

Google が提案していることでもあり、さすがこういった新機能はすぐ Chrome に実装されました(Firefoxには提案中)。
Issue 24451003: Client Hints (Closed)
デスクトップパソコンでは、この CH-DPRを送るChrome拡張も用意されていますが、先日この Client Hints が実装されたバージョンが Chrome Beta for Android (32.0.1700.58) に降りてきました。本命のモバイルデバイス(Nexus4 デバイスピクセル比は2.0)で早速この Client Hints を試したいと思います。

尚、以下は試験的な機能の評価のために作業を行ったことを記述したものです。これを真似て発生した一切の責任を負いかねますのでご注意下さい。

3-1. クライアント側の準備

Client Hintsを有効にするには、Chrome 側ではコマンドラインオプションで --enable-client-hints が必要です。Androidバイスでは直接オプション指定して Chrome が起動できませんので、 /data/local ディレクトリに chrome-command-line ファイルを作成して指定します。

root@mako:/data/local # ls -l
ls -l
-rw-rw-r-- root     root           29 2013-12-13 17:05 chrome-command-line
root@mako:/data/local # cat chrome-command-line
cat chrome-command-line
chrome --enable-client-hints
root@mako:/data/local #

Chrome Beta for Android を起動して、 about://version でオプションフラグを確認してみます。 --enable-client-hints が記載されていれば成功です(下図赤線)。

尚 SPDY Proxy を有効にして試験したところ、画像の読み込みが極端に遅くなることがありました。Google の SPDY Proxy サービスとなにか相性が悪いのでしょうか、今回は SPDY Proxy の機能を外して試験しました。 (← サーバ側のコードが間違ってました。 jpg 画像なのに content-type を png にしてたのが問題でした。ごめんなさい Google さん。)
また、ピンチズームやマルチディスプレイなどの対応はまだの様です。

3-2. サーバ側の準備

Client Hints を持つHTTPリクエストを受けるため Node.jsを使った Client Hints 対応サーバを作りました。画像は Ilya Grigorik さんが試験サーバ上で公開しているパンダの画像3種類(1X, 1.5X, 2.0X)を使いました。
https://github.com/igrigorik/http-client-hints/tree/master/server/ch-dpr-server/assets
Node.js サーバコードは以下の通りです。 リクエストヘッダの ch-dpr 値を見て、3種類の画像を出しわけています。そしてレスポンスに必要なヘッダ(CDR, Vary)を加えているだけです。

// Client Hints デバイスピクセル比 対応サーバ
var http = require('http');
var fs = require('fs');
// デバイスピクセル比に応じて3種類の画像を用意
var image_1x = fs.readFileSync('./assets/photo-1.0x.jpg');
var image_1_5x = fs.readFileSync('./assets/photo-1.5x.jpg');
var image_2x = fs.readFileSync('./assets/photo-2.0x.jpg');
var server = http.createServer(function(req, res) {
  var content,content_length,content_type;
  var dpr =  req.headers['ch-dpr']; // Client Hintsのデバイスピクセル比を取得
  switch(req.url) {
  case '/photo.jpg':
    content_type = 'image/jpg';
    if (dpr === undefined) {
      content = image_1x;
      content_length = image_1x.length;
      break;
    }
    dpr = parseFloat(dpr);
    // デバイスピクセル比に応じて送信する画像を分ける
    if(dpr>= 2.0) {
      content = image_2x;
      content_length = image_2x.length;
    } else if ( dpr >= 1.5) {
      content = image_1_5x;
      content_length = image_1_5x.length;
    } else {
      content = image_1x;
      content_length = image_1x.length;
    }
    break;
  default:
    content_type = 'text/html';
    var dpr_msg  = dpr ? dpr: 'No Device Pixel Ratio in Client Hints';
    content = '<!doctype html><html><head>Client Header</head><body>'
        + '<h1>Device Pixel Ratio:' + dpr_msg + '</h1>'
        + '<img src="photo.jpg" /></body></html>';
    content_length = Buffer.byteLength(content);
  }
  if (dpr) {
    res.setHeader('dpr', dpr); // 受け取った DPR 値を返す
    res.setHeader('vary', 'ch-dpr'); // キャッシュに影響されないよう vary ヘッダを返す
  }
  res.writeHead(200, {
    'content-type': content_type,
    'content-length': content_length
  });
  res.end(content);
});
server.listen(80, function() {
  console.log('Listening on port 80');
});

4. Client Hints でどう変わる?

さぁ実際にどうなるか見てみましょう。

4.1 Chrome Beta for Android with Client Hints

Nexus4(DPR 2.0) の Chrome for Android(32.0.1700.58) で Client Hints を使ったらこのように大きな画像(2X)になりました。

4.2 Chrome for Android without Client Hints

ちなみに Client Hints に対応していない Chrome for Android(31.0.1650.59:ベータでない)を使うとこのように小さな画像(1X)になります。

今後この仕様がどのように展開していくのかわかりませんが、今まで viewport やら CSS やらで苦労してきたことが、Client Hints を使って HTTP のプロトコルレベルでの対応することで、少し(かなり?)は楽になるということです(完全に楽になるかはわかりませんが)。

でもこれも「Webの進化がプロトコルを変えつつある」という一つの例じゃないかと思います。