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 }