server push

server push (HTTP)

[114] server push は、クライアントから明示的な要求がなくても、 予めサーバーから応答を送信するものです。

[121] 一番最初にフックとなるクライアントからサーバーへの要求が必要とはいえ、 通常の HTTP とは異なり、 サーバーが主体的にクライアントに情報を送信できる仕組みとなっています。

仕様書

意味

[35] HTTP/2 server push は、 応答とそれに対応する「約束 (promised) 」要求を、 先にクライアントが開始した要求に関連付ける形でサーバーから予め送信 (push) するものです >>34。

[36] server push は、クライアントが元の要求に対する応答を完全に処理するために当該応答が必要になるであろうとわかっている場合に便利です >>34。

[55] server push は、意味的にはクライアントの要求にサーバーが応答を返す通常の場合と同じですが、 要求をクライアントではなくサーバーが (クライアントへと) PUSH_PROMISE フレームにより送信します >>34。

[52] クライアントは、 push できません >>34。

約束要求

[44] サーバーからクライアントへと送信する要求を、 約束要求 (promised request) といいます。

[39] 約束要求は、要求本体を含んではなりません >>34。

[56] PUSH_PROMISE フレーム (と CONTINUATION フレーム) が、要求の header block を含みます。 要求のヘッダーを妥当かつ完全に含まなければなりません。 >>34

[49] サーバーは、当該サーバーが権限を有する (authoritative) :authority 疑似ヘッダー値を含めなければなりません >>34。

HTTP接続も参照。

[37] 約束要求は、キャッシュ可能でなければなりません >>34。

[38] 約束要求は、安全でなければなりません >>34。

[58] サーバーは、 :method 疑似ヘッダーに安全でキャッシュ可能な要求メソッドを指定しなければなりません >>34。

[61] サーバーは、約束された応答を参照するフレームを送信する前に、 PUSH_PROMISE フレームを送信するべきです。 これによりクライアントが PUSH_PROMISE フレームを送信する前に別途要求を発行することを防ぐことができます。 >>34

[62] 例えば DATA フレームに画像を埋め込むリンクが含まれているなら、 それよりも前に当該画像の PUSH_PROMISE フレームを送信することにできます。 >>34

[71] 複数の著者の資源を提供するサーバーは、 他の著者の管轄下のはずの資源をプッシュできてしまわないようにしなければなりません >>70。

[72] レンタルサーバーやクラウドサービスの逆串などで注意が必要です。

プッシュ応答

[48] 約束要求に続いてサーバーからクライアントへと送信する応答を、 プッシュ応答 (pushed response) といいます。

[65] サーバーは PUSH_PROMISE フレーム (と CONTINUATION フレーム) に続けて (同じストリームで) プッシュ応答を配送開始できます >>34。

文脈

[119] Webページは HTML文書から参照される CSS や JavaScript などいろいろなファイルの集合体として構成されますが、 その読み込みの最適化のため、 HTML文書の応答中に server push を使って他の構成ファイルが送信されることがあります。

[120] Web Push Protocol では、 プッシュメッセージ購読資源、 プッシュメッセージ購読集合資源、 受領証購読資源で server push が使われます。

処理

[60] クライアントは、約束要求に完全で妥当なヘッダーの集合が含まれていなければ、 ストリームエラー PROTOCOL_ERROR としなければなりません >>34。

[40] クライアントは、キャッシュ可能でない約束要求を受信したら、 ストリームエラー PROTOCOL_ERROR としなければなりません >>34。

[41] クライアントは、安全でない約束要求を受信したら、 ストリームエラー PROTOCOL_ERROR としなければなりません >>34。

[59] クライアントは、 :method 疑似ヘッダーに安全でない要求メソッドが指定されていれば、 ストリームエラー PROTOCOL_ERROR としなければなりません >>34。

[43] クライアントが新しい要求メソッドが安全と知っていなければ、 エラーになります >>34。

