概念定义
HTTP/3 保留了方法、头部、状态码等 HTTP 语义,但把传输层换成 QUIC。QUIC 在 UDP 之上以用户态实现了流、流控、拥塞控制与 TLS 1.3 加密。每个流独立进行丢包恢复,单个丢包只会停顿它所在的流。
工作机制
客户端通常先经 HTTP/2 的 Alt-Svc 响应头发现 HTTP/3 端点,再在 UDP 上发起 QUIC 连接。加密与传输握手合并,通常一个往返即可建立密钥和连接状态。连接标识符独立于 IP 地址,使 Wi-Fi 与蜂窝网络切换时连接可以迁移。
使用场景
高丢包或高延迟网络收益最大:按流恢复避免停顿,更少的握手缩短首字节时间。视频会议、大文件传输与移动网页浏览是典型赢家。服务端需具备 UDP 容量和成熟的 QUIC 实现再开启。
常见误区
QUIC 不是 TLS 的替代,而是内嵌 TLS 1.3 完成握手与加密。QUIC 基于 UDP 但自行实现了可靠有序传输。部分企业防火墙会限速或封禁 UDP,务必保留 HTTP/2 回退;软件实现的 QUIC 每连接 CPU 开销可能高于内核 TCP。灰度后应持续观察视频卡顿率与连接错误率。
代码示例
package main
import (
"context"
"encoding/json"
"errors"
"fmt"
"net/http"
"time"
)
type Shipment struct {
ID string `json:"id"`
Status string `json:"status"`
}
func queryShipment(ctx context.Context, baseURL, id string) (*Shipment, error) {
ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
defer cancel() // 必须释放计时器资源
req, err := http.NewRequestWithContext(ctx, http.MethodGet, baseURL+"/shipments/"+id, nil)
if err != nil {
return nil, fmt.Errorf("build request: %w", err)
}
req.Header.Set("Accept", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
if errors.Is(err, context.DeadlineExceeded) {
return nil, fmt.Errorf("shipment %s timed out after 3s: %w", id, err)
}
return nil, fmt.Errorf("request failed: %w", err)
}
defer resp.Body.Close()
var shipment Shipment
if err := json.NewDecoder(resp.Body).Decode(&shipment); err != nil {
return nil, fmt.Errorf("decode body: %w", err)
}
return &shipment, nil
}
func main() {
s, err := queryShipment(context.Background(), "https://api.example.com", "sh-1")
fmt.Println(s, err)
}
package main
import (
"context"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"time"
)
type fetchResult struct {
Status int
Body string
}
func fetchWithDeadline(ctx context.Context, client *http.Client, url string, timeout time.Duration) (fetchResult, error) {
ctx, cancel := context.WithTimeout(ctx, timeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil {
return fetchResult{}, fmt.Errorf("build request: %w", err)
}
resp, err := client.Do(req)
if err != nil {
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return fetchResult{}, fmt.Errorf("fetch %s: deadline exceeded after %s: %w", url, timeout, context.DeadlineExceeded)
}
return fetchResult{}, fmt.Errorf("fetch %s: %w", url, err)
}
defer resp.Body.Close()
body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
if err != nil {
return fetchResult{}, fmt.Errorf("read body: %w", err)
}
return fetchResult{Status: resp.StatusCode, Body: string(body)}, nil
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/fast", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, "ok")
})
mux.HandleFunc("/slow", func(w http.ResponseWriter, r *http.Request) {
select {
case <-time.After(200 * time.Millisecond):
fmt.Fprint(w, "late")
case <-r.Context().Done():
}
})
server := httptest.NewServer(mux)
defer server.Close()
client := &http.Client{Timeout: 5 * time.Second}
ctx := context.Background()
result, err := fetchWithDeadline(ctx, client, server.URL+"/fast", 500*time.Millisecond)
fmt.Printf("fast: status=%d body=%q err=%v\n", result.Status, result.Body, err)
result, err = fetchWithDeadline(ctx, client, server.URL+"/slow", 50*time.Millisecond)
fmt.Printf("slow: status=%d body=%q err=%v\n", result.Status, result.Body, err)
if errors.Is(err, context.DeadlineExceeded) {
fmt.Println("slow request classified as a deadline error")
}
}