概念定义
HTTP/2 是二进制的、多路复用的 HTTP 修订版。它不再为每个请求开一条连接,也不以串行流水线承载,而是在一条 TCP 连接上承载许多并发流,每个帧都带有流 id 标签。它用 HPACK 压缩头部,支持服务器推送与流优先级,并消除了主导 HTTP/1.1 的每请求连接开销。其弱点是所有流共享一条 TCP 连接,因此丢一个包仍会把它们一起卡住。
为什么重要
HTTP/1.1 迫使浏览器与服务器采取变通手段:多条连接、域分片、精灵图与激进的打包,都是为了隐藏串行请求带来的延迟。HTTP/2 在一条连接上多路复用并压缩重复的头部字节,使这些手段大多不再必要。页面以更少的往返与更少的连接建立加载,且因为协商发生在 TLS 中,应用代码几乎无需改动。
工程实践
停止域分片、减少过度打包,让协议去做那些技巧此前近似的事。对服务器推送保持保守或直接关闭,因为它可能发送客户端已有的数据。记住单条 TCP 连接意味着丢包会卡住所有流,因此在易丢包的移动网络上,基于 QUIC 的 HTTP/3 可能表现更好。监控每条连接的流数量,并相应调优服务器的并发放大上限。
代码示例
package httpx
import (
"context"
"net/http"
"time"
)
func Retry(ctx context.Context, client *http.Client, req *http.Request, attempts int) (*http.Response, error) {
var lastErr error
delay := 100 * time.Millisecond
for i := 0; i < attempts; i++ {
resp, err := client.Do(req.WithContext(ctx))
if err == nil {
if resp.StatusCode < 500 && resp.StatusCode != http.StatusTooManyRequests {
return resp, nil
}
resp.Body.Close()
} else {
lastErr = err
}
select {
case <-ctx.Done():
return nil, ctx.Err()
case <-time.After(delay):
}
delay *= 2
}
if lastErr != nil {
return nil, lastErr
}
return nil, context.DeadlineExceeded
}
export function parseRange(header, size) {
if (!header || typeof header !== 'string') return null;
const match = /^bytes=(\d*)-(\d*)$/.exec(header.trim());
if (!match) return { unsatisfiable: true };
const [, startRaw, endRaw] = match;
if (startRaw === '' && endRaw === '') return { unsatisfiable: true };
let start;
let end;
if (startRaw === '') {
const suffix = Number(endRaw);
if (suffix === 0) return { unsatisfiable: true };
start = Math.max(size - suffix, 0);
end = size - 1;
} else {
start = Number(startRaw);
end = endRaw === '' ? size - 1 : Number(endRaw);
}
if (start > end || start >= size) return { unsatisfiable: true };
return { start, end: Math.min(end, size - 1), length: Math.min(end, size - 1) - start + 1 };
}