[42] クライアントは、要求本体の存在を示した約束要求を受信したら、 ストリームエラー PROTOCOL_ERROR としなければなりません >>34。

[50] クライアントは、サーバーが権限を有する (authoritative) かプロキシであって対応する要求に関するプッシュ応答を提供するよう設定されていることを検証しなければなりません。 それ以外の値の :authority 疑似ヘッダーが含まれていたら、 ストリームエラー PROTOCOL_ERROR としなければなりません >>34。

[69] DNS-ID または共通名として example.com が指定された証明書を使っているサーバーの場合、 https://www.example.org/doc のプッシュ応答を提供してはなりません。 >>34

HTTP接続も参照。

[45] プッシュ応答は、キャッシュ可能なら、 クライアントの HTTPキャッシュに蓄積できます >>34。

[46] プッシュ応答は、ストリームが open である間、起源サーバーで成功裏に検証されたものとみなします >>34。

[47] プッシュ応答は、キャッシュ可能でなければ、 HTTPキャッシュに蓄積してはなりません。 別途応用に提供しても構いません。 >>34

[51] 中間器は、サーバーからの push をクライアントへと転送しないことにしても構いません。 またサーバーから送られてこなくても独自にクライアントに push しても構いません。 >>34

[66] クライアントは、 PUSH_PROMISE フレームを受信し、 プッシュ応答を受け入れることにしたら、 そのストリームが閉じられるまでの間、 その応答に対する要求を発行するべきではありません >>34。

[67] クライアントは、何らかの理由でプッシュ応答を受信したくない時や、 サーバーがプッシュ応答を送信し始めるまでの時間がかかりすぎている時は、 RST_STREAM フレームに誤り符号 CANCEL や REFUSED_STREAM を指定し、当該ストリーム識別子を指定して送信できます >>34。

[73] サーバーが authoritative でない応答をキャッシュしたり、 使ったりしてはなりません >>70。

[74] authoritative かどうかの判定については、 HTTP接続の再利用の項を参照。

[75] 例えば example.com のサーバー証明書を持つ TLS接続上で https://example.net/ の応答がプッシュされても、 使ってはなりません。

[30] Firefox も Chrome も、異なる起源の約束要求を受信した時、 ストリームエラー REFUSED_STREAM とするようです。

[106] Firefox も Chrome も、この検査を (PUSH_PROMISE の受信時でも fetch 時でもなく) 応答の受信時に行うようです。

[111] 同じ :path の約束要求を受信したら、 Chrome はストリームエラー PROTOCOL_ERROR、 Firefox はストリームエラー INTERNAL_ERROR とします。この検査は PUSH_PROMISE 受信時点で行い、 約束ストリームに対して送信します。

[77] クライアントは、 reserved (remote) 状態にあるストリームの数を制限するべきです。 超過したらストリームエラー ENHANCE_YOUR_CALM とできます。 >>76

PUSH_PROMISE フレーム

意味

[9] PUSH_PROMISE フレーム (フレーム型 0x5) は、 peer に対してストリームを開始することを予め通知するものです >>8。

[23] PUSH_PROMISE によって予約する順序は、そのストリームを利用する順序と一致していなくても構いません >>8。

構文

[57] ストリーム識別子は、要求が属するストリームを表します >>34。 0x0 を指定することはできません。

[16] 次のフラグがあります。

[17] END_HEADERS (0x4 = 第2ビット)
設定されていれば、header block 全体が含まれており、 CONTINUATION フレームが続かないことを表します >>8。 [18] 設定されていなければ、同じストリームで CONTINUATION フレームが続かなければなりません >>8。
[20] PADDED (0x8 = 第3ビット)
設定されていれば、詰め長欄と詰めが存在することを示します >>8。

[80] 他のフラグは、0 でなければなりません。 受信者は、無視しなければなりません。 >>79

width
8
  1. 1 0
  2. 1 0
  3. 1 END_HEADERS
  4. 1 PADDED
  5. 1 0
  6. 1 0
  7. 1 0
  8. 1 0

