一、首先什么是HTTP3?
虽然 HTTP/2 解决了很多之前旧版本的问题,但是它还是存在一个巨大的问题,主要是底层支撑的 TCP 协议造成的。
上文提到 HTTP/2 使用了多路复用,一般来说同一域名下只需要使用一个 TCP 连接。但当这个连接中出现了丢包的情况,那就会导致 HTTP/2 的表现情况反倒不如 HTTP/1 了。
因为在出现丢包的情况下,整个 TCP 都要开始等待重传,也就导致了后面的所有数据都被阻塞了。但是对于 HTTP/1.1 来说,可以开启多个 TCP 连接,出现这种情况反到只会影响其中一个连接,剩余的 TCP 连接还可以正常传输数据。
那么可能就会有人考虑到去修改 TCP 协议,其实这已经是一件不可能完成的任务了。因为 TCP 存在的时间实在太长,已经充斥在各种设备中,并且这个协议是由操作系统实现的,更新起来不大现实。
基于这个原因,Google 就更起炉灶搞了一个基于 UDP 协议的 QUIC 协议,并且使用在了 HTTP/3 上,HTTP/3 之前名为 HTTP-over-QUIC,从这个名字中我们也可以发现,HTTP/3 最大的改造就是使用了 QUIC。
QUIC 虽然基于 UDP,但是在原本的基础上新增了很多功能。
二、用go语言实现HTTP3
package main
import (
"compress/flate"
"compress/gzip"
"flag"
"fmt"
"io"
"log"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"github.com/itchio/go-brotli/enc"
"github.com/lucas-clemente/quic-go/http3"
)
type compressResponseWriter struct {
io.Writer
http.ResponseWriter
}
func (w compressResponseWriter) Write(b []byte) (int, error) {
return w.Writer.Write(b)
}
func (w compressResponseWriter) WriteHeader(code int) {
w.Header().Del("Content-Length")
w.ResponseWriter.WriteHeader(code)
}
func gzipHandler(fn http.HandlerFunc, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Encoding", "gzip")
gz, err := gzip.NewWriterLevel(w, gzip.BestSpeed)
if err != nil {
fmt.Printf("Error closing gzip: %+v\n", err)
}
defer func() {
err := gz.Close()
if err != nil {
fmt.Printf("Error closing gzip: %+v\n", err)
}
}()
gzr := compressResponseWriter{Writer: gz, ResponseWriter: w}
fn(gzr, r)
}
func deflateHandler(fn http.HandlerFunc, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Encoding", "deflate")
df, err := flate.NewWriter(w, flate.BestSpeed)
if err != nil {
fmt.Printf("Error closing deflate: %+v\n", err)
}
defer func() {
err := df.Close()
if err != nil {
fmt.Printf("Error closing deflate: %+v\n", err)
}
}()
dfr := compressResponseWriter{Writer: df, ResponseWriter: w}
fn(dfr, r)
}
func brotliHandler(fn http.HandlerFunc, w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Encoding", "br")
op := &enc.BrotliWriterOptions{
Quality: 4,
LGWin: 10,
}
br := enc.NewBrotliWriter(w, op)
defer func() {
err := br.Close()
if err != nil {
fmt.Printf("Error closing brotli: %+v\n", err)
}
}()
brr := compressResponseWriter{Writer: br, ResponseWriter: w}
fn(brr, r)
}
type handler struct {
enableCompress bool
}
func (h *handler) makeCompressionHandler(fn http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if h.enableCompress {
w.Header().Set("Vary", "Assept-Encoding")
if strings.Contains(r.Header.Get("Accept-Encoding"), "br") {
brotliHandler(fn, w, r)
return
} else if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
gzipHandler(fn, w, r)
return
} else if strings.Contains(r.Header.Get("Accept-Encoding"), "deflate") {
deflateHandler(fn, w, r)
return
}
}
fn(w, r)
}
}
func reverseProxy(backEndUrl string) func(http.ResponseWriter, *http.Request) {
url, err := url.Parse(backEndUrl)
if err != nil {
panic(err)
}
proxy := httputil.NewSingleHostReverseProxy(url)
return proxy.ServeHTTP
}
func main() {
var (
bg = flag.String("b", "http://localhost:9000", "background")
port = flag.Uint("p", 8080, "front port")
useHttp3 = flag.Bool("enable_http3", false, "use http3")
certPath = flag.String("c", "", "cert file path")
keyPath = flag.String("k", "", "key file path")
enableCompress = flag.Bool("enable_compress", false, "enable compress")
)
flag.Parse()
h := handler{
enableCompress: *enableCompress,
}
proxyServer := http.Server{
Addr: fmt.Sprintf("0.0.0.0:%d", *port),
Handler: h.makeCompressionHandler(reverseProxy(*bg)),
}
if *useHttp3 {
fmt.Println("http3")
if err := http3.ListenAndServe(proxyServer.Addr, *certPath, *keyPath, proxyServer.Handler); err != nil {
log.Fatalln(err)
}
} else {
fmt.Println("http")
if err := proxyServer.ListenAndServe(); err != nil {
log.Fatalln(err)
}
}
}