istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/echo/server/forwarder/http.go (about) 1 // Copyright Istio Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package forwarder 16 17 import ( 18 "bytes" 19 "context" 20 "crypto/tls" 21 "fmt" 22 "io" 23 "net" 24 "net/http" 25 "sort" 26 "strconv" 27 "strings" 28 "time" 29 30 "github.com/quic-go/quic-go" 31 "github.com/quic-go/quic-go/http3" 32 "golang.org/x/net/http2" 33 34 "istio.io/istio/pkg/hbone" 35 "istio.io/istio/pkg/test/echo" 36 "istio.io/istio/pkg/test/echo/common/scheme" 37 "istio.io/istio/pkg/test/echo/proto" 38 ) 39 40 var _ protocol = &httpProtocol{} 41 42 type httpProtocol struct { 43 e *executor 44 } 45 46 func newHTTPProtocol(e *executor) *httpProtocol { 47 return &httpProtocol{e: e} 48 } 49 50 type httpTransportGetter func() (http.RoundTripper, func(), error) 51 52 func (c *httpProtocol) ForwardEcho(ctx context.Context, cfg *Config) (*proto.ForwardEchoResponse, error) { 53 var getTransport httpTransportGetter 54 var closeSharedTransport func() 55 56 switch { 57 case cfg.Request.Http3: 58 getTransport, closeSharedTransport = newHTTP3TransportGetter(cfg) 59 case cfg.Request.Http2: 60 getTransport, closeSharedTransport = newHTTP2TransportGetter(cfg) 61 default: 62 getTransport, closeSharedTransport = newHTTPTransportGetter(cfg) 63 } 64 65 defer closeSharedTransport() 66 67 call := &httpCall{ 68 httpProtocol: c, 69 getTransport: getTransport, 70 } 71 72 return doForward(ctx, cfg, c.e, call.makeRequest) 73 } 74 75 func newHTTP3TransportGetter(cfg *Config) (httpTransportGetter, func()) { 76 newConn := func() *http3.RoundTripper { 77 return &http3.RoundTripper{ 78 TLSClientConfig: cfg.tlsConfig, 79 QUICConfig: &quic.Config{}, 80 } 81 } 82 closeFn := func(conn *http3.RoundTripper) func() { 83 return func() { 84 _ = conn.Close() 85 } 86 } 87 noCloseFn := func() {} 88 89 if cfg.newConnectionPerRequest { 90 // Create a new transport (i.e. connection) for each request. 91 return func() (http.RoundTripper, func(), error) { 92 conn := newConn() 93 return conn, closeFn(conn), nil 94 }, noCloseFn 95 } 96 97 // Re-use the same transport for all requests. For HTTP3, this should result 98 // in multiplexing all requests over the same connection. 99 conn := newConn() 100 return func() (http.RoundTripper, func(), error) { 101 return conn, noCloseFn, nil 102 }, closeFn(conn) 103 } 104 105 func newHTTP2TransportGetter(cfg *Config) (httpTransportGetter, func()) { 106 newConn := func() *http2.Transport { 107 if cfg.scheme == scheme.HTTPS { 108 return &http2.Transport{ 109 TLSClientConfig: cfg.tlsConfig, 110 DialTLS: func(network, addr string, tlsConfig *tls.Config) (net.Conn, error) { 111 return hbone.TLSDialWithDialer(newDialer(cfg), network, addr, tlsConfig) 112 }, 113 } 114 } 115 116 return &http2.Transport{ 117 // Golang doesn't have first class support for h2c, so we provide some workarounds 118 // See https://www.mailgun.com/blog/http-2-cleartext-h2c-client-example-go/ 119 // So http2.Transport doesn't complain the URL scheme isn't 'https' 120 AllowHTTP: true, 121 // Pretend we are dialing a TLS endpoint. (Note, we ignore the passed tls.Config) 122 DialTLSContext: func(ctx context.Context, network, addr string, _ *tls.Config) (net.Conn, error) { 123 return newDialer(cfg).Dial(network, addr) 124 }, 125 } 126 } 127 closeFn := func(conn *http2.Transport) func() { 128 return conn.CloseIdleConnections 129 } 130 noCloseFn := func() {} 131 132 if cfg.newConnectionPerRequest { 133 // Create a new transport (i.e. connection) for each request. 134 return func() (http.RoundTripper, func(), error) { 135 conn := newConn() 136 return conn, closeFn(conn), nil 137 }, noCloseFn 138 } 139 140 // Re-use the same transport for all requests. For HTTP2, this should result 141 // in multiplexing all requests over the same connection. 142 conn := newConn() 143 return func() (http.RoundTripper, func(), error) { 144 return conn, noCloseFn, nil 145 }, closeFn(conn) 146 } 147 148 func newHTTPTransportGetter(cfg *Config) (httpTransportGetter, func()) { 149 newConn := func() *http.Transport { 150 dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { 151 return newDialer(cfg).DialContext(ctx, network, addr) 152 } 153 if len(cfg.UDS) > 0 { 154 dialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { 155 return newDialer(cfg).DialContext(ctx, "unix", cfg.UDS) 156 } 157 } 158 out := &http.Transport{ 159 // No connection pooling. 160 DisableKeepAlives: true, 161 TLSClientConfig: cfg.tlsConfig, 162 DialContext: dialContext, 163 } 164 165 // Set the proxy in the transport, if specified. 166 // for socks5 proxy is setup is done in the newDialer function. 167 if !strings.HasPrefix(cfg.Proxy, "socks5://") { 168 out.Proxy = cfg.proxyURL 169 } 170 return out 171 } 172 noCloseFn := func() {} 173 174 // Always create a new HTTP transport for each request, since HTTP can't multiplex over 175 // a single connection. 176 return func() (http.RoundTripper, func(), error) { 177 conn := newConn() 178 return conn, noCloseFn, nil 179 }, noCloseFn 180 } 181 182 type httpCall struct { 183 *httpProtocol 184 getTransport httpTransportGetter 185 } 186 187 func (c *httpCall) makeRequest(ctx context.Context, cfg *Config, requestID int) (string, error) { 188 start := time.Now() 189 190 r := cfg.Request 191 var outBuffer bytes.Buffer 192 echo.ForwarderURLField.WriteForRequest(&outBuffer, requestID, r.Url) 193 194 // Set the per-request timeout. 195 ctx, cancel := context.WithTimeout(ctx, cfg.timeout) 196 defer cancel() 197 198 httpReq, err := http.NewRequestWithContext(ctx, cfg.method, cfg.urlHost, nil) 199 if err != nil { 200 return outBuffer.String(), err 201 } 202 203 // Use raw path, we don't want golang normalizing anything since we use this for testing purposes 204 httpReq.URL.Opaque = cfg.urlPath 205 206 // Use the host header as the host. 207 httpReq.Host = cfg.hostHeader 208 209 // Copy the headers. 210 httpReq.Header = cfg.headers.Clone() 211 writeForwardedHeaders(&outBuffer, requestID, cfg.headers) 212 213 // Propagate previous response cookies if any 214 if cfg.PropagateResponse != nil { 215 cfg.PropagateResponse(httpReq, cfg.previousResponse) 216 } 217 // Get the transport. 218 transport, closeTransport, err := c.getTransport() 219 if err != nil { 220 return outBuffer.String(), err 221 } 222 defer closeTransport() 223 224 // Create a new HTTP client. 225 client := &http.Client{ 226 CheckRedirect: cfg.checkRedirect, 227 Timeout: cfg.timeout, 228 Transport: transport, 229 } 230 231 // Make the request. 232 httpResp, err := client.Do(httpReq) 233 if err != nil { 234 return outBuffer.String(), err 235 } 236 cfg.previousResponse = httpResp 237 238 echo.LatencyField.WriteForRequest(&outBuffer, requestID, fmt.Sprintf("%v", time.Since(start))) 239 echo.ActiveRequestsField.WriteForRequest(&outBuffer, requestID, fmt.Sprintf("%d", c.e.ActiveRequests())) 240 241 // Process the response. 242 err = processHTTPResponse(requestID, httpResp, &outBuffer) 243 244 // Extract the output string. 245 return outBuffer.String(), err 246 } 247 248 func processHTTPResponse(requestID int, httpResp *http.Response, outBuffer *bytes.Buffer) error { 249 // Make sure we close the body before exiting. 250 defer func() { 251 if err := httpResp.Body.Close(); err != nil { 252 echo.WriteError(outBuffer, requestID, err) 253 } 254 }() 255 256 echo.StatusCodeField.WriteForRequest(outBuffer, requestID, strconv.Itoa(httpResp.StatusCode)) 257 258 // Read the entire body. 259 data, err := io.ReadAll(httpResp.Body) 260 if err != nil { 261 return err 262 } 263 264 // Write the response headers to the output buffer. 265 var keys []string 266 for k := range httpResp.Header { 267 keys = append(keys, k) 268 } 269 sort.Strings(keys) 270 for _, key := range keys { 271 values := httpResp.Header[key] 272 for _, value := range values { 273 echo.ResponseHeaderField.WriteKeyValueForRequest(outBuffer, requestID, key, value) 274 } 275 } 276 277 // Write the lines of the body to the output buffer. 278 for _, line := range strings.Split(string(data), "\n") { 279 if line != "" { 280 echo.WriteBodyLine(outBuffer, requestID, line) 281 } 282 } 283 return nil 284 } 285 286 func (c *httpProtocol) Close() error { 287 return nil 288 }