[11] payload は、次の欄で構成されます。

[12] 詰め長 (Pad Length)
フレームの詰めの長さをバイト単位で指定する 8ビットの欄です >>8。 PADDED フラグが設定されている場合のみ存在します >>8。 値は0でも構いません。値が実際の詰めの長さを超えることは禁止されていませんが、 当然正しく処理できませんし、可能な長さを超えるなら接続エラーとなります。
[13] R
予約されている1ビットです >>8。
[10] 約束ストリームID (Promised Stream ID)
送信者がで予約するストリームの符号無し31ビットストリーム識別子を示します >>8 (ネットワークバイト順 >>78)。 送信者が作成する次のストリームの識別子として妥当なものでなければなりません >>8。 idle 状態でなければなりません >>8。 これは関連付けられた (associated) ストリームを表します >>8。 選択方法はストリーム識別子を参照。
[14] ヘッダーブロック素片 (Header Block Fragment)
要求ヘッダーを含んだヘッダーブロック素片です >>8。
[15] 詰め (Padding)
メッセージの長さを隠すためのセキュリティー機能です。 送信者は、すべて 0 のオクテットにしなければなりません。 >>29 詰め長の長さより、詰めの最大長は、255バイトです。

width
32
  1. 8 詰め長
  2. 1 R
  3. 31 ストリーム識別子
  4. 88... header block fragment
  5. 32... 詰め

文脈

[64] PUSH_PROMISE フレームは、 任意のクライアントが開始したストリームに対して、 サーバーが送信することができます >>34。

[21] peer が開始したストリームで状態が open か half-closed (remote) である場合にしか送信してはなりません >>8, >>34。

[86] 送信すると、指定した約束ストリームIDのストリームの状態は、 idle から reserved (local) へと遷移します >>83。

[3] 設定 SETTINGS_ENABLE_PUSH が 0 に設定されていれば、 PUSH_PROMISE フレームを送信してはなりません >>1, >>8。

[63] クライアントは、 PUSH_PROMISE フレームを送信してはなりません >>34。

処理

[81] 受信者は、payload が短すぎるか、設定 SETTINGS_MAX_FRAME_SIZE より長いなら、接続エラー FRAME_SIZE_ERROR としなければなりません >>82。

[4] 設定 SETTINGS_ENABLE_PUSH を 0 に設定し、 acknowledge されたエンドポイントは、 PUSH_PROMISE フレームを受信したら、 接続エラー PROTOCOL_ERROR としなければなりません >>1, >>8。

[22] ストリーム識別子が 0x0 なら、 接続エラー PROTOCOL_ERROR としなければなりません >>8。

[108] 未使用のストリーム識別子の PUSH_STREAM を受信すると、約束ストリーム識別子のストリームの側で Firefox はストリームエラー PROTOCOL_ERROR、 Chrome はストリームエラー STREAM_CLOSED とするようです。ただし約束ストリーム識別子が 0 なら、接続エラー PROTOCOL_ERROR とするようです。

仕様上は接続エラー PROTOCOL_ERROR。ストリームの状態遷移参照。

[109] 要求送信済みで HEADERS 受信前のストリーム (PUSH_PROMISE で作成されたストリーム含む。) のストリーム識別子で PUSH_PROMISE を受信したら、 Firefox も Chrome もエラーとはしません。

[110] END_STREAM 受信済みのストリームで PUSH_PROMISE を受信したら、 Firefox は接続エラー PROTOCOL_ERROR、 Chrome はストリームエラー PROTOCOL_ERROR とします。

[28] 約束ストリーム識別子が idle 状態でないなら、 接続エラー PROTOCOL_ERROR としなければなりません >>8。

[93] 明記されていませんが、送信者が開始できないストリーム識別子や 0の場合も同様に扱うべきと思われます。Chrome や Firefox はそのようにしています。

