github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/net/http/httputil/reverseproxy.go (about) 1 // Copyright 2011 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // HTTP reverse proxy handler 6 7 package httputil 8 9 import ( 10 "context" 11 "errors" 12 "fmt" 13 "io" 14 "log" 15 "mime" 16 "net" 17 "net/http" 18 "net/http/httptrace" 19 "net/http/internal/ascii" 20 "net/textproto" 21 "net/url" 22 "strings" 23 "sync" 24 "time" 25 26 "golang.org/x/net/http/httpguts" 27 ) 28 29 // A ProxyRequest contains a request to be rewritten by a [ReverseProxy]. 30 type ProxyRequest struct { 31 // In is the request received by the proxy. 32 // The Rewrite function must not modify In. 33 In *http.Request 34 35 // Out is the request which will be sent by the proxy. 36 // The Rewrite function may modify or replace this request. 37 // Hop-by-hop headers are removed from this request 38 // before Rewrite is called. 39 Out *http.Request 40 } 41 42 // SetURL routes the outbound request to the scheme, host, and base path 43 // provided in target. If the target's path is "/base" and the incoming 44 // request was for "/dir", the target request will be for "/base/dir". 45 // 46 // SetURL rewrites the outbound Host header to match the target's host. 47 // To preserve the inbound request's Host header (the default behavior 48 // of [NewSingleHostReverseProxy]): 49 // 50 // rewriteFunc := func(r *httputil.ProxyRequest) { 51 // r.SetURL(url) 52 // r.Out.Host = r.In.Host 53 // } 54 func (r *ProxyRequest) SetURL(target *url.URL) { 55 rewriteRequestURL(r.Out, target) 56 r.Out.Host = "" 57 } 58 59 // SetXForwarded sets the X-Forwarded-For, X-Forwarded-Host, and 60 // X-Forwarded-Proto headers of the outbound request. 61 // 62 // - The X-Forwarded-For header is set to the client IP address. 63 // - The X-Forwarded-Host header is set to the host name requested 64 // by the client. 65 // - The X-Forwarded-Proto header is set to "http" or "https", depending 66 // on whether the inbound request was made on a TLS-enabled connection. 67 // 68 // If the outbound request contains an existing X-Forwarded-For header, 69 // SetXForwarded appends the client IP address to it. To append to the 70 // inbound request's X-Forwarded-For header (the default behavior of 71 // [ReverseProxy] when using a Director function), copy the header 72 // from the inbound request before calling SetXForwarded: 73 // 74 // rewriteFunc := func(r *httputil.ProxyRequest) { 75 // r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"] 76 // r.SetXForwarded() 77 // } 78 func (r *ProxyRequest) SetXForwarded() { 79 clientIP, _, err := net.SplitHostPort(r.In.RemoteAddr) 80 if err == nil { 81 prior := r.Out.Header["X-Forwarded-For"] 82 if len(prior) > 0 { 83 clientIP = strings.Join(prior, ", ") + ", " + clientIP 84 } 85 r.Out.Header.Set("X-Forwarded-For", clientIP) 86 } else { 87 r.Out.Header.Del("X-Forwarded-For") 88 } 89 r.Out.Header.Set("X-Forwarded-Host", r.In.Host) 90 if r.In.TLS == nil { 91 r.Out.Header.Set("X-Forwarded-Proto", "http") 92 } else { 93 r.Out.Header.Set("X-Forwarded-Proto", "https") 94 } 95 } 96 97 // ReverseProxy is an HTTP Handler that takes an incoming request and 98 // sends it to another server, proxying the response back to the 99 // client. 100 // 101 // 1xx responses are forwarded to the client if the underlying 102 // transport supports ClientTrace.Got1xxResponse. 103 type ReverseProxy struct { 104 // Rewrite must be a function which modifies 105 // the request into a new request to be sent 106 // using Transport. Its response is then copied 107 // back to the original client unmodified. 108 // Rewrite must not access the provided ProxyRequest 109 // or its contents after returning. 110 // 111 // The Forwarded, X-Forwarded, X-Forwarded-Host, 112 // and X-Forwarded-Proto headers are removed from the 113 // outbound request before Rewrite is called. See also 114 // the ProxyRequest.SetXForwarded method. 115 // 116 // Unparsable query parameters are removed from the 117 // outbound request before Rewrite is called. 118 // The Rewrite function may copy the inbound URL's 119 // RawQuery to the outbound URL to preserve the original 120 // parameter string. Note that this can lead to security 121 // issues if the proxy's interpretation of query parameters 122 // does not match that of the downstream server. 123 // 124 // At most one of Rewrite or Director may be set. 125 Rewrite func(*ProxyRequest) 126 127 // Director is a function which modifies 128 // the request into a new request to be sent 129 // using Transport. Its response is then copied 130 // back to the original client unmodified. 131 // Director must not access the provided Request 132 // after returning. 133 // 134 // By default, the X-Forwarded-For header is set to the 135 // value of the client IP address. If an X-Forwarded-For 136 // header already exists, the client IP is appended to the 137 // existing values. As a special case, if the header 138 // exists in the Request.Header map but has a nil value 139 // (such as when set by the Director func), the X-Forwarded-For 140 // header is not modified. 141 // 142 // To prevent IP spoofing, be sure to delete any pre-existing 143 // X-Forwarded-For header coming from the client or 144 // an untrusted proxy. 145 // 146 // Hop-by-hop headers are removed from the request after 147 // Director returns, which can remove headers added by 148 // Director. Use a Rewrite function instead to ensure 149 // modifications to the request are preserved. 150 // 151 // Unparsable query parameters are removed from the outbound 152 // request if Request.Form is set after Director returns. 153 // 154 // At most one of Rewrite or Director may be set. 155 Director func(*http.Request) 156 157 // The transport used to perform proxy requests. 158 // If nil, http.DefaultTransport is used. 159 Transport http.RoundTripper 160 161 // FlushInterval specifies the flush interval 162 // to flush to the client while copying the 163 // response body. 164 // If zero, no periodic flushing is done. 165 // A negative value means to flush immediately 166 // after each write to the client. 167 // The FlushInterval is ignored when ReverseProxy 168 // recognizes a response as a streaming response, or 169 // if its ContentLength is -1; for such responses, writes 170 // are flushed to the client immediately. 171 FlushInterval time.Duration 172 173 // ErrorLog specifies an optional logger for errors 174 // that occur when attempting to proxy the request. 175 // If nil, logging is done via the log package's standard logger. 176 ErrorLog *log.Logger 177 178 // BufferPool optionally specifies a buffer pool to 179 // get byte slices for use by io.CopyBuffer when 180 // copying HTTP response bodies. 181 BufferPool BufferPool 182 183 // ModifyResponse is an optional function that modifies the 184 // Response from the backend. It is called if the backend 185 // returns a response at all, with any HTTP status code. 186 // If the backend is unreachable, the optional ErrorHandler is 187 // called without any call to ModifyResponse. 188 // 189 // If ModifyResponse returns an error, ErrorHandler is called 190 // with its error value. If ErrorHandler is nil, its default 191 // implementation is used. 192 ModifyResponse func(*http.Response) error 193 194 // ErrorHandler is an optional function that handles errors 195 // reaching the backend or errors from ModifyResponse. 196 // 197 // If nil, the default is to log the provided error and return 198 // a 502 Status Bad Gateway response. 199 ErrorHandler func(http.ResponseWriter, *http.Request, error) 200 } 201 202 // A BufferPool is an interface for getting and returning temporary 203 // byte slices for use by [io.CopyBuffer]. 204 type BufferPool interface { 205 Get() []byte 206 Put([]byte) 207 } 208 209 func singleJoiningSlash(a, b string) string { 210 aslash := strings.HasSuffix(a, "/") 211 bslash := strings.HasPrefix(b, "/") 212 switch { 213 case aslash && bslash: 214 return a + b[1:] 215 case !aslash && !bslash: 216 return a + "/" + b 217 } 218 return a + b 219 } 220 221 func joinURLPath(a, b *url.URL) (path, rawpath string) { 222 if a.RawPath == "" && b.RawPath == "" { 223 return singleJoiningSlash(a.Path, b.Path), "" 224 } 225 // Same as singleJoiningSlash, but uses EscapedPath to determine 226 // whether a slash should be added 227 apath := a.EscapedPath() 228 bpath := b.EscapedPath() 229 230 aslash := strings.HasSuffix(apath, "/") 231 bslash := strings.HasPrefix(bpath, "/") 232 233 switch { 234 case aslash && bslash: 235 return a.Path + b.Path[1:], apath + bpath[1:] 236 case !aslash && !bslash: 237 return a.Path + "/" + b.Path, apath + "/" + bpath 238 } 239 return a.Path + b.Path, apath + bpath 240 } 241 242 // NewSingleHostReverseProxy returns a new [ReverseProxy] that routes 243 // URLs to the scheme, host, and base path provided in target. If the 244 // target's path is "/base" and the incoming request was for "/dir", 245 // the target request will be for /base/dir. 246 // 247 // NewSingleHostReverseProxy does not rewrite the Host header. 248 // 249 // To customize the ReverseProxy behavior beyond what 250 // NewSingleHostReverseProxy provides, use ReverseProxy directly 251 // with a Rewrite function. The ProxyRequest SetURL method 252 // may be used to route the outbound request. (Note that SetURL, 253 // unlike NewSingleHostReverseProxy, rewrites the Host header 254 // of the outbound request by default.) 255 // 256 // proxy := &ReverseProxy{ 257 // Rewrite: func(r *ProxyRequest) { 258 // r.SetURL(target) 259 // r.Out.Host = r.In.Host // if desired 260 // }, 261 // } 262 func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy { 263 director := func(req *http.Request) { 264 rewriteRequestURL(req, target) 265 } 266 return &ReverseProxy{Director: director} 267 } 268 269 func rewriteRequestURL(req *http.Request, target *url.URL) { 270 targetQuery := target.RawQuery 271 req.URL.Scheme = target.Scheme 272 req.URL.Host = target.Host 273 req.URL.Path, req.URL.RawPath = joinURLPath(target, req.URL) 274 if targetQuery == "" || req.URL.RawQuery == "" { 275 req.URL.RawQuery = targetQuery + req.URL.RawQuery 276 } else { 277 req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery 278 } 279 } 280 281 func copyHeader(dst, src http.Header) { 282 for k, vv := range src { 283 for _, v := range vv { 284 dst.Add(k, v) 285 } 286 } 287 } 288 289 // Hop-by-hop headers. These are removed when sent to the backend. 290 // As of RFC 7230, hop-by-hop headers are required to appear in the 291 // Connection header field. These are the headers defined by the 292 // obsoleted RFC 2616 (section 13.5.1) and are used for backward 293 // compatibility. 294 var hopHeaders = []string{ 295 "Connection", 296 "Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google 297 "Keep-Alive", 298 "Proxy-Authenticate", 299 "Proxy-Authorization", 300 "Te", // canonicalized version of "TE" 301 "Trailer", // not Trailers per URL above; https://www.rfc-editor.org/errata_search.php?eid=4522 302 "Transfer-Encoding", 303 "Upgrade", 304 } 305 306 func (p *ReverseProxy) defaultErrorHandler(rw http.ResponseWriter, req *http.Request, err error) { 307 p.logf("http: proxy error: %v", err) 308 rw.WriteHeader(http.StatusBadGateway) 309 } 310 311 func (p *ReverseProxy) getErrorHandler() func(http.ResponseWriter, *http.Request, error) { 312 if p.ErrorHandler != nil { 313 return p.ErrorHandler 314 } 315 return p.defaultErrorHandler 316 } 317 318 // modifyResponse conditionally runs the optional ModifyResponse hook 319 // and reports whether the request should proceed. 320 func (p *ReverseProxy) modifyResponse(rw http.ResponseWriter, res *http.Response, req *http.Request) bool { 321 if p.ModifyResponse == nil { 322 return true 323 } 324 if err := p.ModifyResponse(res); err != nil { 325 res.Body.Close() 326 p.getErrorHandler()(rw, req, err) 327 return false 328 } 329 return true 330 } 331 332 func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 333 transport := p.Transport 334 if transport == nil { 335 transport = http.DefaultTransport 336 } 337 338 ctx := req.Context() 339 if ctx.Done() != nil { 340 // CloseNotifier predates context.Context, and has been 341 // entirely superseded by it. If the request contains 342 // a Context that carries a cancellation signal, don't 343 // bother spinning up a goroutine to watch the CloseNotify 344 // channel (if any). 345 // 346 // If the request Context has a nil Done channel (which 347 // means it is either context.Background, or a custom 348 // Context implementation with no cancellation signal), 349 // then consult the CloseNotifier if available. 350 } else if cn, ok := rw.(http.CloseNotifier); ok { 351 var cancel context.CancelFunc 352 ctx, cancel = context.WithCancel(ctx) 353 defer cancel() 354 notifyChan := cn.CloseNotify() 355 go func() { 356 select { 357 case <-notifyChan: 358 cancel() 359 case <-ctx.Done(): 360 } 361 }() 362 } 363 364 outreq := req.Clone(ctx) 365 if req.ContentLength == 0 { 366 outreq.Body = nil // Issue 16036: nil Body for http.Transport retries 367 } 368 if outreq.Body != nil { 369 // Reading from the request body after returning from a handler is not 370 // allowed, and the RoundTrip goroutine that reads the Body can outlive 371 // this handler. This can lead to a crash if the handler panics (see 372 // Issue 46866). Although calling Close doesn't guarantee there isn't 373 // any Read in flight after the handle returns, in practice it's safe to 374 // read after closing it. 375 defer outreq.Body.Close() 376 } 377 if outreq.Header == nil { 378 outreq.Header = make(http.Header) // Issue 33142: historical behavior was to always allocate 379 } 380 381 if (p.Director != nil) == (p.Rewrite != nil) { 382 p.getErrorHandler()(rw, req, errors.New("ReverseProxy must have exactly one of Director or Rewrite set")) 383 return 384 } 385 386 if p.Director != nil { 387 p.Director(outreq) 388 if outreq.Form != nil { 389 outreq.URL.RawQuery = cleanQueryParams(outreq.URL.RawQuery) 390 } 391 } 392 outreq.Close = false 393 394 reqUpType := upgradeType(outreq.Header) 395 if !ascii.IsPrint(reqUpType) { 396 p.getErrorHandler()(rw, req, fmt.Errorf("client tried to switch to invalid protocol %q", reqUpType)) 397 return 398 } 399 removeHopByHopHeaders(outreq.Header) 400 401 // Issue 21096: tell backend applications that care about trailer support 402 // that we support trailers. (We do, but we don't go out of our way to 403 // advertise that unless the incoming client request thought it was worth 404 // mentioning.) Note that we look at req.Header, not outreq.Header, since 405 // the latter has passed through removeHopByHopHeaders. 406 if httpguts.HeaderValuesContainsToken(req.Header["Te"], "trailers") { 407 outreq.Header.Set("Te", "trailers") 408 } 409 410 // After stripping all the hop-by-hop connection headers above, add back any 411 // necessary for protocol upgrades, such as for websockets. 412 if reqUpType != "" { 413 outreq.Header.Set("Connection", "Upgrade") 414 outreq.Header.Set("Upgrade", reqUpType) 415 } 416 417 if p.Rewrite != nil { 418 // Strip client-provided forwarding headers. 419 // The Rewrite func may use SetXForwarded to set new values 420 // for these or copy the previous values from the inbound request. 421 outreq.Header.Del("Forwarded") 422 outreq.Header.Del("X-Forwarded-For") 423 outreq.Header.Del("X-Forwarded-Host") 424 outreq.Header.Del("X-Forwarded-Proto") 425 426 // Remove unparsable query parameters from the outbound request. 427 outreq.URL.RawQuery = cleanQueryParams(outreq.URL.RawQuery) 428 429 pr := &ProxyRequest{ 430 In: req, 431 Out: outreq, 432 } 433 p.Rewrite(pr) 434 outreq = pr.Out 435 } else { 436 if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { 437 // If we aren't the first proxy retain prior 438 // X-Forwarded-For information as a comma+space 439 // separated list and fold multiple headers into one. 440 prior, ok := outreq.Header["X-Forwarded-For"] 441 omit := ok && prior == nil // Issue 38079: nil now means don't populate the header 442 if len(prior) > 0 { 443 clientIP = strings.Join(prior, ", ") + ", " + clientIP 444 } 445 if !omit { 446 outreq.Header.Set("X-Forwarded-For", clientIP) 447 } 448 } 449 } 450 451 if _, ok := outreq.Header["User-Agent"]; !ok { 452 // If the outbound request doesn't have a User-Agent header set, 453 // don't send the default Go HTTP client User-Agent. 454 outreq.Header.Set("User-Agent", "") 455 } 456 457 var ( 458 roundTripMutex sync.Mutex 459 roundTripDone bool 460 ) 461 trace := &httptrace.ClientTrace{ 462 Got1xxResponse: func(code int, header textproto.MIMEHeader) error { 463 roundTripMutex.Lock() 464 defer roundTripMutex.Unlock() 465 if roundTripDone { 466 // If RoundTrip has returned, don't try to further modify 467 // the ResponseWriter's header map. 468 return nil 469 } 470 h := rw.Header() 471 copyHeader(h, http.Header(header)) 472 rw.WriteHeader(code) 473 474 // Clear headers, it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses 475 clear(h) 476 return nil 477 }, 478 } 479 outreq = outreq.WithContext(httptrace.WithClientTrace(outreq.Context(), trace)) 480 481 res, err := transport.RoundTrip(outreq) 482 roundTripMutex.Lock() 483 roundTripDone = true 484 roundTripMutex.Unlock() 485 if err != nil { 486 p.getErrorHandler()(rw, outreq, err) 487 return 488 } 489 490 // Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc) 491 if res.StatusCode == http.StatusSwitchingProtocols { 492 if !p.modifyResponse(rw, res, outreq) { 493 return 494 } 495 p.handleUpgradeResponse(rw, outreq, res) 496 return 497 } 498 499 removeHopByHopHeaders(res.Header) 500 501 if !p.modifyResponse(rw, res, outreq) { 502 return 503 } 504 505 copyHeader(rw.Header(), res.Header) 506 507 // The "Trailer" header isn't included in the Transport's response, 508 // at least for *http.Transport. Build it up from Trailer. 509 announcedTrailers := len(res.Trailer) 510 if announcedTrailers > 0 { 511 trailerKeys := make([]string, 0, len(res.Trailer)) 512 for k := range res.Trailer { 513 trailerKeys = append(trailerKeys, k) 514 } 515 rw.Header().Add("Trailer", strings.Join(trailerKeys, ", ")) 516 } 517 518 rw.WriteHeader(res.StatusCode) 519 520 err = p.copyResponse(rw, res.Body, p.flushInterval(res)) 521 if err != nil { 522 defer res.Body.Close() 523 // Since we're streaming the response, if we run into an error all we can do 524 // is abort the request. Issue 23643: ReverseProxy should use ErrAbortHandler 525 // on read error while copying body. 526 if !shouldPanicOnCopyError(req) { 527 p.logf("suppressing panic for copyResponse error in test; copy error: %v", err) 528 return 529 } 530 panic(http.ErrAbortHandler) 531 } 532 res.Body.Close() // close now, instead of defer, to populate res.Trailer 533 534 if len(res.Trailer) > 0 { 535 // Force chunking if we saw a response trailer. 536 // This prevents net/http from calculating the length for short 537 // bodies and adding a Content-Length. 538 http.NewResponseController(rw).Flush() 539 } 540 541 if len(res.Trailer) == announcedTrailers { 542 copyHeader(rw.Header(), res.Trailer) 543 return 544 } 545 546 for k, vv := range res.Trailer { 547 k = http.TrailerPrefix + k 548 for _, v := range vv { 549 rw.Header().Add(k, v) 550 } 551 } 552 } 553 554 var inOurTests bool // whether we're in our own tests 555 556 // shouldPanicOnCopyError reports whether the reverse proxy should 557 // panic with http.ErrAbortHandler. This is the right thing to do by 558 // default, but Go 1.10 and earlier did not, so existing unit tests 559 // weren't expecting panics. Only panic in our own tests, or when 560 // running under the HTTP server. 561 func shouldPanicOnCopyError(req *http.Request) bool { 562 if inOurTests { 563 // Our tests know to handle this panic. 564 return true 565 } 566 if req.Context().Value(http.ServerContextKey) != nil { 567 // We seem to be running under an HTTP server, so 568 // it'll recover the panic. 569 return true 570 } 571 // Otherwise act like Go 1.10 and earlier to not break 572 // existing tests. 573 return false 574 } 575 576 // removeHopByHopHeaders removes hop-by-hop headers. 577 func removeHopByHopHeaders(h http.Header) { 578 // RFC 7230, section 6.1: Remove headers listed in the "Connection" header. 579 for _, f := range h["Connection"] { 580 for _, sf := range strings.Split(f, ",") { 581 if sf = textproto.TrimString(sf); sf != "" { 582 h.Del(sf) 583 } 584 } 585 } 586 // RFC 2616, section 13.5.1: Remove a set of known hop-by-hop headers. 587 // This behavior is superseded by the RFC 7230 Connection header, but 588 // preserve it for backwards compatibility. 589 for _, f := range hopHeaders { 590 h.Del(f) 591 } 592 } 593 594 // flushInterval returns the p.FlushInterval value, conditionally 595 // overriding its value for a specific request/response. 596 func (p *ReverseProxy) flushInterval(res *http.Response) time.Duration { 597 resCT := res.Header.Get("Content-Type") 598 599 // For Server-Sent Events responses, flush immediately. 600 // The MIME type is defined in https://www.w3.org/TR/eventsource/#text-event-stream 601 if baseCT, _, _ := mime.ParseMediaType(resCT); baseCT == "text/event-stream" { 602 return -1 // negative means immediately 603 } 604 605 // We might have the case of streaming for which Content-Length might be unset. 606 if res.ContentLength == -1 { 607 return -1 608 } 609 610 return p.FlushInterval 611 } 612 613 func (p *ReverseProxy) copyResponse(dst http.ResponseWriter, src io.Reader, flushInterval time.Duration) error { 614 var w io.Writer = dst 615 616 if flushInterval != 0 { 617 mlw := &maxLatencyWriter{ 618 dst: dst, 619 flush: http.NewResponseController(dst).Flush, 620 latency: flushInterval, 621 } 622 defer mlw.stop() 623 624 // set up initial timer so headers get flushed even if body writes are delayed 625 mlw.flushPending = true 626 mlw.t = time.AfterFunc(flushInterval, mlw.delayedFlush) 627 628 w = mlw 629 } 630 631 var buf []byte 632 if p.BufferPool != nil { 633 buf = p.BufferPool.Get() 634 defer p.BufferPool.Put(buf) 635 } 636 _, err := p.copyBuffer(w, src, buf) 637 return err 638 } 639 640 // copyBuffer returns any write errors or non-EOF read errors, and the amount 641 // of bytes written. 642 func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) { 643 if len(buf) == 0 { 644 buf = make([]byte, 32*1024) 645 } 646 var written int64 647 for { 648 nr, rerr := src.Read(buf) 649 if rerr != nil && rerr != io.EOF && rerr != context.Canceled { 650 p.logf("httputil: ReverseProxy read error during body copy: %v", rerr) 651 } 652 if nr > 0 { 653 nw, werr := dst.Write(buf[:nr]) 654 if nw > 0 { 655 written += int64(nw) 656 } 657 if werr != nil { 658 return written, werr 659 } 660 if nr != nw { 661 return written, io.ErrShortWrite 662 } 663 } 664 if rerr != nil { 665 if rerr == io.EOF { 666 rerr = nil 667 } 668 return written, rerr 669 } 670 } 671 } 672 673 func (p *ReverseProxy) logf(format string, args ...any) { 674 if p.ErrorLog != nil { 675 p.ErrorLog.Printf(format, args...) 676 } else { 677 log.Printf(format, args...) 678 } 679 } 680 681 type maxLatencyWriter struct { 682 dst io.Writer 683 flush func() error 684 latency time.Duration // non-zero; negative means to flush immediately 685 686 mu sync.Mutex // protects t, flushPending, and dst.Flush 687 t *time.Timer 688 flushPending bool 689 } 690 691 func (m *maxLatencyWriter) Write(p []byte) (n int, err error) { 692 m.mu.Lock() 693 defer m.mu.Unlock() 694 n, err = m.dst.Write(p) 695 if m.latency < 0 { 696 m.flush() 697 return 698 } 699 if m.flushPending { 700 return 701 } 702 if m.t == nil { 703 m.t = time.AfterFunc(m.latency, m.delayedFlush) 704 } else { 705 m.t.Reset(m.latency) 706 } 707 m.flushPending = true 708 return 709 } 710 711 func (m *maxLatencyWriter) delayedFlush() { 712 m.mu.Lock() 713 defer m.mu.Unlock() 714 if !m.flushPending { // if stop was called but AfterFunc already started this goroutine 715 return 716 } 717 m.flush() 718 m.flushPending = false 719 } 720 721 func (m *maxLatencyWriter) stop() { 722 m.mu.Lock() 723 defer m.mu.Unlock() 724 m.flushPending = false 725 if m.t != nil { 726 m.t.Stop() 727 } 728 } 729 730 func upgradeType(h http.Header) string { 731 if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") { 732 return "" 733 } 734 return h.Get("Upgrade") 735 } 736 737 func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.Request, res *http.Response) { 738 reqUpType := upgradeType(req.Header) 739 resUpType := upgradeType(res.Header) 740 if !ascii.IsPrint(resUpType) { // We know reqUpType is ASCII, it's checked by the caller. 741 p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch to invalid protocol %q", resUpType)) 742 } 743 if !ascii.EqualFold(reqUpType, resUpType) { 744 p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType)) 745 return 746 } 747 748 backConn, ok := res.Body.(io.ReadWriteCloser) 749 if !ok { 750 p.getErrorHandler()(rw, req, fmt.Errorf("internal error: 101 switching protocols response with non-writable body")) 751 return 752 } 753 754 rc := http.NewResponseController(rw) 755 conn, brw, hijackErr := rc.Hijack() 756 if errors.Is(hijackErr, http.ErrNotSupported) { 757 p.getErrorHandler()(rw, req, fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", rw)) 758 return 759 } 760 761 backConnCloseCh := make(chan bool) 762 go func() { 763 // Ensure that the cancellation of a request closes the backend. 764 // See issue https://golang.org/issue/35559. 765 select { 766 case <-req.Context().Done(): 767 case <-backConnCloseCh: 768 } 769 backConn.Close() 770 }() 771 defer close(backConnCloseCh) 772 773 if hijackErr != nil { 774 p.getErrorHandler()(rw, req, fmt.Errorf("Hijack failed on protocol switch: %v", hijackErr)) 775 return 776 } 777 defer conn.Close() 778 779 copyHeader(rw.Header(), res.Header) 780 781 res.Header = rw.Header() 782 res.Body = nil // so res.Write only writes the headers; we have res.Body in backConn above 783 if err := res.Write(brw); err != nil { 784 p.getErrorHandler()(rw, req, fmt.Errorf("response write: %v", err)) 785 return 786 } 787 if err := brw.Flush(); err != nil { 788 p.getErrorHandler()(rw, req, fmt.Errorf("response flush: %v", err)) 789 return 790 } 791 errc := make(chan error, 1) 792 spc := switchProtocolCopier{user: conn, backend: backConn} 793 go spc.copyToBackend(errc) 794 go spc.copyFromBackend(errc) 795 <-errc 796 } 797 798 // switchProtocolCopier exists so goroutines proxying data back and 799 // forth have nice names in stacks. 800 type switchProtocolCopier struct { 801 user, backend io.ReadWriter 802 } 803 804 func (c switchProtocolCopier) copyFromBackend(errc chan<- error) { 805 _, err := io.Copy(c.user, c.backend) 806 errc <- err 807 } 808 809 func (c switchProtocolCopier) copyToBackend(errc chan<- error) { 810 _, err := io.Copy(c.backend, c.user) 811 errc <- err 812 } 813 814 func cleanQueryParams(s string) string { 815 reencode := func(s string) string { 816 v, _ := url.ParseQuery(s) 817 return v.Encode() 818 } 819 for i := 0; i < len(s); { 820 switch s[i] { 821 case ';': 822 return reencode(s) 823 case '%': 824 if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) { 825 return reencode(s) 826 } 827 i += 3 828 default: 829 i++ 830 } 831 } 832 return s 833 } 834 835 func ishex(c byte) bool { 836 switch { 837 case '0' <= c && c <= '9': 838 return true 839 case 'a' <= c && c <= 'f': 840 return true 841 case 'A' <= c && c <= 'F': 842 return true 843 } 844 return false 845 }