mirror of
https://github.com/eolinker/apinto
synced 2025-10-22 08:19:34 +08:00
257 lines
5.7 KiB
Go
257 lines
5.7 KiB
Go
package fasthttp_client
|
|
|
|
import (
|
|
"fmt"
|
|
"net"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/eolinker/eosc/eocontext"
|
|
|
|
"github.com/valyala/fasthttp"
|
|
)
|
|
|
|
type Addr struct {
|
|
IP net.IP
|
|
Port int
|
|
}
|
|
|
|
func resolveAddr(scheme string, addr string) (*Addr, error) {
|
|
as := strings.Split(addr, ":")
|
|
if len(as) < 2 {
|
|
if scheme == "http" {
|
|
addr = fmt.Sprintf("%s:80", addr)
|
|
} else if scheme == "https" {
|
|
addr = fmt.Sprintf("%s:443", addr)
|
|
}
|
|
}
|
|
tcpAddr, err := net.ResolveTCPAddr("tcp", addr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Addr{
|
|
IP: tcpAddr.IP,
|
|
Port: tcpAddr.Port,
|
|
}, nil
|
|
}
|
|
|
|
func ProxyTimeout(scheme string, node eocontext.INode, req *fasthttp.Request, resp *fasthttp.Response, timeout time.Duration) (*Addr, error) {
|
|
tcpAddr, err := resolveAddr(scheme, node.Addr())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
addr := fmt.Sprintf("%s://%s:%d", scheme, tcpAddr.IP.String(), tcpAddr.Port)
|
|
err = defaultClient.ProxyTimeout(addr, req, resp, timeout)
|
|
if err != nil {
|
|
node.Down()
|
|
}
|
|
return tcpAddr, err
|
|
}
|
|
|
|
var defaultClient Client
|
|
|
|
const (
|
|
DefaultMaxConns = 10240
|
|
DefaultMaxConnWaitTimeout = time.Second * 60
|
|
DefaultMaxRedirectCount = 2
|
|
)
|
|
|
|
// Client implements http client.
|
|
//
|
|
// Copying Client by value is prohibited. Create new instance instead.
|
|
//
|
|
// It is safe calling Client methods from concurrently running goroutines.
|
|
//
|
|
// The fields of a Client should not be changed while it is in use.
|
|
type Client struct {
|
|
mLock sync.Mutex
|
|
m map[string]*fasthttp.HostClient
|
|
ms map[string]*fasthttp.HostClient
|
|
}
|
|
|
|
func readAddress(addr string) (scheme, host string) {
|
|
if i := strings.Index(addr, "://"); i > 0 {
|
|
return strings.ToLower(addr[:i]), addr[i+3:]
|
|
}
|
|
return "http", addr
|
|
}
|
|
|
|
func (c *Client) getHostClient(addr string) (*fasthttp.HostClient, string, error) {
|
|
|
|
scheme, host := readAddress(addr)
|
|
|
|
isTLS := false
|
|
if strings.EqualFold(scheme, "https") {
|
|
isTLS = true
|
|
} else if !strings.EqualFold(scheme, "http") {
|
|
return nil, "", fmt.Errorf("unsupported protocol %q. http and https are supported", scheme)
|
|
}
|
|
|
|
startCleaner := false
|
|
|
|
c.mLock.Lock()
|
|
m := c.m
|
|
if isTLS {
|
|
m = c.ms
|
|
}
|
|
if m == nil {
|
|
m = make(map[string]*fasthttp.HostClient)
|
|
if isTLS {
|
|
c.ms = m
|
|
} else {
|
|
c.m = m
|
|
}
|
|
}
|
|
hc := m[host]
|
|
if hc == nil {
|
|
hc = &fasthttp.HostClient{
|
|
Addr: addMissingPort(host, isTLS),
|
|
IsTLS: isTLS,
|
|
Dial: Dial,
|
|
MaxConns: DefaultMaxConns,
|
|
MaxConnWaitTimeout: DefaultMaxConnWaitTimeout,
|
|
RetryIf: func(request *fasthttp.Request) bool {
|
|
return false
|
|
},
|
|
}
|
|
m[host] = hc
|
|
if len(m) == 1 {
|
|
startCleaner = true
|
|
}
|
|
}
|
|
c.mLock.Unlock()
|
|
|
|
if startCleaner {
|
|
go c.mCleaner(m)
|
|
}
|
|
return hc, scheme, nil
|
|
}
|
|
|
|
//func (c *Client) getDialFunc() fasthttp.DialFunc {
|
|
// return func(addr string) (net.Conn, error) {
|
|
// atomic.AddInt64(&dialCount, 1)
|
|
// conn, err := tcpDial.Dial(addr)
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
// c.conn = conn
|
|
// return &debugConn{Conn: conn}, nil
|
|
// }
|
|
//}
|
|
//
|
|
//func (c *Client) RemoteAddr() string {
|
|
// if c.conn != nil {
|
|
// return c.conn.RemoteAddr().String()
|
|
// }
|
|
// return "unknown"
|
|
//}
|
|
//
|
|
//func (c *Client) LocalAddr() string {
|
|
// if c.conn != nil {
|
|
// return c.conn.RemoteAddr().String()
|
|
// }
|
|
// return "unknown"
|
|
//}
|
|
|
|
// ProxyTimeout performs the given request and waits for response during
|
|
// the given timeout duration.
|
|
//
|
|
// Request must contain at least non-zero RequestURI with full url (including
|
|
// scheme and host) or non-zero Host header + RequestURI.
|
|
//
|
|
// Client determines the server to be requested in the following order:
|
|
//
|
|
// - from RequestURI if it contains full url with scheme and host;
|
|
// - from Host header otherwise.
|
|
//
|
|
// The function doesn't follow redirects. Use Get* for following redirects.
|
|
//
|
|
// Response is ignored if resp is nil.
|
|
//
|
|
// ErrTimeout is returned if the response wasn't returned during
|
|
// the given timeout.
|
|
//
|
|
// ErrNoFreeConns is returned if all Client.MaxConnsPerHost connections
|
|
// to the requested host are busy.
|
|
//
|
|
// It is recommended obtaining req and resp via AcquireRequest
|
|
// and AcquireResponse in performance-critical code.
|
|
//
|
|
// Warning: ProxyTimeout does not terminate the request itself. The request will
|
|
// continue in the background and the response will be discarded.
|
|
// If requests take too long and the connection pool gets filled up please
|
|
// try setting a ReadTimeout.
|
|
func (c *Client) ProxyTimeout(addr string, req *fasthttp.Request, resp *fasthttp.Response, timeout time.Duration) error {
|
|
client, scheme, err := c.getHostClient(addr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
request := req
|
|
request.URI().SetScheme(scheme)
|
|
request.Header.ResetConnectionClose()
|
|
request.Header.Set("Connection", "keep-alive")
|
|
|
|
connectionClose := resp.ConnectionClose()
|
|
err = client.DoTimeout(request, resp, timeout)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if fasthttp.StatusCodeIsRedirect(resp.StatusCode()) {
|
|
err = client.DoRedirects(request, resp, DefaultMaxRedirectCount)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
if connectionClose {
|
|
resp.SetConnectionClose()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *Client) mCleaner(m map[string]*fasthttp.HostClient) {
|
|
mustStop := false
|
|
|
|
//sleep := c.MaxIdleConnDuration
|
|
//if sleep < time.Second {
|
|
// sleep = time.Second
|
|
//} else if sleep > 10*time.Second {
|
|
// sleep = 10 * time.Second
|
|
//}
|
|
sleep := time.Second * 10
|
|
for {
|
|
c.mLock.Lock()
|
|
for k, v := range m {
|
|
shouldRemove := v.ConnsCount() == 0
|
|
if shouldRemove {
|
|
delete(m, k)
|
|
}
|
|
}
|
|
if len(m) == 0 {
|
|
mustStop = true
|
|
}
|
|
|
|
c.mLock.Unlock()
|
|
|
|
if mustStop {
|
|
break
|
|
}
|
|
time.Sleep(sleep)
|
|
}
|
|
}
|
|
|
|
func addMissingPort(addr string, isTLS bool) string {
|
|
n := strings.Index(addr, ":")
|
|
if n >= 0 {
|
|
return addr
|
|
}
|
|
port := 80
|
|
if isTLS {
|
|
port = 443
|
|
}
|
|
return net.JoinHostPort(addr, strconv.Itoa(port))
|
|
}
|