[26] 受信者は、ストリームの状態が open でも half-closed (local) でもなければ、接続エラー PROTOCOL_ERROR としなければなりません >>8。

[27] ただし、RST_STREAM を送信した後 (の closed 状態) には、 受信した PUSH_PROMISE を処理しなければなりません >>8, >>83。

が十分な時間の経過後はエラーとしても構いません >>83。 ストリームの状態遷移の項を参照。

[32] 受信者は、詰めに 0 以外があれば接続エラー PROTOCOL_ERROR としても構いません。 >>29

[33] 受信者は、詰め長が可能な長さより長ければ、 接続エラー PROTOCOL_ERROR としなければなりません >>29。

[24] 受信者は、約束されたストリーム識別子を参照する RST_STREAM フレームを返送することで、 約束ストリームを拒絶することができます >>8。

[84] PUSH_PROMISE フレームを受信すると、 その約束ストリームIDのストリームは、idle 状態から reserved (remote) 状態へ遷移します。 >>83

[85] 実際には約束ストリームIDで指定されたストリームは存在していないはずですから、 作成して初期状態の idle 状態となり、直ちに reserved (remote) 状態に遷移します。

[88] 約束ストリームIDのストリームは、ストリーム識別子のストリームに (重みは既定値で) 依存性を持つ状態となります >>87。

依存性木を参照。

[25] ストリームの状態遷移の項、ヘッダーリストの項も参照。

[19] END_HEADERS フラグが設定されていない場合、 次のフレームが CONTINUATION で無いか、 違うストリームなら、接続エラー PROTOCOL_ERROR としなければなりません >>8。

[53] サーバーは、PUSH_PROMISE フレームを受信したら、 接続エラー PROTOCOL_ERROR としなければなりません >>34。

[107] PUSH_PROMISE のヘッダーが奇形な場合、 Chrome はエラーの種別に応じて接続エラー COMPRESSION_ERROR または約束ストリームのストリームエラー PROTOCOL_ERROR、 Firefox は接続エラー PROTOCOL_ERROR とするようです。

関連

[122] JavaScript の Promise とは無関係です。

設定

[2] SETTINGS_ENABLE_PUSH (0x2) は、 server push を無効化するために使うことができます。 >>1

[5] 値 1 は、 server push を認めることを表します。これは初期値です。 >>1

[6] 値 0 は、 server push を認めないことを表します >>34。

[7] 1 と 0 以外の値は、接続エラー PROTOCOL_ERROR としなければなりません >>1。

[54] クライアントは、 SETTINGS_ENABLE_PUSH が 0 以外の値に設定されようとしたら、 拒絶して接続エラー PROTOCOL_ERROR としなければなりません >>34。

[101] 1 が初期値なので、クライアントは最初エラーになるべき値が設定されていることになりますが、 RFC ではこの点に特に説明はありません。。。

[102] 実際には Chrome も Firefox も、値の検査をしていないようです。

[68] クライアントは、設定 SETTINGS_MAX_CONCURRENT_STREAMS によってサーバーが並行してプッシュできる応答の数を制限できます。 0 を設定することで、server push のためのストリームの作成を抑制できます。 ただし PUSH_PROMISE フレームの送信を禁止するものではありません。 >>34

fetch と server push

[92] Chrome や Firefox は、 fetch に当たり PUSH_PROMISE を受け取っていれば、新たに要求を送信せずにその応答を待つようです。 しかし fetch の呼び出し方法 (やタイミング?) によって別途要求を送信することもあります。

歴史

前史

[127] Improving HTTP Latency, , https://web.archive.org/web/20010603065233/http://archive.ncsa.uiuc.edu/SDG/IT94/Proceedings/DDay/mogul/HTTPLatency.html

HTTP/2

[89] IIS 10.0 と ASP.NET 4.6 で HTTP/2 のサーバープッシュが使えるようになっていたので試した - しばやん雑記 ( 版) http://blog.shibayan.jp/entry/20150506/1430846908

