github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/internal/transport/proxy.go (about)

     1  /*
     2   *
     3   * Copyright 2017 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   *
    17   */
    18  
    19  package transport
    20  
    21  import (
    22  	"bufio"
    23  	"context"
    24  	"encoding/base64"
    25  	"fmt"
    26  	"io"
    27  	"net"
    28  	"net/url"
    29  
    30  	"github.com/hxx258456/ccgo/gmhttp/httputil"
    31  
    32  	http "github.com/hxx258456/ccgo/gmhttp"
    33  )
    34  
    35  const proxyAuthHeaderKey = "Proxy-Authorization"
    36  
    37  var (
    38  	// The following variable will be overwritten in the tests.
    39  	httpProxyFromEnvironment = http.ProxyFromEnvironment
    40  )
    41  
    42  func mapAddress(address string) (*url.URL, error) {
    43  	req := &http.Request{
    44  		URL: &url.URL{
    45  			Scheme: "https",
    46  			Host:   address,
    47  		},
    48  	}
    49  	url, err := httpProxyFromEnvironment(req)
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  	return url, nil
    54  }
    55  
    56  // To read a response from a net.Conn, http.ReadResponse() takes a bufio.Reader.
    57  // It's possible that this reader reads more than what's need for the response and stores
    58  // those bytes in the buffer.
    59  // bufConn wraps the original net.Conn and the bufio.Reader to make sure we don't lose the
    60  // bytes in the buffer.
    61  type bufConn struct {
    62  	net.Conn
    63  	r io.Reader
    64  }
    65  
    66  func (c *bufConn) Read(b []byte) (int, error) {
    67  	return c.r.Read(b)
    68  }
    69  
    70  func basicAuth(username, password string) string {
    71  	auth := username + ":" + password
    72  	return base64.StdEncoding.EncodeToString([]byte(auth))
    73  }
    74  
    75  func doHTTPConnectHandshake(ctx context.Context, conn net.Conn, backendAddr string, proxyURL *url.URL, grpcUA string) (_ net.Conn, err error) {
    76  	defer func() {
    77  		if err != nil {
    78  			conn.Close()
    79  		}
    80  	}()
    81  
    82  	req := &http.Request{
    83  		Method: http.MethodConnect,
    84  		URL:    &url.URL{Host: backendAddr},
    85  		Header: map[string][]string{"User-Agent": {grpcUA}},
    86  	}
    87  	if t := proxyURL.User; t != nil {
    88  		u := t.Username()
    89  		p, _ := t.Password()
    90  		req.Header.Add(proxyAuthHeaderKey, "Basic "+basicAuth(u, p))
    91  	}
    92  
    93  	if err := sendHTTPRequest(ctx, req, conn); err != nil {
    94  		return nil, fmt.Errorf("failed to write the HTTP request: %v", err)
    95  	}
    96  
    97  	r := bufio.NewReader(conn)
    98  	resp, err := http.ReadResponse(r, req)
    99  	if err != nil {
   100  		return nil, fmt.Errorf("reading server HTTP response: %v", err)
   101  	}
   102  	defer resp.Body.Close()
   103  	if resp.StatusCode != http.StatusOK {
   104  		dump, err := httputil.DumpResponse(resp, true)
   105  		if err != nil {
   106  			return nil, fmt.Errorf("failed to do connect handshake, status code: %s", resp.Status)
   107  		}
   108  		return nil, fmt.Errorf("failed to do connect handshake, response: %q", dump)
   109  	}
   110  
   111  	return &bufConn{Conn: conn, r: r}, nil
   112  }
   113  
   114  // proxyDial dials, connecting to a proxy first if necessary. Checks if a proxy
   115  // is necessary, dials, does the HTTP CONNECT handshake, and returns the
   116  // connection.
   117  func proxyDial(ctx context.Context, addr string, grpcUA string) (conn net.Conn, err error) {
   118  	newAddr := addr
   119  	proxyURL, err := mapAddress(addr)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	if proxyURL != nil {
   124  		newAddr = proxyURL.Host
   125  	}
   126  
   127  	conn, err = (&net.Dialer{}).DialContext(ctx, "tcp", newAddr)
   128  	if err != nil {
   129  		return
   130  	}
   131  	if proxyURL != nil {
   132  		// proxy is disabled if proxyURL is nil.
   133  		conn, err = doHTTPConnectHandshake(ctx, conn, addr, proxyURL, grpcUA)
   134  	}
   135  	return
   136  }
   137  
   138  func sendHTTPRequest(ctx context.Context, req *http.Request, conn net.Conn) error {
   139  	req = req.WithContext(ctx)
   140  	if err := req.Write(conn); err != nil {
   141  		return fmt.Errorf("failed to write the HTTP request: %v", err)
   142  	}
   143  	return nil
   144  }