Go H2C

HTTP/1.1

启动一个 HTTP 服务示例。

1
2
3
4
5
	m := http.NewServeMux()
	m.HandleFunc("/proto", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(r.Proto))
	})
	go http.ListenAndServe(":8080", m)

H2C

h2c 是未加密的 http/2 协议,启动一个 h2c 服务示例。

1
2
3
4
5
6
	m := http.NewServeMux()
	m.HandleFunc("/proto", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte(r.Proto))
	})
	h := h2c.NewHandler(m, &http2.Server{IdleTimeout: time.Minute})
	go http.ListenAndServe(":8080", h)

客户端

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
	cli := &http.Client{
		Timeout: 10 * time.Second,
		Transport: &http2.Transport{
			AllowHTTP: true,
			DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
				var dialer net.Dialer
				return dialer.DialContext(ctx, network, addr)
			},
		},
	}

	{
		resp, err := cli.Get("http://localhost:8080/proto")
		if err != nil {
			t.Fatal(err)
		}
		defer resp.Body.Close()
		data, err := io.ReadAll(resp.Body)
		if err != nil {
			t.Fatal(err)
		}
		fmt.Println("h2c", string(data))
	}

h2c 客户端请求 h2c 服务端,可以从下面截图看出协议是 HTTP/2.0。

h1 客户端请求 h2c 服务端,协议为 HTTP/1.1。

image-20241025170723671

那如果 h2c 客户端请求 h1 服务端呢? 将会收获以下错误。

Get "http://localhost:8080/proto": read tcp [::1]:61776->[::1]:8080: read: connection reset by peer

我们可以自己实现 RoundTrip 接口,当 h2c 连接不上时,降级到 h1 。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

// CustomTransport 实现 HTTP/2 回退到 HTTP/1.1 的逻辑
type CustomTransport struct {
	h2cTransport *http2.Transport
	h1Transport  *http.Transport
}

func NewCustomTransport() *CustomTransport {
	return &CustomTransport{
		h2cTransport: &http2.Transport{
			AllowHTTP: true,
			DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) {
				var dialer net.Dialer
				return dialer.DialContext(ctx, network, addr)
			},
			IdleConnTimeout: time.Minute,
		},
		h1Transport: &http.Transport{
			IdleConnTimeout: time.Minute,
		},
	}
}

func (f *CustomTransport) RoundTrip(req *http.Request) (*http.Response, error) {
	// 使用 HTTP/2 Transport 作为默认传输
	resp, err := f.h2cTransport.RoundTrip(req)
	if err != nil {
		// 如果 HTTP/2 失败,回退到 HTTP/1.1 连接
		return f.h1Transport.RoundTrip(req)
	}
	return resp, nil
}

再尝试一次试试

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
	cli := &http.Client{
		Timeout: 10 * time.Second,
    Transport: NewCustomTransport(),
	}

	{
		resp, err := cli.Get("http://localhost:8080/proto")
		if err != nil {
			t.Fatal(err)
		}
		defer resp.Body.Close()
		data, err := io.ReadAll(resp.Body)
		if err != nil {
			t.Fatal(err)
		}
		fmt.Println("h2c", string(data))
	}
Licensed under CC BY-NC-SA 4.0
本文阅读量 次, 总访问量 ,总访客数
Built with Hugo .   Theme Stack designed by Jimmy