go-micro.dev/v5@v5.12.0/transport/http_proxy.go (about)

     1  package transport
     2  
     3  import (
     4  	"bufio"
     5  	"encoding/base64"
     6  	"fmt"
     7  	"io"
     8  	"net"
     9  	"net/http"
    10  	"net/http/httputil"
    11  	"net/url"
    12  )
    13  
    14  const (
    15  	proxyAuthHeader = "Proxy-Authorization"
    16  )
    17  
    18  func getURL(addr string) (*url.URL, error) {
    19  	r := &http.Request{
    20  		URL: &url.URL{
    21  			Scheme: "https",
    22  			Host:   addr,
    23  		},
    24  	}
    25  
    26  	return http.ProxyFromEnvironment(r)
    27  }
    28  
    29  type pbuffer struct {
    30  	net.Conn
    31  	r io.Reader
    32  }
    33  
    34  func (p *pbuffer) Read(b []byte) (int, error) {
    35  	return p.r.Read(b)
    36  }
    37  
    38  func proxyDial(conn net.Conn, addr string, proxyURL *url.URL) (_ net.Conn, err error) {
    39  	defer func() {
    40  		if err != nil {
    41  			// trunk-ignore(golangci-lint/errcheck)
    42  			conn.Close()
    43  		}
    44  	}()
    45  
    46  	r := &http.Request{
    47  		Method: http.MethodConnect,
    48  		URL:    &url.URL{Host: addr},
    49  		Header: map[string][]string{"User-Agent": {"micro/latest"}},
    50  	}
    51  
    52  	if user := proxyURL.User; user != nil {
    53  		u := user.Username()
    54  		p, _ := user.Password()
    55  		auth := []byte(u + ":" + p)
    56  		basicAuth := base64.StdEncoding.EncodeToString(auth)
    57  		r.Header.Add(proxyAuthHeader, "Basic "+basicAuth)
    58  	}
    59  
    60  	if err := r.Write(conn); err != nil {
    61  		return nil, fmt.Errorf("failed to write the HTTP request: %w", err)
    62  	}
    63  
    64  	br := bufio.NewReader(conn)
    65  
    66  	rsp, err := http.ReadResponse(br, r)
    67  	if err != nil {
    68  		return nil, fmt.Errorf("reading server HTTP response: %w", err)
    69  	}
    70  
    71  	defer func() {
    72  		err = rsp.Body.Close()
    73  	}()
    74  
    75  	if rsp.StatusCode != http.StatusOK {
    76  		dump, err := httputil.DumpResponse(rsp, true)
    77  		if err != nil {
    78  			return nil, fmt.Errorf("failed to do connect handshake, status code: %s", rsp.Status)
    79  		}
    80  
    81  		return nil, fmt.Errorf("failed to do connect handshake, response: %q", dump)
    82  	}
    83  
    84  	return &pbuffer{Conn: conn, r: br}, nil
    85  }
    86  
    87  // Creates a new connection.
    88  func newConn(dial func(string) (net.Conn, error)) func(string) (net.Conn, error) {
    89  	return func(addr string) (net.Conn, error) {
    90  		// get the proxy url
    91  		proxyURL, err := getURL(addr)
    92  		if err != nil {
    93  			return nil, err
    94  		}
    95  
    96  		// set to addr
    97  		callAddr := addr
    98  
    99  		// got proxy
   100  		if proxyURL != nil {
   101  			callAddr = proxyURL.Host
   102  		}
   103  
   104  		// dial the addr
   105  		c, err := dial(callAddr)
   106  		if err != nil {
   107  			return nil, err
   108  		}
   109  
   110  		// do proxy connect if we have proxy url
   111  		if proxyURL != nil {
   112  			c, err = proxyDial(c, addr, proxyURL)
   113  		}
   114  
   115  		return c, err
   116  	}
   117  }