[90] HTTP/2 server push support · Issue #51 · whatwg/fetch ( 版) https://github.com/whatwg/fetch/issues/51

[91] HTTP/2 Push + browser cache - Google ドキュメント ( 版) https://docs.google.com/document/d/1v3rjj0DMDTocUtZSjOwdwt8D-yhCw6R5SVaax4MPgMc/edit?pli=1

[94] Issue 135485 - chromium - SPDY - Pushed stream - crash accessing https://jetty.intalio.com:10111/spdy - An open-source project to help move the web forward. - Google Project Hosting ( 版) https://code.google.com/p/chromium/issues/detail?id=135485

[95] Issue 377538 - chromium - Robust support for HTTP/2 server-push - An open-source project to help move the web forward. - Google Project Hosting ( 版) https://code.google.com/p/chromium/issues/detail?id=377538

[96] Issue 328167 - chromium - Server-push for proactive cache management - An open-source project to help move the web forward. - Google Project Hosting ( 版) https://code.google.com/p/chromium/issues/detail?id=328167

[97] 958712 – HTTP/2 push continuations not completely implemented ( 版) https://bugzilla.mozilla.org/show_bug.cgi?id=958712

[98] draft-thomson-webpush-http2-02 - Generic Event Delivery Using HTTP Push ( 版) http://tools.ietf.org/html/draft-thomson-webpush-http2-02

[99] 1072478 – loading never stops due to slow SPDY push ( 版) https://bugzilla.mozilla.org/show_bug.cgi?id=1072478

[100] 1167851 – HTTP/2 pushes and WINDOW_UPDATA frame ( 版) https://bugzilla.mozilla.org/show_bug.cgi?id=1167851

[103] 1127618 – PUSH_PROMISE is immediately rejected with a RST_STREAM ( 版) https://bugzilla.mozilla.org/show_bug.cgi?id=1127618

[104] Issue 331663007: Implement PUSH_PROMISE handling in spdy_session - Code Review ( 版) https://codereview.chromium.org/331663007

[105] Issue 331663007: Implement PUSH_PROMISE handling in spdy_session - Code Review ( 版) https://codereview.chromium.org/331663007

[112] Preload ( 版) https://w3c.github.io/preload/#server-push-http-2

[113] Announcing Support for HTTP/2 Server Push ( 版) https://blog.cloudflare.com/announcing-support-for-http-2-server-push-2/

So, if you want to push assets for a given request, you simply add a specially formatted Link header to the response:

Link: </asset/to/push.js>; rel=preload;

[115] RFC 8030 - Generic Event Delivery Using HTTP Push () https://tools.ietf.org/html/rfc8030

[116] mod_http2 - Apache HTTP Server Version 2.4 ( ()) https://httpd.apache.org/docs/2.4/mod/mod_http2.html#h2push

Server pushes are detected by inspecting the Link headers of responses (see https://tools.ietf.org/html/rfc5988 for the specification). When a link thus specified has the rel=preload attribute, it is treated as a resource to be pushed.

[117] google/node-h2-auto-push () https://github.com/google/node-h2-auto-push

[118] preload, destinations, and module scripts · Issue #486 · whatwg/fetch () https://github.com/whatwg/fetch/issues/486

[123] HTTP/2 | 2020 | The Web Almanac by HTTP Archive () https://almanac.httparchive.org/en/2020/http2#goodbye-server-push

[124] Web Performance Calendar » HTTP/2 Push: The details () https://calendar.perfplanet.com/2016/http2-push-the-details/

[125] Intent to Remove: HTTP/2 and gQUIC server push () https://groups.google.com/a/chromium.org/g/blink-dev/c/K3rYLvmQUBY/m/vOWBKZGoAQAJ?pli=1

[126] b5428cd580034a30c7c76769241ebe565461fc53 - chromium/src - Git at Google, https://chromium.googlesource.com/chromium/src/+/b5428cd580034a30c7c76769241ebe565461fc53