HTTP/2 preserves the semantics of methods, status codes, caching, etc., from HTTP, expressing multiple exchanges within a single connection using frames and streams. It allows responses for different requests to be interleaved, overcoming the limitation of HTTP/1.1 where responses had to be ordered by request; the underlying TCP still provides a single ordered byte stream.
The current base specification is RFC 9113, which obsoletes RFC 7540. Do not describe it as a transport protocol that rewrites TCP, nor interpret "single connection, multiple streams" as meaning only one permanent connection is open for the entire site. RFC 9113
Problem to Diagnose
Check First
h2 is negotiated, but why no GET text seen?
Request headers use header blocks and HPACK
One response is slow; do other streams have to wait for it?
Check stream scheduling, connection window, and TCP gaps separately
Does END_HEADERS mean the request is finished?
End of header block is different from end of sending direction
Window is large, why is sending stopped?
Check both stream window and connection window simultaneously
Why does the compression table occasionally fail to decode?
Both directions have state; packet captures may lack history
Which requests can be retried after GOAWAY?
Last-Stream-ID and business execution uncertainty
Connection Establishment and Field Representation
HTTPS commonly uses TLS ALPN to negotiate h2, after which the client sends the defined connection preface and SETTINGS; the server also starts its connection preface with SETTINGS. Although the client's 24-byte fixed preface appears to contain HTTP text, it is not sending a normal HTTP/1.1 request to the application.
HTTP/2 without TLS can be established based on prior knowledge. The historical h2c Upgrade mechanism should not be considered the mandatory entry point for all HTTP/2; the latest base specification has deprecated this upgrade mechanism.
Normal requests express the target using pseudo-fields such as :method, :scheme, :authority, and :path; responses use :status. Pseudo-fields should appear before regular fields, and field names must be lowercase; specific methods like CONNECT have corresponding rules. Do not cram a complete HTTP/1.1 start line into HEADERS.
Connection-specific fields such as Connection, Keep-Alive, and Transfer-Encoding cannot be copied directly; TE has a special case allowing only trailers. Content-Length can still express the expected content length, but streams are delimited by HTTP/2 frames and END_STREAM, so CL cannot be used to replace stream state judgment.
Frame Header 9 B: Length is not the full frame length
Field
Length
Meaning
Length
24 bit
Number of payload bytes only
Type
8 bit
DATA, HEADERS, SETTINGS, etc.
Flags
8 bit
Interpreted per frame type
Reserved + Stream ID
1 + 31 bit
Reserved bit and associated stream; 0 is used for applicable connection control
The receiver's default maximum frame payload is 16,384 B, which can be adjusted within a specified range via SETTINGS_MAX_FRAME_SIZE, up to a maximum of 16,777,215 B. This upper limit is not the total length of a response or a field list.
Below is a DATA frame carrying 5 B of content and ending the sending direction on stream 1; the complete frame is 14 B. The code only handles the frame header and length, not the connection state machine.
Request streams created by the client use odd IDs; push streams initiated by the server use even IDs. You cannot arbitrarily open an even-numbered request stream for arbitrary bidirectional RPC. IDs are used monotonically within the connection and are not recycled or reused after completion; stream 0 is not a business request stream.
END_HEADERS indicates that the current compressed header block is complete; END_STREAM indicates that the sending direction is finished. Only a GET request header can set both flags simultaneously on HEADERS, but the server can still send responses on the same stream. A POST with content might first send HEADERS, then multiple DATA frames, and finally end with DATA or trailer HEADERS.
If a header block is split into CONTINUATION, the corresponding CONTINUATION frames for that stream must be sent consecutively until END_HEADERS; no DATA from other streams can be interleaved. Thus, multiplexing also has protocol-level contiguous regions and cannot be simply understood as any frame being able to jump the queue arbitrarily.
Multiplexing: Separate Application Queuing from TCP Head-of-Line Blocking
When stream 1's slow query is not yet ready to respond, stream 3 can send its own response first; but all sent frames eventually fall into the same TCP sequence number space. After a segment of TCP bytes is lost, the receiver cannot deliver the bytes after the gap as a continuous stream to HTTP/2 in advance, even if they come from another HTTP stream.
This does not mean that losing one packet will revoke data already delivered by all streams, nor does it mean that frames naturally correspond to specific TCP packets. Connection-level bandwidth, congestion control, server scheduling, application lock contention, and HPACK state can also form cross-stream impacts.
Two-Layer Flow Control: Only DATA is Handled, Not Congestion Control
For a sending direction, DATA is limited by both connection and stream quotas. The default initial quota is 65,535 B; SETTINGS_INITIAL_WINDOW_SIZE modifies the stream window and cannot be used to increase the connection window. The connection quota is adjusted via stream 0 WINDOW_UPDATE. The reverse sending direction has its own quota.
The entire payload of DATA counts towards flow control, including padding and its length field, but excluding the 9 B frame header; HEADERS, PING, SETTINGS, etc., do not consume the DATA window. Therefore, limiting only DATA is insufficient to restrict the peer's field decoding or control frame resource consumption.
connection=10_000stream={1:8_000,3:8_000}defsend(sid,size):globalconnectionassert0<=size<=min(connection,stream[sid])connection-=sizestream[sid]-=sizesend(1,6_000)assertconnection==4_000andstream[1]==2_000send(3,4_000)assertconnection==0andstream[3]==4_000# Only increasing the stream window still cannot break through the connection remaining amount.
stream[3]+=10_000assertmin(connection,stream[3])==0connection+=2_000assertmin(connection,stream[3])==2_000print('After connection window update, stream 3 can send:',min(connection,stream[3]))
WINDOW_UPDATE increments cannot be zero, and windows also have an upper limit. Lowering SETTINGS_INITIAL_WINDOW_SIZE may also cause existing stream windows to temporarily become negative; in this case, you must wait for the quota to recover rather than forcibly converting the negative number into an unsigned large number. Applications must consume and release receive budgets in a timely manner to avoid flow control stalls.
Why can't I send even after replenishing the quota for only one stream?
After exhausting the connection quota, only clicking WINDOW_UPDATE for stream 3 causes the gold bar to grow, but the send button remains unavailable. Only after replenishing the connection quota does sending resume. This state demonstrates how the constraint of "taking the minimum of two remaining amounts" forms through historical operations.
Preparing the visual
Why can't I send even after replenishing the quota for only one stream?
Each DATA deducts from two ledgers. Stream 3 still has a balance, but sending pauses if the connection is zero; HEADERS are not in these two DATA ledgers, but are still subject to other resource constraints.
{"id":"http2-shared-window","title":"Why can't I send even after replenishing the quota for only one stream?","summary":"Each DATA deducts from two ledgers. Stream 3 still has a balance, but sending pauses if the connection is zero; HEADERS are not in these two DATA ledgers, but are still subject to other resource constraints.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"actions\"><button id=\"s1\" data-i18n=\"s1\"></button><button id=\"s3\" data-i18n=\"s3\"></button><button id=\"w1\" data-i18n=\"w1\"></button><button id=\"w3\" data-i18n=\"w3\"></button><button id=\"wc\" data-i18n=\"wc\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><section class=\"panel\"><h3 data-i18n=\"windows\"></h3><div id=\"windows\"></div></section><p id=\"equation\" class=\"formula\"></p><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let c=10000,s={1:8000,3:8000};function draw(){intervals('windows',[{start:0,end:c,label:'connection '+c},...[1,3].map(i=>({start:0,end:s[i],label:'stream'+i+' '+s[i],cls:'gold'}))],20000);set('equation','stream1 credit = min('+c+','+s[1]+')\\nstream3 credit = min('+c+','+s[3]+')');[1,3].forEach(i=>{$('#s'+i).disabled=Math.min(c,s[i])<1000;$('#w'+i).disabled=s[i]>=20000});$('#wc').disabled=c>=20000;$('#windows').dataset.snapshot=JSON.stringify({c,s})}[1,3].forEach(i=>{$('#s'+i).onclick=()=>{c-=1000;s[i]-=1000;draw()};$('#w'+i).onclick=()=>{s[i]+=1000;draw()}});$('#wc').onclick=()=>{c+=1000;draw()};$('#reset').onclick=()=>{c=10000;s={1:8000,3:8000};draw()};draw();","audio":false,"strings":{"heading":"Why can't I send even after replenishing the quota for only one stream?","intro":"Send 1000 B DATA on any stream, or separately replenish stream and connection windows.","reset":"Reset","s1":"Stream 1 Send DATA","s3":"Stream 3 Send DATA","scope":"Single direction, two streams initially 8000 B each, connection 10000 B; no padding, fixed 1000 B each time, update limit 20000 teaching value, does not simulate TCP and SETTINGS shrinking window.","scopeLabel":"Model Scope and Conventions","w1":"Stream 1 WINDOW_UPDATE","w3":"Stream 3 WINDOW_UPDATE","wc":"Connection WINDOW_UPDATE","windows":"Remaining DATA Quota (B)"}}
HPACK: Each Direction Maintains Compression State Independently
HPACK uses a 61-item static table, a dynamic table, and optional fixed Huffman encoding. The encoder in the request direction and the decoder on the server share one corresponding state; the response direction is another. It is not that the server stores response fields into it, and the client can reference them in the next request.
Dynamic table entries are inserted according to encoding instructions; later-inserted items are placed at the front, and old items are evicted based on capacity. It is not that "all fields are automatically stored after each HEADERS ends," and accessing an old item does not promote it to LRU. Entry billing is name byte count + value byte count + 32, which is different from the compressed wire length.
For example, :authority: www.example.com occupies 10+15+32=57 B of table capacity; the first dynamic index is 62. The next time a new item is inserted, the old item's index becomes 63. Static index 2 corresponds to :method: GET, expressed as 0x82 in single-byte indexed form; index 62 corresponds to 0xbe, but you only know what it represents when the direction and historical state match.
Never Indexed is used for encoding constraints on sensitive fields; it does not encrypt fields, does not hide the total length, and does not eliminate all compression side channels. The field table capacity limit and the decoded field list size limit are different resource controls. RFC 7541: HPACK
Why does index 62 point to a different field next time?
After inserting two items consecutively, 62 points to the later-inserted one, and the previous field becomes 63. Referencing 62 again and observing the table order shows no change. After shrinking the capacity and calling back the slider, evicted items do not reappear because capacity limits and actual historical state are two separate variables.
Preparing the visual
Why does index 62 point to a different field next time?
Index is the position in the current direction's compression state, not a permanent field number. Insertion shifts old indices back, capacity is billed by name+value+32, and reading does not perform LRU promotion.
{"id":"hpack-index-history","title":"Why does index 62 point to a different field next time?","summary":"Index is the position in the current direction's compression state, not a permanent field number. Insertion shifts old indices back, capacity is billed by name+value+32, and reading does not perform LRU promotion.","height":1000,"html":"<h2 data-i18n=\"heading\"></h2><p class=\"intro\" data-i18n=\"intro\"></p><div class=\"control\"><label for=\"capacity\" data-i18n=\"capacity\"></label><output for=\"capacity\" id=\"capacity-value\"></output><input id=\"capacity\" type=\"range\" min=\"0\" max=\"180\" value=\"120\" step=\"10\"></div><div class=\"actions\"><button id=\"a\" data-i18n=\"a\"></button><button id=\"b\" data-i18n=\"b\"></button><button id=\"ref\" data-i18n=\"ref\"></button><button id=\"reset\" data-i18n=\"reset\"></button></div><section class=\"panel\"><h3 data-i18n=\"entries\"></h3><div id=\"entries\"></div></section><section class=\"panel\"><h3 data-i18n=\"last\"></h3><div id=\"last\"></div></section><details class=\"scope\"><summary data-i18n=\"scopeLabel\"></summary><p data-i18n=\"scope\"></p></details>","css":".intro{margin:8px 0 18px;color:var(--muted);font-size:14px}.caption,.hint{font-size:12px;color:var(--muted);margin-top:12px}.control{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:6px 12px;align-items:center;margin:14px 0;font-size:13px}.control label{font-size:13px}.control output{color:var(--accent);min-width:5em;text-align:right;font-variant-numeric:tabular-nums}.control input{grid-column:1/-1;width:100%;margin:0}.presets{display:flex;gap:4px;align-items:stretch}.presets button{flex:1 1 0;min-width:0;overflow-wrap:anywhere;font-size:13px}.actions{display:flex;gap:8px;flex-wrap:wrap;margin:14px 0}.actions button{flex:1;min-width:0;font-size:13px;min-height:42px}.copy{display:grid;font-size:14px;margin:16px 0}.copy>*{grid-area:1/1;margin:0}.reserve{visibility:hidden}.scope{margin-top:14px;color:var(--muted);font-size:12px}.scope summary{padding:8px 0;cursor:pointer}.scope p{margin-top:10px}.two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.card{border:1px solid var(--rule);border-radius:9px;padding:14px;min-width:0}.card header{font-size:14px;font-weight:600;margin-bottom:10px}.card button{font-size:12px;width:100%;min-width:0}.card small{display:block;font-size:12px;color:var(--muted)}.card code{font-size:12px;overflow-wrap:anywhere}.metrics{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px;margin:16px 0}.metrics>div{border-left:2px solid var(--accent);padding-left:10px;min-width:0}.metrics span{display:block;min-height:3.2em;color:var(--muted);font-size:12px}.metrics strong{font-size:23px;display:block;font-weight:500;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.formula{border-top:1px solid var(--rule);padding-top:12px;margin-top:14px;font-size:13px;font-variant-numeric:tabular-nums;overflow-wrap:anywhere}.value{font:500 20px var(--viz-font-ui)}.legend{display:flex;flex-wrap:wrap;gap:6px 14px;font-size:12px;color:var(--muted);margin:12px 0}.legend span:before{content:'';display:inline-block;width:8px;height:8px;border-radius:2px;background:var(--accent);margin-right:6px}.legend .second:before{background:var(--second)}.check{display:flex;align-items:center;gap:8px;font-size:13px;margin:12px 0}.check input{width:16px;height:16px;flex-shrink:0;accent-color:var(--accent)}select,input[type=text],input[type=number]{background:var(--paper);color:var(--ink);font:inherit;font-size:13px;padding:10px;border:1px solid var(--rule);border-radius:7px;max-width:100%;min-width:0}select{width:100%;min-height:42px}.band{height:20px;display:flex;background:var(--surface);border-radius:4px;overflow:hidden}.band i{height:100%;display:block;background:var(--accent);transition:width .25s}.band i.second{background:var(--second)}button:disabled{cursor:default}.status{font-size:13px;min-height:4.8em}.mono{font-family:var(--viz-font-mono);font-variant-numeric:tabular-nums}.muted{color:var(--muted)}\n@media(max-width:480px){.two{gap:8px}.card{padding:12px 9px}.actions button,.presets button{font-size:12px}.metrics strong{font-size:20px}.metrics{gap:8px}}\n@media(prefers-reduced-motion:reduce){*,*::before,*::after{transition:none!important;animation:none!important}}\n.track-row{margin:16px 0}.track-row>span{display:block;font-size:12px;color:var(--muted);margin-bottom:7px}.track{display:flex;gap:5px;min-width:0}.cell{flex:1;min-width:0;min-height:48px;border:1px solid var(--rule);border-radius:5px;background:var(--surface);display:flex;align-items:center;justify-content:center;text-align:center;padding:5px 3px;font-size:12px;overflow-wrap:anywhere;font-variant-numeric:tabular-nums}.cell.on{background:var(--accent-soft);border-color:var(--accent)}.cell.gold{background:var(--second-soft);border-color:var(--second)}.cell.empty{border-style:dashed;color:var(--muted)}.cell.error{border-color:var(--second);text-decoration:line-through}.choices{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px;margin:14px 0}.choices button{min-width:0;padding:10px 4px;font-size:12px;overflow-wrap:anywhere}.choices button[aria-current=true]{border-color:var(--accent);background:var(--accent-soft)}.relation{display:grid;grid-template-columns:minmax(0,1fr) 28px minmax(0,1fr);align-items:center;gap:6px;margin:12px 0}.relation .cell{min-height:72px}.relation>i{text-align:center;font-style:normal;color:var(--accent)}.readout{font-size:13px;white-space:pre-line;min-height:6.4em;overflow-wrap:anywhere}.plot{position:relative;border-left:1px solid var(--rule);border-bottom:1px solid var(--rule);padding:8px 0;margin:18px 0}.plot-row{height:28px;position:relative;margin:4px 0;background:var(--surface)}.plot-row i{position:absolute;inset-block:5px;background:var(--accent);min-width:2px;transition:width .2s,left .2s}.plot-row b{position:relative;padding:4px 6px;font:11px/28px var(--viz-font-ui);background:var(--paper);border-radius:3px}.axis{display:flex;justify-content:space-between;font-size:11px;color:var(--muted)}\n\n/* Action labels must remain readable across locales and narrow embeds. */\n.actions{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}\n.actions button{overflow-wrap:anywhere;white-space:normal;min-height:58px;padding:8px}\nbutton{overflow-wrap:anywhere}\n@media(min-width:640px){.actions{grid-template-columns:repeat(3,minmax(0,1fr))}}\n\n.metrics span{overflow-wrap:anywhere}\n.panel{border:1px solid var(--rule);border-radius:9px;padding:12px;margin:14px 0;min-width:0}.panel h3{font-size:13px;margin:0 0 10px;color:var(--muted)}.source{font:13px/1.8 var(--viz-font-mono);white-space:pre-wrap;overflow-wrap:anywhere;margin:12px 0}.source mark{background:var(--accent-soft);color:var(--ink);border-bottom:2px solid var(--accent)}.source .fault{background:var(--second-soft);border-color:var(--second)}.diagram{display:block;width:100%;height:auto;max-height:320px;margin:14px 0;overflow:visible}.diagram text{fill:var(--ink);font:12px var(--viz-font-ui)}.diagram .edge{stroke:var(--muted);stroke-width:1.5;fill:none}.diagram .edge.on{stroke:var(--accent);stroke-width:3}.diagram .node{fill:var(--surface);stroke:var(--rule);stroke-width:1.5}.diagram .node.on{fill:var(--accent-soft);stroke:var(--accent)}.diagram .node.gold{fill:var(--second-soft);stroke:var(--second)}.log{font:12px/1.8 var(--viz-font-mono);white-space:pre-line;overflow-wrap:anywhere;min-height:7.2em}.table{display:grid;gap:5px;margin:14px 0}.table .tr{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:5px}.table .tr>*{padding:8px 5px;background:var(--surface);border-radius:4px;min-width:0;overflow-wrap:anywhere;font-size:12px}.table .tr .on{background:var(--accent-soft)}.table .tr .gold{background:var(--second-soft)}.choices button{min-height:44px}.metrics strong{min-height:1.6em}.code-line{display:block}.code-line.on{background:var(--accent-soft);border-left:2px solid var(--accent);padding-left:6px}.status-badge{font-size:12px;color:var(--accent)}.tile-grid{display:grid;grid-template-columns:repeat(4,minmax(0,1fr));gap:6px}.tile-grid .cell{min-height:40px}.actions button{min-height:48px}.form-field{display:grid;gap:8px;margin:14px 0;font-size:13px}.form-field input{width:100%;font-family:var(--viz-font-ui)}.metrics.three{grid-template-columns:repeat(3,minmax(0,1fr))}.formula{min-height:3.2em}.presets{flex-wrap:wrap}.presets button{flex-basis:25%;min-height:44px}\n\n.budget{display:flex;height:28px;margin:16px 0;background:var(--surface);border-radius:5px;overflow:hidden}.budget i{display:block;background:var(--accent);transition:width .2s}.budget i.gold{background:var(--second)}.intervals{margin:16px 0}.interval{position:relative;height:36px;background:var(--surface);margin:6px 0;border-radius:4px;overflow:hidden}.interval i{position:absolute;height:100%;background:var(--accent-soft);border-left:2px solid var(--accent)}.interval i.gold{background:var(--second-soft);border-color:var(--second)}.interval span{position:relative;z-index:1;font:12px/36px var(--viz-font-ui);padding-left:7px}.table .tr>*{min-height:4.8em}.formula{white-space:pre-line;min-height:7.2em}.actions:has(>button:only-child){grid-template-columns:1fr}.metrics strong{min-height:3.2em}.rangegrid{display:grid;grid-template-columns:repeat(12,minmax(0,1fr));gap:3px;margin:14px 0}.rangegrid .cell{min-height:30px;padding:3px 1px;font-size:10px}.message{min-height:7.2em;white-space:pre-line;font:13px/1.8 var(--viz-font-ui);overflow-wrap:anywhere}\n/* Allow CJK hanging punctuation without overflowing a metric's border box. */\n.metrics>div{padding-right:4px}.metrics span{line-break:anywhere}\n\n#entries .tr{grid-template-columns:.6fr 1.7fr .6fr}.tr>*{min-height:5em}#last{min-height:3.6em;overflow-wrap:anywhere;font:13px/1.8 var(--viz-font-ui)}","js":"const $=s=>document.querySelector(s);const set=(id,v)=>$('#'+id).textContent=v;const t=k=>viz.t(k);function cells(id,values){$('#'+id).replaceChildren(...values.map(v=>{const e=document.createElement('div');e.className='cell '+(v.cls||'');e.textContent=v.text;e.title=v.title||v.text;return e;}));}function pressed(mode){document.querySelectorAll('[data-mode]').forEach(e=>e.setAttribute('aria-pressed',String(e.dataset.mode===mode)));}const esc=s=>String(s).replace(/[&<>\"']/g,c=>({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''}[c]));const fmt=s=>'{'+[...s].sort().join(', ')+'}';function graph(id,nodes,edges){const el=$('#'+id);el.setAttribute('viewBox','0 0 360 200');el.innerHTML='<defs><marker id=\"arrow-'+id+'\" viewBox=\"0 0 10 10\" refX=\"8\" refY=\"5\" markerWidth=\"5\" markerHeight=\"5\" orient=\"auto-start-reverse\"><path d=\"M 0 0 L 10 5 L 0 10 z\" fill=\"var(--muted)\"/></marker></defs>'+edges.map(e=>{const a=nodes[e[0]],b=nodes[e[1]],dx=b.x-a.x,dy=b.y-a.y,d=Math.hypot(dx,dy)||1;if(edges.some(r=>r[0]===e[1]&&r[1]===e[0])){const nx=-dy/d,ny=dx/d;return '<path class=\"edge '+(e[2]?'on':'')+'\" d=\"M '+(a.x+dx/d*22+nx*12)+' '+(a.y+dy/d*22+ny*12)+' Q '+((a.x+b.x)/2+nx*38)+' '+((a.y+b.y)/2+ny*38)+' '+(b.x-dx/d*25+nx*12)+' '+(b.y-dy/d*25+ny*12)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}return '<line class=\"edge '+(e[2]?'on':'')+'\" x1=\"'+(a.x+dx/d*25)+'\" y1=\"'+(a.y+dy/d*25)+'\" x2=\"'+(b.x-dx/d*28)+'\" y2=\"'+(b.y-dy/d*28)+'\" marker-end=\"url(#arrow-'+id+')\"/>';}).join('')+nodes.map(n=>'<g><circle class=\"node '+(n.cls||'')+'\" cx=\"'+n.x+'\" cy=\"'+n.y+'\" r=\"25\"/><text x=\"'+n.x+'\" y=\"'+(n.y+4)+'\" text-anchor=\"middle\">'+esc(n.name)+'</text>'+(n.sub?'<text x=\"'+n.x+'\" y=\"'+(n.y+43)+'\" text-anchor=\"middle\">'+esc(n.sub)+'</text>':'')+'</g>').join('');}function table(id,rows){$('#'+id).classList.add('table');$('#'+id).replaceChildren(...rows.map(r=>{const row=document.createElement('div');row.className='tr';r.forEach(x=>{const c=document.createElement('div');if(typeof x==='object'){c.textContent=x.text;c.className=x.cls||'';}else c.textContent=x;row.append(c)});return row;}));}function band(id,parts,total){$('#'+id).replaceChildren(...parts.map((p,i)=>{const e=document.createElement('i');e.style.width=(100*p.value/total)+'%';e.className=i%2?'gold':'on';e.title=p.name+': '+p.value;return e}));}function intervals(id,items,total){$('#'+id).replaceChildren(...items.map(p=>{const row=document.createElement('div');row.className='interval';const bar=document.createElement('i');bar.style.left=(100*p.start/total)+'%';bar.style.width=(100*(p.end-p.start)/total)+'%';bar.className=p.cls||'';const label=document.createElement('span');label.textContent=p.label;row.append(bar,label);return row;}));}let entries=[],last='∅';const size=e=>e[0].length+e[1].length+32;function evict(){while(entries.reduce((s,e)=>s+size(e),0)>+$('#capacity').value)entries.pop()}function draw(){set('capacity-value',$('#capacity').value);const rows=[['index','field','B']];for(let i=0;i<5;i++)rows.push(entries[i]?[62+i,entries[i].join(': '),size(entries[i])]:['—','—','—']);table('entries',rows);set('last',last);$('#ref').disabled=!entries.length;$('#entries').dataset.values=JSON.stringify(entries);}function insert(e){entries.unshift(e);evict();draw()}$('#a').onclick=()=>insert([':authority','www.example.com']);$('#b').onclick=()=>insert(['x-id','7']);$('#ref').onclick=()=>{last='62 → '+entries[0].join(': ');draw()};$('#capacity').oninput=()=>{evict();draw()};$('#reset').onclick=()=>{entries=[];last='∅';draw()};draw();","audio":false,"strings":{"a":"Insert :authority","b":"Insert x-id","capacity":"Dynamic Table Capacity (B)","entries":"Current Dynamic Table","heading":"Why does index 62 point to a different field next time?","intro":"Insert fields, reference current index 62, then shrink table capacity; referencing does not move the entry to the front.","last":"Last Reference Result","ref":"Reference index 62","reset":"Reset","scope":"Fixed two ASCII fields authority and x-id, simulating HPACK dynamic table order and eviction, does not encode Huffman or complete header blocks. Shrinking capacity permanently evicts, expanding again does not restore.","scopeLabel":"Model Scope and Conventions"}}
SETTINGS, Priority, and Resource Boundaries
SETTINGS describe the constraints the sender is willing to receive, allowing the peer to adjust its own sending accordingly. For example, the client declares the maximum frame length, limiting the frames the server sends to the client; this cannot be interpreted in reverse as permission for the client's own sending. SETTINGS with ACK no longer carry setting items.
MAX_CONCURRENT_STREAMS limits the streams that can be concurrently active in the corresponding direction, not the cumulative number of requests in the connection history. Even if only instantaneous concurrency is controlled, it may still bear the workload caused by rapid stream creation/cancellation. You should also pay attention to request cost, queuing, field size, and cancellation propagation.
The old priority dependency tree has been deprecated in RFC 9113; RFC 9218 defines extensible priority, where urgency is 0 to 7 (smaller is more urgent), and incremental indicates whether progressive delivery is suitable. These are scheduling hints, do not guarantee fixed throughput shares, and cannot break through flow control windows. RFC 9218
Cancellation and Graceful Exit: Do Not Treat RST as Transaction Rollback
RST_STREAM terminates a stream, but it does not prove that the application did not execute the request, nor does it automatically rollback database transactions. Implementations must still maintain compression and flow control state involving the connection; you cannot see that a stream was cancelled and arbitrarily skip all related frames.
GOAWAY uses Last-Stream-ID to inform the peer which streams may have already been processed. Assuming the last client stream ID accepted by the server was 5, stream 7 can be retried on a new connection; for streams 1, 3, and 5, those that did not receive a complete response still have execution uncertainty and must be handled with deduplication based on method and business logic. Graceful exit can first stop new streams, then wait for existing streams to complete; you cannot simply receive GOAWAY and retransmit all requests. RFC 9113: Connection Exit
Server Push: Protocol Capability vs. Browser Support
PUSH_PROMISE once allowed the server to commit in advance and send resources the client might need; unknown cache hits, wasted transmission, and scheduling complexity would weaken the benefits. Chrome removed HTTP/2 Server Push support starting from version 106, but you cannot write that the HTTP/2 specification has deleted the push frame. Chrome Removal Notice
103 Early Hints or preload hints let the client decide whether to initiate the request themselves; this is not the same mechanism as the server directly pushing content. Deployment choices should be based on real client support and measurement results; you cannot bind "enabling HTTP/2" together with "should enable push."
Continue Reading
HTTP/1.1: Message boundaries, caching, and method semantics.
HTTP/3: QUIC streams and QPACK dependency trade-offs.
TCP: Byte sequence numbers, windows, and congestion control.