github.com/laof/lite-speed-test@v0.0.0-20230930011949-1f39b7037845/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  	C "github.com/laof/lite-speed-test/constant"
    17  	"github.com/laof/lite-speed-test/transport/dialer"
    18  )
    19  
    20  type Http struct {
    21  	*Base
    22  	user      string
    23  	pass      string
    24  	tlsConfig *tls.Config
    25  }
    26  
    27  type HttpOption struct {
    28  	BasicOption
    29  	Name           string `proxy:"name"`
    30  	Remarks        string `proxy:"remarks,omitempty"`
    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  }
    39  
    40  func (h *Http) StreamConn(c net.Conn, metadata *C.Metadata) (net.Conn, error) {
    41  	if h.tlsConfig != nil {
    42  		cc := tls.Client(c, h.tlsConfig)
    43  		ctx, cancel := context.WithTimeout(context.Background(), C.DefaultTLSTimeout)
    44  		defer cancel()
    45  		err := cc.HandshakeContext(ctx)
    46  		c = cc
    47  		if err != nil {
    48  			return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
    49  		}
    50  	}
    51  
    52  	if err := h.shakeHand(metadata, c); err != nil {
    53  		return nil, err
    54  	}
    55  	return c, nil
    56  }
    57  
    58  // DialContext implements C.ProxyAdapter
    59  func (h *Http) DialContext(ctx context.Context, metadata *C.Metadata) (net.Conn, error) {
    60  	c, err := dialer.DialContext(ctx, "tcp", h.addr)
    61  	if err != nil {
    62  		return nil, fmt.Errorf("%s connect error: %w", h.addr, err)
    63  	}
    64  	tcpKeepAlive(c)
    65  
    66  	defer c.Close()
    67  
    68  	return h.StreamConn(c, metadata)
    69  }
    70  
    71  func (h *Http) shakeHand(metadata *C.Metadata, rw io.ReadWriter) error {
    72  	addr := metadata.RemoteAddress()
    73  	req := &http.Request{
    74  		Method: http.MethodConnect,
    75  		URL: &url.URL{
    76  			Host: addr,
    77  		},
    78  		Host: addr,
    79  		Header: http.Header{
    80  			"Proxy-Connection": []string{"Keep-Alive"},
    81  		},
    82  	}
    83  
    84  	if h.user != "" && h.pass != "" {
    85  		auth := h.user + ":" + h.pass
    86  		req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(auth)))
    87  	}
    88  
    89  	if err := req.Write(rw); err != nil {
    90  		return err
    91  	}
    92  
    93  	resp, err := http.ReadResponse(bufio.NewReader(rw), req)
    94  	if err != nil {
    95  		return err
    96  	}
    97  
    98  	if resp.StatusCode == http.StatusOK {
    99  		return nil
   100  	}
   101  
   102  	if resp.StatusCode == http.StatusProxyAuthRequired {
   103  		return errors.New("HTTP need auth")
   104  	}
   105  
   106  	if resp.StatusCode == http.StatusMethodNotAllowed {
   107  		return errors.New("CONNECT method not allowed by proxy")
   108  	}
   109  
   110  	if resp.StatusCode >= http.StatusInternalServerError {
   111  		return errors.New(resp.Status)
   112  	}
   113  
   114  	return fmt.Errorf("can not connect remote err code: %d", resp.StatusCode)
   115  }
   116  
   117  func (h *Http) DialUDP(metadata *C.Metadata) (net.PacketConn, error) {
   118  	return nil, errors.New("not support udp")
   119  }
   120  
   121  func NewHttp(option HttpOption) *Http {
   122  	var tlsConfig *tls.Config
   123  	if option.TLS {
   124  		sni := option.Server
   125  		if option.SNI != "" {
   126  			sni = option.SNI
   127  		}
   128  		tlsConfig = &tls.Config{
   129  			InsecureSkipVerify: option.SkipCertVerify,
   130  			ServerName:         sni,
   131  		}
   132  	}
   133  
   134  	return &Http{
   135  		Base: &Base{
   136  			name: option.Name,
   137  			addr: net.JoinHostPort(option.Server, strconv.Itoa(option.Port)),
   138  		},
   139  		user:      option.UserName,
   140  		pass:      option.Password,
   141  		tlsConfig: tlsConfig,
   142  	}
   143  }