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

     1  package http2
     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  	"golang.org/x/net/http2"
    15  
    16  	"github.com/fumiama/terasu"
    17  	"github.com/fumiama/terasu/dns"
    18  	"github.com/fumiama/terasu/ip"
    19  )
    20  
    21  var (
    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: &http2.Transport{
    37  		DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (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, cfg)
    80  				err = terasu.Use(tlsConn).HandshakeContext(ctx, terasu.DefaultFirstFragmentLen)
    81  				if err == nil {
    82  					break
    83  				}
    84  				_ = tlsConn.Close()
    85  				tlsConn = nil
    86  				conn, err = defaultDialer.DialContext(ctx, network, net.JoinHostPort(a, port))
    87  				if err != nil {
    88  					continue
    89  				}
    90  				tlsConn = tls.Client(conn, cfg)
    91  				err = tlsConn.HandshakeContext(ctx)
    92  				if err == nil {
    93  					break
    94  				}
    95  				_ = tlsConn.Close()
    96  				tlsConn = nil
    97  			}
    98  			return tlsConn, err
    99  		},
   100  	},
   101  }
   102  
   103  func Get(url string) (resp *http.Response, err error) {
   104  	return DefaultClient.Get(url)
   105  }
   106  
   107  func Head(url string) (resp *http.Response, err error) {
   108  	return DefaultClient.Head(url)
   109  }
   110  
   111  func Post(url string, contentType string, body io.Reader) (resp *http.Response, err error) {
   112  	return DefaultClient.Post(url, contentType, body)
   113  }
   114  
   115  func PostForm(url string, data url.Values) (resp *http.Response, err error) {
   116  	return DefaultClient.PostForm(url, data)
   117  }