ぼちぼち日記

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

Service WorkerとHTTP/2が切り開く新しいWeb Pushの世界

この記事は、HTTP2 Advent Calendar 2014の6日目のエントリーです(2日前にフライイング公開してます)。

1. はじめに、

HTTP/2仕様の標準化作業は、WGラストコールも終わり、今後IESGレビューやIETFラストコール等の大詰めの段階に来ました。来年のRFC化に向けてまだまだ予断を許しませんが、プロトコル設計自体の作業はほぼ完了し、後はすんなり行くことを祈るばかりです。

こんな状況なのに気が早いですが、もう既に標準化後を見据え、HTTP/2の機能を使った新しい仕組みを作る動きが始まっています。

そこで今回はHTTP/2技術の応用として、HTTP/2の「サーバプッシュ機能」と今ホットなブラウザの新技術「Service Worker機能」を組み合わせた次世代のプッシュ機能「Web Push/Push API」について書いてみたいと思います。
ただ、個人的に色々タスクがいくつか立て込んでいて、アドカレのブログを書いている場合じゃないとひどく怒られそうなので、手短に内容を書いていることをお許し下さい(担当者様ごめんなさい)。 と書きつつ長くなってしまった(汗)…

(注意事項) Web Push/Push API仕様ともに現在仕様策定中です。今後の仕様変更で本記事の内容が変わる可能性が大きいです。その点十分ご留意してお読みください。

2. Web Push の概要

まず初めにWeb Pushはどのような仕組みで行われるのか。その概要を一枚の図にしました。ちょっとごちゃごちゃ説明文を図に書いていますが登場人物は3人、

  • クライアント(ブラウザには、Webアプリケーション(Web App)、Service Worker、Push APIが動いています)
  • Webアプリケーションのサーバ(app.example.jp)
  • プッシュサーバ(push.example.jp)

です。

ざっとした流れは、

  1. クライアントからプッシュサーバの登録情報(endopoint, registrationID)をアプリサーバに通知。
  2. アプリサーバが、プッシュ通知をHTTP PUTのリクエストボディに付けてプッシュサーバに送信。
  3. プッシュサーバは、プッシュ通知をチャネルで区別し、送信クライアントを選定。
  4. プッシュサーバは、HTTP/2のサーバプッシュやGCM(Google Cloud Message)など利用してクライアントにプッシュ通知を送信。プロトコルは別にWebSocket/SSEでも構いません。
  5. クライアントは、プッシュサーバからプッシュ通知を受けると、Service Worker上でPushイベントが発生。ArrayBuffer, blob, json, textの形式でプッシュデータを取り出せるようになる。
  6. プッシュされたデータはキャッシュ更新なり、postMessageでDOMに渡したり、クライアント上でいかようにでも処理することができる。

といった段取りになります。*1

Web Pushは、クライアントとプッシュサーバ間をHTTP/2で接続し、クライアント上のService WorkerがHTTP/2サーバプッシュを介してプッシュ通知を受ける仕組みです。*2

次に、Web Pushで使われる2つの基本技術、HTTP/2サーバプッシュとService Workerについて軽く解説します。

3. HTTP/2サーバプッシュ機能とは、

SPDYやHTTP/2の目玉機能の一つに、サーバプッシュ機能が挙げられます。
サーバプッシュは、サーバ側がリクエストを受けるとその後に続くリクエストを先取りし、サーバ側から画像等のコンテンツを送り込む機能です。送り込まれたコンテンツは、クライアントのキャッシュ領域に保持されます。実際にサーバへリクエストが行われることなく、クライアントはキャッシュ領域からコンテンツが読み込みます。サーバプッシュによってクライアントのリクエストが減り、Webの表示が更に高速化されることが期待されています。

HTTP/2では従来のSPDYのサーバプッシュの仕組みを改良し、新しくPUSH_PROMISEというフレームが新設されました。このPUSH_PROMISEフレームは、サーバからコンテンツを送るストリームを事前に予約することができるため、SPDYより柔軟なタイミングでコンテンツを送り込むことが可能になりました。

