Golang:启用 idleConn 时,http2 客户端立即通过代理关闭连接
Golang: http2 client immediately close connections via proxy while idleConn was enabled
0x00 TL;DR
我正在使用 Go 实现一个 http 客户端,并使用 squid 作为转发代理向远程服务器发送请求。当通过代理使用 http/1.1 或 http/1.1, 没有代理的 http2 时一切顺利,但是,当通过代理使用 http2 客户端时,大多数连接立即关闭,只有一两个连接被保留.
不确定是我的错误代码还是什么。在 http 传输上启用了 idleConn 配置。提前致谢。
0x01 环境
代码
package main
import (
"context"
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"sync"
"time"
)
const (
proxyURL = "http://{{proxy-ip}}:3128"
)
func main() {
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
doSendRequests()
wg.Done()
}()
wg.Wait()
}
func doSendRequests() {
//client := newHTTPClient(nil)
client := newHTTPClient(proxy)
round := 0
for {
round += 1
for i := 0; i < 5; i++ {
go func(r int) {
start := time.Now()
var err error
defer func() {
duration := time.Since(start)
fmt.Printf("Round: %d, A: %v, duration: %v\n", r, err, duration)
}()
req := newRequest("https://www.google.com")
resp, err := client.Do(req)
if err == nil {
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
} else {
fmt.Printf("A: %v\n", err)
}
}(round)
}
time.Sleep(100 * time.Second)
}
}
type TLSDialer struct {
net.Dialer
}
var (
config = &tls.Config{InsecureSkipVerify: true}
)
func (dialer *TLSDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
return tls.DialWithDialer(&dialer.Dialer, network, addr, config)
}
var (
DefaultDialer = net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}
DefaultTLSDialer = TLSDialer{DefaultDialer}
)
func proxy(req *http.Request) (*url.URL, error) {
uri, err := url.Parse(proxyURL)
if err != nil {
return nil, nil
}
return uri, nil
}
func newHTTPClient(proxy func(*http.Request) (*url.URL, error)) *http.Client {
t := &http.Transport{
DialContext: (&DefaultDialer).DialContext,
DisableKeepAlives: false,
DisableCompression: false,
ForceAttemptHTTP2: true,
MaxIdleConns: 5000,
MaxIdleConnsPerHost: 1000,
IdleConnTimeout: 0 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
if proxy == nil {
t.DialTLSContext = (&DefaultTLSDialer).DialContext
} else {
t.Proxy = proxy
t.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
return &http.Client{Transport: t}
}
func newRequest(uri string) *http.Request {
ctx, _ := context.WithTimeout(context.Background(), 60*time.Second)
req, _ := http.NewRequestWithContext(ctx, "GET", uri, nil)
return req
}
环境
进入 1.15.3
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/ec2-user/.cache/go-build"
GOENV="/home/ec2-user/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/ec2-user/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/ec2-user/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build200120630=/tmp/go-build -gno-record-gcc-switches"
uname -a
Linux ip-10-53-74-64.ec2.internal 4.14.243-185.433.amzn2.x86_64 #1 SMP Mon Aug 9 05:55:52 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
鱿鱼4.15
0x02现象
正如我在摘要中提到的,当我通过代理使用 http/1.1 或不使用代理的 http/1.1, http2 时一切顺利。但是,当通过代理使用 http2 客户端时,将创建与请求一样多的连接,然后关闭其他连接,只留下一两个连接。我用 Wireshark 抓包,发现连接最初是从客户端关闭的,所以它可能与代理端无关,对此不确定。
没有代理
// www.google.com
Every 0.5s: netstat -anop | grep '172.217'
Sat Nov 27 04:26:22 2021
tcp 0 0 10.53.74.64:44106 172.217.0.36:443 ESTABLISHED 3110/main keepalive (25.35/0/0)
tcp 0 0 10.53.74.64:44378 172.217.0.36:443 ESTABLISHED 3110/main keepalive (25.44/0/0)
tcp 0 0 10.53.74.64:44084 172.217.0.36:443 ESTABLISHED 3110/main keepalive (25.60/0/0)
tcp 0 0 10.53.74.64:44374 172.217.0.36:443 ESTABLISHED 3110/main keepalive (25.43/0/0)
tcp 0 0 10.53.74.64:44220 172.217.0.36:443 ESTABLISHED 3110/main keepalive (25.52/0/0)
使用代理和 http/1.1
Every 0.5s: netstat -anop | grep ":3128"
Sun Dec 5 13:09:44 2021
tcp 0 0 10.53.74.64:58370 {{proxy-ip}}:3128 ESTABLISHED 26322/main keepalive (23.70/0/0)
tcp 0 0 10.53.74.64:58366 {{proxy-ip}}:3128 ESTABLISHED 26322/main keepalive (23.70/0/0)
tcp 0 0 10.53.74.64:58374 {{proxy-ip}}:3128 ESTABLISHED 26322/main keepalive (23.70/0/0)
tcp 0 0 10.53.74.64:58368 {{proxy-ip}}:3128 ESTABLISHED 26322/main keepalive (23.69/0/0)
tcp 0 0 10.53.74.64:58372 {{proxy-ip}}:3128 ESTABLISHED 26322/main keepalive (23.70/0/0)
使用代理和 http2
如下所示,其他连接在 ESTABLISHED 后不久关闭,并且在 http 传输上启用了 idleConn 配置。
Sun Dec 5 13:19:35 UTC 2021
tcp 0 284 10.53.74.64:58414 {{proxy-ip}}:3128 ESTABLISHED 34301/main on (0.20/0/0)
tcp 0 284 10.53.74.64:58416 {{proxy-ip}}:3128 ESTABLISHED 34301/main on (0.19/0/0)
tcp 0 284 10.53.74.64:58410 {{proxy-ip}}:3128 ESTABLISHED 34301/main on (0.19/0/0)
tcp 0 284 10.53.74.64:58412 {{proxy-ip}}:3128 ESTABLISHED 34301/main on (0.19/0/0)
tcp 0 284 10.53.74.64:58418 {{proxy-ip}}:3128 ESTABLISHED 34301/main on (0.19/0/0)
Sun Dec 5 13:19:35 UTC 2021
tcp 0 89 10.53.74.64:58414 {{proxy-ip}}:3128 FIN_WAIT1 - on (0.14/0/0)
tcp 0 89 10.53.74.64:58416 {{proxy-ip}}:3128 FIN_WAIT1 - on (0.14/0/0)
tcp 0 365 10.53.74.64:58410 {{proxy-ip}}:3128 ESTABLISHED 34301/main on (0.14/0/0)
tcp 0 89 10.53.74.64:58412 {{proxy-ip}}:3128 FIN_WAIT1 - on (0.14/0/0)
tcp 0 89 10.53.74.64:58418 {{proxy-ip}}:3128 FIN_WAIT1 - on (0.14/0/0)
Sun Dec 5 13:19:35 UTC 2021
tcp 0 0 10.53.74.64:58414 {{proxy-ip}}:3128 TIME_WAIT - timewait (59.99/0/0)
tcp 0 0 10.53.74.64:58416 {{proxy-ip}}:3128 TIME_WAIT - timewait (59.99/0/0)
tcp 0 31 10.53.74.64:58410 {{proxy-ip}}:3128 ESTABLISHED 34301/main on (0.26/0/0)
tcp 0 0 10.53.74.64:58412 {{proxy-ip}}:3128 TIME_WAIT - timewait (59.99/0/0)
tcp 0 0 10.53.74.64:58418 {{proxy-ip}}:3128 TIME_WAIT - timewait (59.99/0/0)
最新的golang 1.17.4(目前:2021-12-05)可以复现。有没有人可以提供一种方法来弄清楚为什么会发生这种情况?谢谢。
EDIT1:所有请求均无误完成。
EDIT2:我在 Golang 回购上打开了一个问题,以防有人感兴趣:
https://github.com/golang/go/issues/50000
最后,我弄明白了,一切都按预期工作,所以这与 net/http2 无关。
更多详情请参考本期:https://github.com/golang/go/issues/50000.
0x00 TL;DR
我正在使用 Go 实现一个 http 客户端,并使用 squid 作为转发代理向远程服务器发送请求。当通过代理使用 http/1.1 或 http/1.1, 没有代理的 http2 时一切顺利,但是,当通过代理使用 http2 客户端时,大多数连接立即关闭,只有一两个连接被保留.
不确定是我的错误代码还是什么。在 http 传输上启用了 idleConn 配置。提前致谢。
0x01 环境
代码
package main
import (
"context"
"crypto/tls"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"sync"
"time"
)
const (
proxyURL = "http://{{proxy-ip}}:3128"
)
func main() {
wg := &sync.WaitGroup{}
wg.Add(1)
go func() {
doSendRequests()
wg.Done()
}()
wg.Wait()
}
func doSendRequests() {
//client := newHTTPClient(nil)
client := newHTTPClient(proxy)
round := 0
for {
round += 1
for i := 0; i < 5; i++ {
go func(r int) {
start := time.Now()
var err error
defer func() {
duration := time.Since(start)
fmt.Printf("Round: %d, A: %v, duration: %v\n", r, err, duration)
}()
req := newRequest("https://www.google.com")
resp, err := client.Do(req)
if err == nil {
io.Copy(ioutil.Discard, resp.Body)
resp.Body.Close()
} else {
fmt.Printf("A: %v\n", err)
}
}(round)
}
time.Sleep(100 * time.Second)
}
}
type TLSDialer struct {
net.Dialer
}
var (
config = &tls.Config{InsecureSkipVerify: true}
)
func (dialer *TLSDialer) DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
return tls.DialWithDialer(&dialer.Dialer, network, addr, config)
}
var (
DefaultDialer = net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}
DefaultTLSDialer = TLSDialer{DefaultDialer}
)
func proxy(req *http.Request) (*url.URL, error) {
uri, err := url.Parse(proxyURL)
if err != nil {
return nil, nil
}
return uri, nil
}
func newHTTPClient(proxy func(*http.Request) (*url.URL, error)) *http.Client {
t := &http.Transport{
DialContext: (&DefaultDialer).DialContext,
DisableKeepAlives: false,
DisableCompression: false,
ForceAttemptHTTP2: true,
MaxIdleConns: 5000,
MaxIdleConnsPerHost: 1000,
IdleConnTimeout: 0 * time.Second,
ExpectContinueTimeout: 1 * time.Second,
}
if proxy == nil {
t.DialTLSContext = (&DefaultTLSDialer).DialContext
} else {
t.Proxy = proxy
t.TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
return &http.Client{Transport: t}
}
func newRequest(uri string) *http.Request {
ctx, _ := context.WithTimeout(context.Background(), 60*time.Second)
req, _ := http.NewRequestWithContext(ctx, "GET", uri, nil)
return req
}
环境
进入 1.15.3
GO111MODULE=""
GOARCH="amd64"
GOBIN=""
GOCACHE="/home/ec2-user/.cache/go-build"
GOENV="/home/ec2-user/.config/go/env"
GOEXE=""
GOFLAGS=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOINSECURE=""
GOMODCACHE="/home/ec2-user/go/pkg/mod"
GONOPROXY=""
GONOSUMDB=""
GOOS="linux"
GOPATH="/home/ec2-user/go"
GOPRIVATE=""
GOPROXY="https://proxy.golang.org,direct"
GOROOT="/usr/local/go"
GOSUMDB="sum.golang.org"
GOTMPDIR=""
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
GCCGO="gccgo"
AR="ar"
CC="gcc"
CXX="g++"
CGO_ENABLED="1"
GOMOD=""
CGO_CFLAGS="-g -O2"
CGO_CPPFLAGS=""
CGO_CXXFLAGS="-g -O2"
CGO_FFLAGS="-g -O2"
CGO_LDFLAGS="-g -O2"
PKG_CONFIG="pkg-config"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build200120630=/tmp/go-build -gno-record-gcc-switches"
uname -a
Linux ip-10-53-74-64.ec2.internal 4.14.243-185.433.amzn2.x86_64 #1 SMP Mon Aug 9 05:55:52 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
鱿鱼4.15
0x02现象
正如我在摘要中提到的,当我通过代理使用 http/1.1 或不使用代理的 http/1.1, http2 时一切顺利。但是,当通过代理使用 http2 客户端时,将创建与请求一样多的连接,然后关闭其他连接,只留下一两个连接。我用 Wireshark 抓包,发现连接最初是从客户端关闭的,所以它可能与代理端无关,对此不确定。
没有代理
// www.google.com
Every 0.5s: netstat -anop | grep '172.217'
Sat Nov 27 04:26:22 2021
tcp 0 0 10.53.74.64:44106 172.217.0.36:443 ESTABLISHED 3110/main keepalive (25.35/0/0)
tcp 0 0 10.53.74.64:44378 172.217.0.36:443 ESTABLISHED 3110/main keepalive (25.44/0/0)
tcp 0 0 10.53.74.64:44084 172.217.0.36:443 ESTABLISHED 3110/main keepalive (25.60/0/0)
tcp 0 0 10.53.74.64:44374 172.217.0.36:443 ESTABLISHED 3110/main keepalive (25.43/0/0)
tcp 0 0 10.53.74.64:44220 172.217.0.36:443 ESTABLISHED 3110/main keepalive (25.52/0/0)
使用代理和 http/1.1
Every 0.5s: netstat -anop | grep ":3128"
Sun Dec 5 13:09:44 2021
tcp 0 0 10.53.74.64:58370 {{proxy-ip}}:3128 ESTABLISHED 26322/main keepalive (23.70/0/0)
tcp 0 0 10.53.74.64:58366 {{proxy-ip}}:3128 ESTABLISHED 26322/main keepalive (23.70/0/0)
tcp 0 0 10.53.74.64:58374 {{proxy-ip}}:3128 ESTABLISHED 26322/main keepalive (23.70/0/0)
tcp 0 0 10.53.74.64:58368 {{proxy-ip}}:3128 ESTABLISHED 26322/main keepalive (23.69/0/0)
tcp 0 0 10.53.74.64:58372 {{proxy-ip}}:3128 ESTABLISHED 26322/main keepalive (23.70/0/0)
使用代理和 http2
如下所示,其他连接在 ESTABLISHED 后不久关闭,并且在 http 传输上启用了 idleConn 配置。
Sun Dec 5 13:19:35 UTC 2021
tcp 0 284 10.53.74.64:58414 {{proxy-ip}}:3128 ESTABLISHED 34301/main on (0.20/0/0)
tcp 0 284 10.53.74.64:58416 {{proxy-ip}}:3128 ESTABLISHED 34301/main on (0.19/0/0)
tcp 0 284 10.53.74.64:58410 {{proxy-ip}}:3128 ESTABLISHED 34301/main on (0.19/0/0)
tcp 0 284 10.53.74.64:58412 {{proxy-ip}}:3128 ESTABLISHED 34301/main on (0.19/0/0)
tcp 0 284 10.53.74.64:58418 {{proxy-ip}}:3128 ESTABLISHED 34301/main on (0.19/0/0)
Sun Dec 5 13:19:35 UTC 2021
tcp 0 89 10.53.74.64:58414 {{proxy-ip}}:3128 FIN_WAIT1 - on (0.14/0/0)
tcp 0 89 10.53.74.64:58416 {{proxy-ip}}:3128 FIN_WAIT1 - on (0.14/0/0)
tcp 0 365 10.53.74.64:58410 {{proxy-ip}}:3128 ESTABLISHED 34301/main on (0.14/0/0)
tcp 0 89 10.53.74.64:58412 {{proxy-ip}}:3128 FIN_WAIT1 - on (0.14/0/0)
tcp 0 89 10.53.74.64:58418 {{proxy-ip}}:3128 FIN_WAIT1 - on (0.14/0/0)
Sun Dec 5 13:19:35 UTC 2021
tcp 0 0 10.53.74.64:58414 {{proxy-ip}}:3128 TIME_WAIT - timewait (59.99/0/0)
tcp 0 0 10.53.74.64:58416 {{proxy-ip}}:3128 TIME_WAIT - timewait (59.99/0/0)
tcp 0 31 10.53.74.64:58410 {{proxy-ip}}:3128 ESTABLISHED 34301/main on (0.26/0/0)
tcp 0 0 10.53.74.64:58412 {{proxy-ip}}:3128 TIME_WAIT - timewait (59.99/0/0)
tcp 0 0 10.53.74.64:58418 {{proxy-ip}}:3128 TIME_WAIT - timewait (59.99/0/0)
最新的golang 1.17.4(目前:2021-12-05)可以复现。有没有人可以提供一种方法来弄清楚为什么会发生这种情况?谢谢。
EDIT1:所有请求均无误完成。
EDIT2:我在 Golang 回购上打开了一个问题,以防有人感兴趣: https://github.com/golang/go/issues/50000
最后,我弄明白了,一切都按预期工作,所以这与 net/http2 无关。
更多详情请参考本期:https://github.com/golang/go/issues/50000.