当前位置: 首页 > 工具软件 > gohttp > 使用案例 >

go实现HTTP3

易宏阔
2023-12-01

一、首先什么是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,但是在原本的基础上新增了很多功能。

参考:读懂 HTTP/2 及 HTTP/3 特性

二、用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)
		}
	}
}

 类似资料: