2020-09-27 15:26:06 +08:00
|
|
|
|
package nodes
|
|
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
|
"context"
|
|
|
|
|
|
"crypto/tls"
|
|
|
|
|
|
"errors"
|
|
|
|
|
|
"github.com/TeaOSLab/EdgeCommon/pkg/serverconfigs"
|
2023-08-06 09:46:32 +08:00
|
|
|
|
"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"
|
2021-10-12 20:20:06 +08:00
|
|
|
|
"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"
|
2021-10-12 20:20:06 +08:00
|
|
|
|
"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()
|
|
|
|
|
|
|
2023-08-06 09:46:32 +08:00
|
|
|
|
const httpClientProxyProtocolTag = "@ProxyProtocol@"
|
2024-04-18 08:29:52 +08:00
|
|
|
|
const maxHTTPRedirects = 8
|
2023-08-06 09:46:32 +08:00
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
2021-12-08 15:17:45 +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,
|
2024-04-17 20:38:00 +08:00
|
|
|
|
followRedirects bool) (rawClient *http.Client, err error) {
|
2020-09-27 15:26:06 +08:00
|
|
|
|
if origin.Addr == nil {
|
2020-11-30 22:27:50 +08:00
|
|
|
|
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 {
|
2024-04-17 20:38:00 +08:00
|
|
|
|
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 10:51:11 +08:00
|
|
|
|
|
2024-04-22 12:38:18 +08:00
|
|
|
|
originHost += ":" + urlPort
|
2024-04-17 20:38:00 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
var rawKey = origin.UniqueKey() + "@" + originAddr + "@" + originHost
|
2023-08-06 09:46:32 +08:00
|
|
|
|
|
|
|
|
|
|
// 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)
|
2023-08-06 09:46:32 +08:00
|
|
|
|
isProxyProtocol = true
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-04-17 20:38:00 +08:00
|
|
|
|
// follow redirects
|
|
|
|
|
|
if followRedirects {
|
|
|
|
|
|
rawKey += "@follow"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-04-17 18:24:21 +08:00
|
|
|
|
var key = xxhash.Sum64String(rawKey)
|
|
|
|
|
|
|
2022-08-03 20:54:08 +08:00
|
|
|
|
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()
|
2020-11-30 22:27:50 +08:00
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
2023-08-06 09:46:32 +08:00
|
|
|
|
if isProxyProtocol { // ProxyProtocol无需保持太多空闲连接
|
|
|
|
|
|
idleConns = 3
|
|
|
|
|
|
} else if isLnRequest { // 可以判断为Ln节点请求
|
2022-08-03 20:54:08 +08:00
|
|
|
|
maxConnections *= 8
|
|
|
|
|
|
idleConns *= 8
|
|
|
|
|
|
idleTimeout *= 4
|
2022-08-08 08:12:49 +08:00
|
|
|
|
} else if sharedNodeConfig != nil && sharedNodeConfig.Level > 1 {
|
|
|
|
|
|
// Ln节点可以适当增加连接数
|
|
|
|
|
|
maxConnections *= 2
|
|
|
|
|
|
idleConns *= 2
|
2022-08-03 20:54:08 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
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
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2022-05-19 20:16:40 +08:00
|
|
|
|
var transport = &HTTPClientTransport{
|
|
|
|
|
|
Transport: &http.Transport{
|
2024-04-17 20:38:00 +08:00
|
|
|
|
DialContext: func(ctx context.Context, network string, addr string) (net.Conn, error) {
|
|
|
|
|
|
var realAddr = originAddr
|
|
|
|
|
|
|
|
|
|
|
|
// for redirections
|
2024-04-22 10:51:11 +08:00
|
|
|
|
if followRedirects && originHost != addr {
|
2024-04-17 20:38:00 +08:00
|
|
|
|
realAddr = addr
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// connect
|
2024-04-17 18:24:21 +08:00
|
|
|
|
conn, dialErr := (&net.Dialer{
|
2022-05-19 20:16:40 +08:00
|
|
|
|
Timeout: connectionTimeout,
|
|
|
|
|
|
KeepAlive: 1 * time.Minute,
|
2024-04-17 20:38:00 +08:00
|
|
|
|
}).DialContext(ctx, network, realAddr)
|
2024-04-17 18:24:21 +08:00
|
|
|
|
if dialErr != nil {
|
|
|
|
|
|
return nil, dialErr
|
2022-05-19 20:16:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-04-17 20:38:00 +08:00
|
|
|
|
// handle PROXY protocol
|
2024-04-17 18:24:21 +08:00
|
|
|
|
proxyErr := this.handlePROXYProtocol(conn, req, proxyProtocol)
|
|
|
|
|
|
if proxyErr != nil {
|
|
|
|
|
|
return nil, proxyErr
|
2022-05-19 20:16:40 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2023-07-31 15:49:04 +08:00
|
|
|
|
return NewOriginConn(conn), nil
|
2022-05-19 20:16:40 +08:00
|
|
|
|
},
|
|
|
|
|
|
MaxIdleConns: 0,
|
|
|
|
|
|
MaxIdleConnsPerHost: idleConns,
|
|
|
|
|
|
MaxConnsPerHost: maxConnections,
|
|
|
|
|
|
IdleConnTimeout: idleTimeout,
|
|
|
|
|
|
ExpectContinueTimeout: 1 * time.Second,
|
2022-07-14 11:58:53 +08:00
|
|
|
|
TLSHandshakeTimeout: 5 * time.Second,
|
2022-05-19 20:16:40 +08:00
|
|
|
|
TLSClientConfig: tlsConfig,
|
2022-08-14 16:28:40 +08:00
|
|
|
|
ReadBufferSize: 8 * 1024,
|
2022-05-19 20:16:40 +08:00
|
|
|
|
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,
|
2022-04-09 20:37:05 +08:00
|
|
|
|
CheckRedirect: func(targetReq *http.Request, via []*http.Request) error {
|
2024-04-18 08:29:52 +08:00
|
|
|
|
// follow redirects
|
|
|
|
|
|
if followRedirects && len(via) <= maxHTTPRedirects {
|
2024-04-17 20:38:00 +08:00
|
|
|
|
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
|
|
|
|
|
2020-11-30 22:27:50 +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 {
|
2023-08-06 09:46:32 +08:00
|
|
|
|
var nowTime = fasttime.Now().Unix()
|
|
|
|
|
|
|
2024-04-17 18:24:21 +08:00
|
|
|
|
var expiredKeys []uint64
|
2023-08-06 09:46:32 +08:00
|
|
|
|
var expiredClients = []*HTTPClient{}
|
2020-09-27 15:26:06 +08:00
|
|
|
|
|
2023-08-06 09:46:32 +08:00
|
|
|
|
// lookup expired clients
|
|
|
|
|
|
this.locker.RLock()
|
2020-09-27 15:26:06 +08:00
|
|
|
|
for k, client := range this.clientsMap {
|
2023-08-06 09:46:32 +08:00
|
|
|
|
if client.AccessTime() < nowTime-86400 ||
|
2024-04-17 18:24:21 +08:00
|
|
|
|
(client.IsProxyProtocol() && client.AccessTime() < nowTime-3600) { // 超过 N 秒没有调用就关闭
|
2023-08-06 09:46:32 +08:00
|
|
|
|
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)
|
2023-08-06 09:46:32 +08:00
|
|
|
|
}
|
|
|
|
|
|
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 {
|
2023-08-06 09:46:32 +08:00
|
|
|
|
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
|
|
|
|
|
|
}
|