PUSH_PROMISE

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文書から参照される CSSJavaScript などいろいろなファイルの集合体として構成されますが、 その読み込みの最適化のため、 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 フレーム誤り符号 CANCELREFUSED_STREAM を指定し、当該ストリーム識別子を指定して送信できます >>34

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

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

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

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

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

[111] 同じ :path約束要求を受信したら、 Chromeストリームエラー PROTOCOL_ERRORFirefoxストリームエラー 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ビットの欄です >>8PADDED フラグが設定されている場合のみ存在します >>8。 値は0でも構いません。値が実際の詰めの長さを超えることは禁止されていませんが、 当然正しく処理できませんし、可能な長さを超えるなら接続エラーとなります。
[13] R
予約されている1ビットです >>8
[10] 約束ストリームID (Promised Stream ID)
送信者がで予約するストリーム符号無し31ビットストリーム識別子を示します >>8 (ネットワークバイト順 >>78)。 送信者が作成する次のストリームの識別子として妥当なものでなければなりません >>8idle 状態でなければなりません >>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 が開始したストリームで状態が openhalf-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_ERRORChromeストリームエラー STREAM_CLOSED とするようです。ただし約束ストリーム識別子が 0 なら、接続エラー PROTOCOL_ERROR とするようです。

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

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

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

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

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

[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_ERRORFirefox接続エラー PROTOCOL_ERROR とするようです。

関連

[122] JavaScriptPromise とは無関係です。

設定

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

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

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

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

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

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

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

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

fetch と server push

[92] ChromeFirefox は、 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