1
18
19 package transport
20
21 import (
22 "bufio"
23 "context"
24 "encoding/base64"
25 "fmt"
26 "io"
27 "net"
28 "net/http"
29 "net/http/httputil"
30 "net/url"
31
32 "google.golang.org/grpc/internal"
33 )
34
35 const proxyAuthHeaderKey = "Proxy-Authorization"
36
37 var (
38
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
57
58
59
60
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
115
116
117 func proxyDial(ctx context.Context, addr string, grpcUA string) (net.Conn, 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 := internal.NetDialerWithTCPKeepalive().DialContext(ctx, "tcp", newAddr)
128 if err != nil {
129 return nil, err
130 }
131 if proxyURL == nil {
132
133 return conn, err
134 }
135 return doHTTPConnectHandshake(ctx, conn, addr, proxyURL, grpcUA)
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 }
145
View as plain text