github.com/fumiama/terasu@v0.0.0-20240507144117-547a591149c0/http/http.go (about)

     1  package http
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"errors"
     7  	"io"
     8  	"net"
     9  	"net/http"
    10  	"net/url"
    11  	"time"
    12  
    13  	"github.com/FloatTech/ttl"
    14  
    15  	"github.com/fumiama/terasu"
    16  	"github.com/fumiama/terasu/dns"
    17  	"github.com/fumiama/terasu/ip"
    18  )
    19  
    20  var (
    21  	ErrNoTLSConnection  = errors.New("no tls connection")
    22  	ErrEmptyHostAddress = errors.New("empty host addr")
    23  )
    24  
    25  var defaultDialer = net.Dialer{
    26  	Timeout: time.Minute,
    27  }
    28  
    29  func SetDefaultClientTimeout(t time.Duration) {
    30  	defaultDialer.Timeout = t
    31  }
    32  
    33  var lookupTable = ttl.NewCache[string, []string](time.Hour)
    34  
    35  var DefaultClient = http.Client{
    36  	Transport: &http.Transport{
    37  		DialTLSContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
    38  			if defaultDialer.Timeout != 0 {
    39  				var cancel context.CancelFunc
    40  				ctx, cancel = context.WithTimeout(ctx, defaultDialer.Timeout)
    41  				defer cancel()
    42  			}
    43  
    44  			if !defaultDialer.Deadline.IsZero() {
    45  				var cancel context.CancelFunc
    46  				ctx, cancel = context.WithDeadline(ctx, defaultDialer.Deadline)
    47  				defer cancel()
    48  			}
    49  
    50  			host, port, err := net.SplitHostPort(addr)
    51  			if err != nil {
    52  				return nil, err
    53  			}
    54  			addrs := lookupTable.Get(host)
    55  			if len(addrs) == 0 {
    56  				addrs, err = dns.DefaultResolver.LookupHost(ctx, host)
    57  				if err != nil {
    58  					if ip.IsIPv6Available.Get() {
    59  						addrs, err = dns.IPv6Servers.LookupHostFallback(ctx, host)
    60  					} else {
    61  						addrs, err = dns.IPv4Servers.LookupHostFallback(ctx, host)
    62  					}
    63  					if err != nil {
    64  						return nil, err
    65  					}
    66  				}
    67  				lookupTable.Set(host, addrs)
    68  			}
    69  			if len(addr) == 0 {
    70  				return nil, ErrEmptyHostAddress
    71  			}
    72  			var conn net.Conn
    73  			var tlsConn *tls.Conn
    74  			for _, a := range addrs {
    75  				conn, err = defaultDialer.DialContext(ctx, network, net.JoinHostPort(a, port))
    76  				if err != nil {
    77  					continue
    78  				}
    79  				tlsConn = tls.Client(conn, &tls.Config{
    80  					ServerName: host,
    81  				})
    82  				err = terasu.Use(tlsConn).HandshakeContext(ctx, terasu.DefaultFirstFragmentLen)
    83  				if err == nil {
    84  					break
    85  				}
    86  				_ = tlsConn.Close()
    87  				tlsConn = nil
    88  				conn, err = defaultDialer.DialContext(ctx, network, net.JoinHostPort(a, port))
    89  				if err != nil {
    90  					continue
    91  				}
    92  				tlsConn = tls.Client(conn, &tls.Config{
    93  					ServerName: host,
    94  				})
    95  				err = tlsConn.HandshakeContext(ctx)
    96  				if err == nil {
    97  					break
    98  				}
    99  				_ = tlsConn.Close()
   100  				tlsConn = nil
   101  			}
   102  			return tlsConn, err
   103  		},
   104  	},
   105  }
   106  
   107  func Get(url string) (resp *http.Response, err error) {
   108  	return DefaultClient.Get(url)
   109  }
   110  
   111  func Head(url string) (resp *http.Response, err error) {
   112  	return DefaultClient.Head(url)
   113  }
   114  
   115  func Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) {
   116  	return DefaultClient.Post(url, contentType, body)
   117  }
   118  
   119  func PostForm(url string, data url.Values) (resp *http.Response, err error) {
   120  	return DefaultClient.PostForm(url, data)
   121  }