Files
EdgeNode/internal/nodes/http_client_pool.go

302 lines
7.4 KiB
Go
Raw Normal View History

2020-09-27 15:26:06 +08:00
package nodes
import (
"context"
"crypto/tls"
"errors"
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
"github.com/TeaOSLab/EdgeNode/internal/utils/fasttime"
2024-05-11 09:23:54 +08:00
"github.com/TeaOSLab/EdgeNode/internal/utils/goman"
2024-04-17 18:24:21 +08:00
"github.com/cespare/xxhash/v2"
"github.com/pires/go-proxyproto"
2023-06-23 11:43:02 +08:00
"golang.org/x/net/http2"
2020-09-27 15:26:06 +08:00
"net"
"net/http"
"runtime"
"strconv"
"strings"
2020-09-27 15:26:06 +08:00
"sync"
"time"
)
2021-04-19 19:29:32 +08:00
// SharedHTTPClientPool HTTP客户端池单例
2020-09-27 15:26:06 +08:00
var SharedHTTPClientPool = NewHTTPClientPool()
const httpClientProxyProtocolTag = "@ProxyProtocol@"
const maxHTTPRedirects = 8
2021-04-19 19:29:32 +08:00
// HTTPClientPool 客户端池
2020-09-27 15:26:06 +08:00
type HTTPClientPool struct {
2024-04-17 18:24:21 +08:00
clientsMap map[uint64]*HTTPClient // origin key => client
2022-07-14 11:58:53 +08:00
cleanTicker *time.Ticker
locker sync.RWMutex
2020-09-27 15:26:06 +08:00
}
2021-04-19 19:29:32 +08:00
// NewHTTPClientPool 获取新对象
2020-09-27 15:26:06 +08:00
func NewHTTPClientPool() *HTTPClientPool {
2022-04-04 12:06:53 +08:00
var pool = &HTTPClientPool{
2022-07-14 11:58:53 +08:00
cleanTicker: time.NewTicker(1 * time.Hour),
2024-04-17 18:24:21 +08:00
clientsMap: map[uint64]*HTTPClient{},
2020-09-27 15:26:06 +08:00
}
goman.New(func() {
pool.cleanClients()
})
2020-09-27 15:26:06 +08:00
return pool
}
2021-04-19 19:29:32 +08:00
// Client 根据地址获取客户端
2022-04-04 12:06:53 +08:00
func (this *HTTPClientPool) Client(req *HTTPRequest,
origin *serverconfigs.OriginConfig,
originAddr string,
proxyProtocol *serverconfigs.ProxyProtocolConfig,
followRedirects bool) (rawClient *http.Client, err error) {
2020-09-27 15:26:06 +08:00
if origin.Addr == nil {
return nil, errors.New("origin addr should not be empty (originId:" + strconv.FormatInt(origin.Id, 10) + ")")
2020-09-27 15:26:06 +08:00
}
2024-04-21 15:13:45 +08:00
if req == nil || req.RawReq == nil || req.RawReq.URL == nil {
err = errors.New("invalid request url")
return
}
var originHost = req.RawReq.URL.Host
var urlPort = req.RawReq.URL.Port()
if len(urlPort) == 0 {
if req.RawReq.URL.Scheme == "http" {
urlPort = "80"
} else {
urlPort = "443"
}
2024-04-22 12:38:18 +08:00
originHost += ":" + urlPort
}
var rawKey = origin.UniqueKey() + "@" + originAddr + "@" + originHost
// if we are under available ProxyProtocol, we add client ip to key to make every client unique
var isProxyProtocol = false
if proxyProtocol != nil && proxyProtocol.IsOn {
2024-04-17 18:24:21 +08:00
rawKey += httpClientProxyProtocolTag + req.requestRemoteAddr(true)
isProxyProtocol = true
}
// follow redirects
if followRedirects {
rawKey += "@follow"
}
2024-04-17 18:24:21 +08:00
var key = xxhash.Sum64String(rawKey)
var isLnRequest = origin.Id == 0
2020-09-27 15:26:06 +08:00
2022-07-14 11:58:53 +08:00
this.locker.RLock()
client, found := this.clientsMap[key]
this.locker.RUnlock()
if found {
client.UpdateAccessTime()
return client.RawClient(), nil
}
// 这里不能使用RLock避免因为并发生成多个同样的client实例
2020-09-27 15:26:06 +08:00
this.locker.Lock()
defer this.locker.Unlock()
2022-07-14 11:58:53 +08:00
// 再次查找
client, found = this.clientsMap[key]
2020-09-27 15:26:06 +08:00
if found {
client.UpdateAccessTime()
return client.RawClient(), nil
2020-09-27 15:26:06 +08:00
}
2022-04-04 12:06:53 +08:00
var maxConnections = origin.MaxConns
var connectionTimeout = origin.ConnTimeoutDuration()
var readTimeout = origin.ReadTimeoutDuration()
var idleTimeout = origin.IdleTimeoutDuration()
var idleConns = origin.MaxIdleConns
2020-09-27 15:26:06 +08:00
// 超时时间
if connectionTimeout <= 0 {
connectionTimeout = 15 * time.Second
}
if idleTimeout <= 0 {
idleTimeout = 2 * time.Minute
}
2022-04-04 12:06:53 +08:00
var numberCPU = runtime.NumCPU()
2020-09-27 15:26:06 +08:00
if numberCPU < 8 {
numberCPU = 8
}
2021-10-01 16:45:46 +08:00
if maxConnections <= 0 {
2022-12-02 10:39:07 +08:00
maxConnections = numberCPU * 64
2021-10-01 16:45:46 +08:00
}
2020-09-27 15:26:06 +08:00
if idleConns <= 0 {
2022-12-02 10:39:07 +08:00
idleConns = numberCPU * 16
2020-09-27 15:26:06 +08:00
}
if isProxyProtocol { // ProxyProtocol无需保持太多空闲连接
idleConns = 3
} else if isLnRequest { // 可以判断为Ln节点请求
maxConnections *= 8
idleConns *= 8
idleTimeout *= 4
} else if sharedNodeConfig != nil && sharedNodeConfig.Level > 1 {
// Ln节点可以适当增加连接数
maxConnections *= 2
idleConns *= 2
}
2020-09-27 15:26:06 +08:00
// TLS通讯
2022-01-16 19:58:07 +08:00
var tlsConfig = &tls.Config{
2020-09-27 15:26:06 +08:00
InsecureSkipVerify: true,
}
if origin.Cert != nil {
2022-01-16 19:58:07 +08:00
var obj = origin.Cert.CertObject()
2020-09-27 15:26:06 +08:00
if obj != nil {
tlsConfig.InsecureSkipVerify = false
tlsConfig.Certificates = []tls.Certificate{*obj}
if len(origin.Cert.ServerName) > 0 {
tlsConfig.ServerName = origin.Cert.ServerName
}
}
}
var transport = &HTTPClientTransport{
Transport: &http.Transport{
DialContext: func(ctx context.Context, network string, addr string) (net.Conn, error) {
var realAddr = originAddr
// for redirections
if followRedirects && originHost != addr {
realAddr = addr
}
// connect
2024-04-17 18:24:21 +08:00
conn, dialErr := (&net.Dialer{
Timeout: connectionTimeout,
KeepAlive: 1 * time.Minute,
}).DialContext(ctx, network, realAddr)
2024-04-17 18:24:21 +08:00
if dialErr != nil {
return nil, dialErr
}
// handle PROXY protocol
2024-04-17 18:24:21 +08:00
proxyErr := this.handlePROXYProtocol(conn, req, proxyProtocol)
if proxyErr != nil {
return nil, proxyErr
}
return NewOriginConn(conn), nil
},
MaxIdleConns: 0,
MaxIdleConnsPerHost: idleConns,
MaxConnsPerHost: maxConnections,
IdleConnTimeout: idleTimeout,
ExpectContinueTimeout: 1 * time.Second,
2022-07-14 11:58:53 +08:00
TLSHandshakeTimeout: 5 * time.Second,
TLSClientConfig: tlsConfig,
2022-08-14 16:28:40 +08:00
ReadBufferSize: 8 * 1024,
Proxy: nil,
2020-09-27 15:26:06 +08:00
},
}
2023-06-23 11:43:02 +08:00
// support http/2
if origin.HTTP2Enabled && origin.Addr != nil && origin.Addr.Protocol == serverconfigs.ProtocolHTTPS {
_ = http2.ConfigureTransport(transport.Transport)
}
2020-09-27 15:26:06 +08:00
rawClient = &http.Client{
Timeout: readTimeout,
Transport: transport,
CheckRedirect: func(targetReq *http.Request, via []*http.Request) error {
// follow redirects
if followRedirects && len(via) <= maxHTTPRedirects {
return nil
2022-03-14 15:07:18 +08:00
}
2020-09-27 15:26:06 +08:00
return http.ErrUseLastResponse
},
}
2024-04-17 18:24:21 +08:00
this.clientsMap[key] = NewHTTPClient(rawClient, isProxyProtocol)
2020-09-27 15:26:06 +08:00
return rawClient, nil
2020-09-27 15:26:06 +08:00
}
// 清理不使用的Client
func (this *HTTPClientPool) cleanClients() {
2022-07-14 11:58:53 +08:00
for range this.cleanTicker.C {
var nowTime = fasttime.Now().Unix()
2024-04-17 18:24:21 +08:00
var expiredKeys []uint64
var expiredClients = []*HTTPClient{}
2020-09-27 15:26:06 +08:00
// lookup expired clients
this.locker.RLock()
2020-09-27 15:26:06 +08:00
for k, client := range this.clientsMap {
if client.AccessTime() < nowTime-86400 ||
2024-04-17 18:24:21 +08:00
(client.IsProxyProtocol() && client.AccessTime() < nowTime-3600) { // 超过 N 秒没有调用就关闭
expiredKeys = append(expiredKeys, k)
expiredClients = append(expiredClients, client)
}
}
this.locker.RUnlock()
// remove expired keys
if len(expiredKeys) > 0 {
this.locker.Lock()
for _, k := range expiredKeys {
2020-09-27 15:26:06 +08:00
delete(this.clientsMap, k)
}
this.locker.Unlock()
}
// close expired clients
if len(expiredClients) > 0 {
for _, client := range expiredClients {
2020-09-27 15:26:06 +08:00
client.Close()
}
}
}
}
2022-01-14 11:21:28 +08:00
// 支持PROXY Protocol
func (this *HTTPClientPool) handlePROXYProtocol(conn net.Conn, req *HTTPRequest, proxyProtocol *serverconfigs.ProxyProtocolConfig) error {
if proxyProtocol != nil &&
proxyProtocol.IsOn &&
(proxyProtocol.Version == serverconfigs.ProxyProtocolVersion1 || proxyProtocol.Version == serverconfigs.ProxyProtocolVersion2) {
2022-01-14 11:21:28 +08:00
var remoteAddr = req.requestRemoteAddr(true)
var transportProtocol = proxyproto.TCPv4
if strings.Contains(remoteAddr, ":") {
transportProtocol = proxyproto.TCPv6
}
var destAddr = conn.RemoteAddr()
var reqConn = req.RawReq.Context().Value(HTTPConnContextKey)
if reqConn != nil {
destAddr = reqConn.(net.Conn).LocalAddr()
}
2022-04-04 12:06:53 +08:00
var header = proxyproto.Header{
2022-01-14 11:21:28 +08:00
Version: byte(proxyProtocol.Version),
Command: proxyproto.PROXY,
TransportProtocol: transportProtocol,
SourceAddr: &net.TCPAddr{
IP: net.ParseIP(remoteAddr),
Port: req.requestRemotePort(),
},
DestinationAddr: destAddr,
}
_, err := header.WriteTo(conn)
if err != nil {
_ = conn.Close()
return err
}
return nil
}
return nil
}