サーバプッシュはHTTP/1.1の時代にはない新しい仕組みです。その効果は非常に期待されているのですが、まだSPDYでも実環境で十分使われているわけではありません。サーバプッシュ機能を十分に活用するのは大変で、これからGoogleTwitter等の大手がサーバプッシュを試験するページやアプリを増やしていくでしょう。もし chrome://net-internals/#spdy の出力で偶数番号のストリームを見かけたらサーバプッシュが使われているものと思ってください。今後どういう場面でサーバプッシュが効果的に使われるのか注目です。

4. Service Worker とは、

Service Workerは、最近動きのあるブラウザテクノロジーの中で最もホットな新機能の一つです。ネットワークプロキシとしても働くService Workerは、従来のAppCacheの欠点を克服し、真のオフラインファーストを実現できる技術として期待されています。
Service Workerの詳細を書くと記事の分量が膨大になってしまいますし、時間的にもちょっと無理です。英文ですがちょうど先日Service Workerに関するHTML5Rocksの記事が公開されましたので、興味のある方はこちら「Introduction to Service Worker」をお読みください。(誰かHTML5のアドカレで書いてくれないかなと期待してます)
各種ブラウザの実装状況等はこちらhttps://jakearchibald.github.io/isserviceworkerready/で見ることができます。

そんな今大注目の Service Workerですが、実はAppCacheの代用用途だけでなくバックグラウンドのコンテンツ同期やプッシュ通知などの応用が検討されています。しかもService Worker の通信は、セキュリティ上の観点からHTTPS通信が必須化されています。これはHTTP/2の利用環境としては最適です。さらにHTTP/2の仕様上では明示的にストリームのタイムアウトが規定されていないため、同時にオープンできるストリームを調整することによってプッシュサーバの大幅なスケールアップが図れます。
そういう背景から、HTTP/2のサーバプッシュ機能とService Workerを組み合わせた次世代のWeb Pushの仕組みの検討が始まりました。

5. Web Pushの背景とユースケース

5.1 Web Pushの標準化が始まる

今年の10月にIETFのraiエリア(Real-time Applications and Infrastructure)でwebpush ワーキングループ(Web-Based Push Notifications)が新設されました。
このWGの元々の始まりはWebRTCで相手にプッシュ通知を行う仕組みがないという動機からでした。その要件としてWebRTCのアプリが起動している時はもちろん、アプリのフォーカスが外れていたり未起動な場合、ネットワーク通信が切れている時など様々な場面に有効にプッシュ通知が働く機能が求められます。
この提言は大きな賛同を得られました。しかし、この要件は特にWebRTCだけに必要なものではないため、より一般的なWebアプリのプッシュ通知の仕組みを作ろうということなりこのWGが作成されました。
現在、プッシュ通知のプロトコル部分は IETF の webpush WGで、W3Cではブラウザ側のAPI仕様を検討する Push APIという役割分担がされています。Push APIの議論の様子や仕様ドラフトは https://github.com/w3c/push-api/https://w3c.github.io/push-api/ で見ることができます。

5.2 想定される Web Push のユースケース

