github.com/yandex/pandora@v0.5.32/components/guns/http/connect.go (about)

     1  package phttp
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"crypto/tls"
     7  	"net"
     8  	"net/http"
     9  	"net/http/httputil"
    10  	"net/url"
    11  
    12  	"github.com/pkg/errors"
    13  	"github.com/yandex/pandora/lib/netutil"
    14  	"go.uber.org/zap"
    15  )
    16  
    17  func NewConnectGun(cfg GunConfig, answLog *zap.Logger) *BaseGun {
    18  	if cfg.TargetResolved == "" {
    19  		cfg.TargetResolved = cfg.Target
    20  	}
    21  
    22  	return NewBaseGun(newConnectClient, cfg, answLog)
    23  }
    24  
    25  func DefaultConnectGunConfig() GunConfig {
    26  	return GunConfig{
    27  		SSL:    false,
    28  		Client: DefaultClientConfig(),
    29  		AutoTag: AutoTagConfig{
    30  			Enabled:     false,
    31  			URIElements: 2,
    32  			NoTagOnly:   true,
    33  		},
    34  		AnswLog: AnswLogConfig{
    35  			Enabled: false,
    36  			Path:    "answ.log",
    37  			Filter:  "error",
    38  		},
    39  		HTTPTrace: HTTPTraceConfig{
    40  			DumpEnabled:  false,
    41  			TraceEnabled: false,
    42  		},
    43  	}
    44  }
    45  
    46  func newConnectClient(conf ClientConfig, target string) Client {
    47  	transport := NewTransport(
    48  		conf.Transport,
    49  		newConnectDialFunc(
    50  			target,
    51  			conf.ConnectSSL,
    52  			NewDialer(conf.Dialer),
    53  		),
    54  		target)
    55  	return NewRedirectingClient(transport, conf.Redirect)
    56  }
    57  
    58  var _ ClientConstructor = newConnectClient
    59  
    60  func newConnectDialFunc(target string, connectSSL bool, dialer netutil.Dialer) netutil.DialerFunc {
    61  	return func(ctx context.Context, network, address string) (conn net.Conn, err error) {
    62  		// TODO(skipor): make connect sample.
    63  		// TODO(skipor): make httptrace callbacks called correctly.
    64  		if network != "tcp" {
    65  			panic("unsupported network " + network)
    66  		}
    67  		defer func() {
    68  			if err != nil && conn != nil {
    69  				_ = conn.Close()
    70  				conn = nil
    71  			}
    72  		}()
    73  		conn, err = dialer.DialContext(ctx, "tcp", target)
    74  		if err != nil {
    75  			err = errors.WithStack(err)
    76  			return
    77  		}
    78  		if connectSSL {
    79  			conn = tls.Client(conn, &tls.Config{InsecureSkipVerify: true})
    80  		}
    81  		req := &http.Request{
    82  			Method:     "CONNECT",
    83  			URL:        &url.URL{},
    84  			Proto:      "HTTP/1.1",
    85  			ProtoMajor: 1,
    86  			ProtoMinor: 1,
    87  			Header:     make(http.Header),
    88  			Body:       nil,
    89  			Host:       address,
    90  		}
    91  		// NOTE(skipor): any logic for CONNECT request can be easily added via hooks.
    92  		err = req.Write(conn)
    93  		if err != nil {
    94  			err = errors.WithStack(err)
    95  			return
    96  		}
    97  		// NOTE(skipor): according to RFC 2817 we can send origin at that moment and not wait
    98  		// for request. That requires to wrap conn and do following logic at first read.
    99  		r := bufio.NewReader(conn)
   100  		res, err := http.ReadResponse(r, req)
   101  		if err != nil {
   102  			err = errors.WithStack(err)
   103  			return
   104  		}
   105  		// RFC 7230 3.3.3.2: Any 2xx (Successful) response to a CONNECT request implies that
   106  		// the connection will become a tunnel immediately after the empty
   107  		// line that concludes the header fields. A client MUST ignore any
   108  		// Content-Length or Transfer-Encoding header fields received in
   109  		// such a message.
   110  		if res.StatusCode != http.StatusOK {
   111  			dump, dumpErr := httputil.DumpResponse(res, false)
   112  			err = errors.Errorf("Unexpected status code. Dumped response:\n%s\n Dump error: %s",
   113  				dump, dumpErr)
   114  			return
   115  		}
   116  		// No need to close body.
   117  		if r.Buffered() != 0 {
   118  			// Already receive something non HTTP from proxy or dialed server.
   119  			// Anyway it is incorrect situation.
   120  			peek, _ := r.Peek(r.Buffered())
   121  			err = errors.Errorf("Unexpected extra data after connect: %q", peek)
   122  			return
   123  		}
   124  		return
   125  	}
   126  }