github.com/igoogolx/clash@v1.19.8/adapter/outbound/http.go (about) 1 package outbound 2 3 import ( 4 "bufio" 5 "context" 6 "crypto/tls" 7 "encoding/base64" 8 "errors" 9 "fmt" 10 "io" 11 "net" 12 "net/http" 13 "net/url" 14 "strconv" 15 16 "github.com/igoogolx/clash/component/dialer" 17 C "github.com/igoogolx/clash/constant" 18 ) 19 20 type Http struct { 21 *Base 22 user string 23 pass string 24 tlsConfig *tls.Config 25 Headers http.Header 26 } 27 28 type HttpOption struct { 29 BasicOption 30 Name string `proxy:"name"` 31 Server string `proxy:"server"` 32 Port int `proxy:"port"` 33 UserName string `proxy:"username,omitempty"` 34 Password string `proxy:"password,omitempty"` 35 TLS bool `proxy:"tls,omitempty"` 36 SNI string `proxy:"sni,omitempty"` 37 SkipCertVerify bool `proxy:"skip-cert-verify,omitempty"` 38 Headers map[string]string `proxy:"headers,omitempty"` 39 } 40 41 // StreamConn implements C.ProxyAdapter 42 func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) { 43 if h.tlsConfig != nil { 44 cc := tls.Client(c, h.tlsConfig) 45 ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout) 46 defer cancel() 47 err := cc.HandshakeContext(ctx) 48 c = cc 49 if err != nil { 50 return nil, fmt.Errorf("%s connect error: %w", h.addr, err) 51 } 52 } 53 54 if err := h.shakeHand(metadata, c); err != nil { 55 return nil, err 56 } 57 return c, nil 58 } 59 60 // DialContext implements C.ProxyAdapter 61 func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata, opts ...dialer.Option) (_ C.Conn, err error) { 62 c, err := dialer.DialContext(ctx, "tcp", h.addr, h.Base.DialOptions(opts...)...) 63 if err != nil { 64 return nil, fmt.Errorf("%s connect error: %w", h.addr, err) 65 } 66 tcpKeepAlive(c) 67 68 defer func(c net.Conn) { 69 safeConnClose(c, err) 70 }(c) 71 72 c, err = h.StreamConn(c, metadata) 73 if err != nil { 74 return nil, err 75 } 76 77 return NewConn(c, h), nil 78 } 79 80 func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error { 81 addr := metadata.RemoteAddress() 82 req := &http.Request{ 83 Method: http.MethodConnect, 84 URL: &url.URL{ 85 Host: addr, 86 }, 87 Host: addr, 88 Header: h.Headers.Clone(), 89 } 90 91 req.Header.Add("Proxy-Connection", "Keep-Alive") 92 93 if h.user != "" && h.pass != "" { 94 auth := h.user + ":" + h.pass 95 req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth))) 96 } 97 98 if err := req.Write(rw); err != nil { 99 return err 100 } 101 102 resp, err := http.ReadResponse(bufio.NewReader(rw), req) 103 if err != nil { 104 return err 105 } 106 107 if resp.StatusCode == http.StatusOK { 108 return nil 109 } 110 111 if resp.StatusCode == http.StatusProxyAuthRequired { 112 return errors.New("HTTP need auth") 113 } 114 115 if resp.StatusCode == http.StatusMethodNotAllowed { 116 return errors.New("CONNECT method not allowed by proxy") 117 } 118 119 if resp.StatusCode >= http.StatusInternalServerError { 120 return errors.New(resp.Status) 121 } 122 123 return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode) 124 } 125 126 func NewHttp(option HttpOption) *Http { 127 var tlsConfig *tls.Config 128 if option.TLS { 129 sni := option.Server 130 if option.SNI != "" { 131 sni = option.SNI 132 } 133 tlsConfig = &tls.Config{ 134 InsecureSkipVerify: option.SkipCertVerify, 135 ServerName: sni, 136 } 137 } 138 139 headers := http.Header{} 140 for name, value := range option.Headers { 141 headers.Add(name, value) 142 } 143 144 return &Http{ 145 Base: &Base{ 146 name: option.Name, 147 addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)), 148 tp: C.Http, 149 iface: option.Interface, 150 rmark: option.RoutingMark, 151 }, 152 user: option.UserName, 153 pass: option.Password, 154 tlsConfig: tlsConfig, 155 Headers: headers, 156 } 157 }