では、Web Push はどういう場面で使われることを想定して仕様策定が進むのか?
Push APIの仕様ドラフトに記載されているユースケースを読んでみるとだいたいイメージが付きます。簡単に訳してみると、

  1. ユーザが現在webappを利用中で webappからプッシュ通知を受け取る場合
    • これは通常の使い方
  2. ユーザがwebappを利用していないが、ブラウザウィンドウやWeb Worker内でwebappが実行されていてプッシュ通知を受け取る場合
    • SNSやmessage,web feed等のプッシュ通知を受け取るような場合を想定。
  3. ブラウザウィンドウ内でもwebappが利用されていないが、プッシュ通知を受けたらwebappを起動できるようなことがあると良い場合
    • WebRTCの入電コールでWebRTCのwebappを立ち上がるような場合を想定。
  4. 複数のwebappが実行されていて、そのうち要求されたアプリにだけプッシュ通知をしたい場合
  5. 一つのブラウザ内で同じwebappが複数のインスタンスで実行されていて、特定のインスタンスに対してプッシュ通知をしたい場合
    • 複数のメールアカウントを違うウィンドウで立ち上げているような場合を想定。
  6. 異なるブラウザで同じwebappが複数のインスタンスで実行されていて、複数の特定インスタンスに対してプッシュ通知をしたい場合
    • 同一の電子メールアカウントを2つのブラウザで立ち上げているような場合を想定。
  7. 異なるブラウザで同じwebappが複数のインスタンスで実行されていて、全てにプッシュ通知をブロードキャストしたい場合

と7つのユースケースが挙げられています。
Web Pushは、複数のブラウザ、複数のアプリインスタンス、アプリの起動・停止中等様々な場面でもプッシュ通知が行えることが想定されています。

6. Service Workerで使うPush API

まずブラウザ側のフロントエンド開発者が実際に触るクライアント側のPush APIがどうなるのか見てみます。
大きくService Workerへのプッシュ機能の登録(register)、アプリサーバへの登録情報の通知(distribution)、プッシュ通知の受信の3つに分かれます。
Push API仕様に記載されているサンプルコード例をちょっと変えたものが下記です。Service WorkerはPromiseインターフェイスを持つので割と見やすく非同期処理が行えます。

// https://app.example.jp/serviceworker.js
this.onpush = function(event) {
  console.log(event.data);
  // ここでIndexedDBにdataを書き込んだり、開いているウィンドウにdataを送ったり、
  // Notificationを表示したりなどができる。
}

// https://app.example.jp/webapp.js
navigator.serviceWorker.ready.then(function(serviceWorkerRegistration) {
  serviceWorkerRegistration.pushRegistrationManager.register('/serviceworker.js').then(
    function(pushRegistration) {
      console.log(pushRegistration.registrationId);
      console.log(pushRegistration.endpoint);
      // ここではアプリケーションサーバがプッシュサービスへプッシュ通知を行うのに必要
      // な登録情報を利用できるようになりました。例えばXMLHttpRequest等を使って、
      // その登録情報をアプリケーションサーバに送信します。
    }, function(error) {
      // 開発中はコンソールにエラーログを出し修正の手がかりにしたりします。
      // サービス環境では、エラー情報をアプリケーションに通知したりするようにするのが
      // いいのかもしれません。
      console.log(error);
    }
  });
});

流れとして、

  1. pushRegistrationManager.register()を使ってServiceWorkerにプッシュ機能の登録する。
  2. 登録が成功したら endpoint(プッシュサーバのURL)と registrationIDが付与されます。これをアプリサーバ側にAjax等で送る。(この部分はコメント記述のみ)
  3. Service Worker側はプッシュ通知を受けると、Pushイベントが発生します。
  4. Pushイベントオブジェクトのdataプロパティに受信したデータが格納されています。 ArrayBuffer(), blob(), json(), text() のメソッドを使ってアプリサーバから送られた形式に変換します。

Push APIの仕様ではプッシュサーバの登録時には何かしら確認UIが出て、勝手に登録されないようになる予定です。

ここでは、一つ重要な手続きが抜けています。プッシュサーバの指定はどうするのか?
実はこれまだ未定です。現時点では明確に定められていません(Push API仕様にはいくつか方法が例示されています)。様々なプッシュ通知を集約して一元的に処理するプッシュサーバの機能はとってもビジネスな臭いがします。

8. HTTP/2 サーバプッシュを用いた Web Push の仕組み

さぁやっとHTTP/2アドカレ本来のテーマ、HTTP/2 サーバプッシュを用いた Web Push の仕組みです。以下に続く7つのステップでHTTP/2のサーバプッシュを用いたWeb Pushが動きます。
draft-thomson-webpush-http2-01Push API W3C Editor's Draft 03 December 2014をもとにしたWeb Pushの仕組みについて書きます。
繰り返しますがまだ仕様策定中で不明なところも多く、また試験実装も完了していないので、そこのところ留意してお読みください。

Step 1: 登録

クライアントの Service Worker がプッシュサーバに登録リクエストを POST で送ります。登録が成功したらサーバは 201(Created) のコードで monitor と subscribe のURLを返します。

Step 2: 接続

Step 1で返されたLocationヘッダから次に再度プッシュサーバへのGET接続をします。これはプッシュサービスを利用する継続用の接続で、サーバはレスポンスを返しません。ロングポーリング再びです。

Step 3: subscribe

アプリ毎にプッシュ通知を受けるChannelを作成してsubscribeします。Step 1で受信した subscribe URLへクライアントからPOSTします。プッシュサーバ上ではアプリ用のPush Channelが作成されます。今後このChannelを通じてメッセージのやり取りが行われます。

Step 4: monitor

クライアントからStep 3で作成した Push Channel をモニターするGETリクエストを送信します。URLはStep 1で得たものです。プッシュ通知を受ける継続用のリクエストなのでサーバはレスポンスを返しません。

Step 5: distribute

クライアントからアプリケーションサーバ側にプッシュサービスの登録情報を渡します。この登録情報は、アプリサーバがプッシュ通知を行うendpointのURLと認証用の登録ID(registrationID)の2つです。実はregistrationIDをどう生成して渡すのかまだ明確に規定されていませんが、プッシュサーバのsubscribe時に渡されるんじゃないかと想像しています。

Step 6: deliver #1

アプリケーションサーバはクライアントへプッシュ通知するイベントが発生したら、渡されたendpointとregistrationIDを使ってプッシュサーバのチャンネルにデータをPUTします。この辺の仕様の詳細は、仕様規定の範囲外になっています。

Step 7: deliver #2

プッシュサーバは、クライアントとの間のHTTP/2接続上でサーバプッシュを使ってプッシュ通知を行います。どんなURLにするのか詳細は未定ですが、おそらくプッシュ通知毎にサブチャネルのID(1)とかを振ることになるのでしょう。サーバプッシュのレスポンスボディ部にプッシュデータを含むことが規定されています。サーバプッシュによってプッシュ通知を受けたクライアントは、Service Worker上のpushイベントを通じてデータを入手します。

一連のWeb Pushの仕組みは以上です。

9. 現状の実装状況

最後に現状の実装状況を紹介します。

Chromeでは、GCMと連動したPush APIの実装が進んでいます(Intent to implement: Push API)。

Firefoxでは、Push APIの前身となるSimple Push APIの開発がされています(まだ実際に試していません)。
実はFirefoxでは、HTTP/2 サーバプッシュによるPush APIを作るためのとっかかりの実装が既に完了しています。(参考ブログ:「Firefox gecko API for HTTP/2 Push」
これは HTTP/2 のサーバプッシュから受けたデータをFirefoxの内部API nsIHttpPushListener で受けられるようにする実装です。nsIHttpPushListener は Addon 等でも使えるので Firefox36 以降で実際試してみることができるでしょう。普段ならここでサンプルAddonと実行結果でも載せるんですが、今回時間がないので勘弁してください(どなたか動作検証していただけると助かります)。今後この nsIHttpPushListener と Service Worker の実装を組み合わせて、Push APIを作っていくものと思われます。

HTTP/2とService Worker、この2つのホットな最新技術を組み合わせた次世代のWeb Pushの実現はもう少し先の世界ですが、これからが楽しみです。

*1:実際には後述するようプッシュサーバへの登録やsubscribeといった事前の手続きが必要になります。

*2:仕様をHTTP/2の利用に限定するかどうかは議論中です