github.com/Kolosok86/http@v0.1.2/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/url" 18 "strings" 19 "sync" 20 "time" 21 22 "github.com/Kolosok86/http" 23 "github.com/Kolosok86/http/httptrace" 24 "github.com/Kolosok86/http/internal/ascii" 25 "github.com/Kolosok86/http/textproto" 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 trace := &httptrace.ClientTrace{ 458 Got1xxResponse: func(code int, header textproto.MIMEHeader) error { 459 h := rw.Header() 460 copyHeader(h, http.Header(header)) 461 rw.WriteHeader(code) 462 463 // Clear headers, it's not automatically done by ResponseWriter.WriteHeader() for 1xx responses 464 for k := range h { 465 delete(h, k) 466 } 467 468 return nil 469 }, 470 } 471 outreq = outreq.WithContext(httptrace.WithClientTrace(outreq.Context(), trace)) 472 473 res, err := transport.RoundTrip(outreq) 474 if err != nil { 475 p.getErrorHandler()(rw, outreq, err) 476 return 477 } 478 479 // Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc) 480 if res.StatusCode == http.StatusSwitchingProtocols { 481 if !p.modifyResponse(rw, res, outreq) { 482 return 483 } 484 p.handleUpgradeResponse(rw, outreq, res) 485 return 486 } 487 488 removeHopByHopHeaders(res.Header) 489 490 if !p.modifyResponse(rw, res, outreq) { 491 return 492 } 493 494 copyHeader(rw.Header(), res.Header) 495 496 // The "Trailer" header isn't included in the Transport's response, 497 // at least for *http.Transport. Build it up from Trailer. 498 announcedTrailers := len(res.Trailer) 499 if announcedTrailers > 0 { 500 trailerKeys := make([]string, 0, len(res.Trailer)) 501 for k := range res.Trailer { 502 trailerKeys = append(trailerKeys, k) 503 } 504 rw.Header().Add("Trailer", strings.Join(trailerKeys, ", ")) 505 } 506 507 rw.WriteHeader(res.StatusCode) 508 509 err = p.copyResponse(rw, res.Body, p.flushInterval(res)) 510 if err != nil { 511 defer res.Body.Close() 512 // Since we're streaming the response, if we run into an error all we can do 513 // is abort the request. Issue 23643: ReverseProxy should use ErrAbortHandler 514 // on read error while copying body. 515 if !shouldPanicOnCopyError(req) { 516 p.logf("suppressing panic for copyResponse error in test; copy error: %v", err) 517 return 518 } 519 panic(http.ErrAbortHandler) 520 } 521 res.Body.Close() // close now, instead of defer, to populate res.Trailer 522 523 if len(res.Trailer) > 0 { 524 // Force chunking if we saw a response trailer. 525 // This prevents net/http from calculating the length for short 526 // bodies and adding a Content-Length. 527 if fl, ok := rw.(http.Flusher); ok { 528 fl.Flush() 529 } 530 } 531 532 if len(res.Trailer) == announcedTrailers { 533 copyHeader(rw.Header(), res.Trailer) 534 return 535 } 536 537 for k, vv := range res.Trailer { 538 k = http.TrailerPrefix + k 539 for _, v := range vv { 540 rw.Header().Add(k, v) 541 } 542 } 543 } 544 545 var inOurTests bool // whether we're in our own tests 546 547 // shouldPanicOnCopyError reports whether the reverse proxy should 548 // panic with http.ErrAbortHandler. This is the right thing to do by 549 // default, but Go 1.10 and earlier did not, so existing unit tests 550 // weren't expecting panics. Only panic in our own tests, or when 551 // running under the HTTP server. 552 func shouldPanicOnCopyError(req *http.Request) bool { 553 if inOurTests { 554 // Our tests know to handle this panic. 555 return true 556 } 557 if req.Context().Value(http.ServerContextKey) != nil { 558 // We seem to be running under an HTTP server, so 559 // it'll recover the panic. 560 return true 561 } 562 // Otherwise act like Go 1.10 and earlier to not break 563 // existing tests. 564 return false 565 } 566 567 // removeHopByHopHeaders removes hop-by-hop headers. 568 func removeHopByHopHeaders(h http.Header) { 569 // RFC 7230, section 6.1: Remove headers listed in the "Connection" header. 570 for _, f := range h["Connection"] { 571 for _, sf := range strings.Split(f, ",") { 572 if sf = textproto.TrimString(sf); sf != "" { 573 h.Del(sf) 574 } 575 } 576 } 577 // RFC 2616, section 13.5.1: Remove a set of known hop-by-hop headers. 578 // This behavior is superseded by the RFC 7230 Connection header, but 579 // preserve it for backwards compatibility. 580 for _, f := range hopHeaders { 581 h.Del(f) 582 } 583 } 584 585 // flushInterval returns the p.FlushInterval value, conditionally 586 // overriding its value for a specific request/response. 587 func (p *ReverseProxy) flushInterval(res *http.Response) time.Duration { 588 resCT := res.Header.Get("Content-Type") 589 590 // For Server-Sent Events responses, flush immediately. 591 // The MIME type is defined in https://www.w3.org/TR/eventsource/#text-event-stream 592 if baseCT, _, _ := mime.ParseMediaType(resCT); baseCT == "text/event-stream" { 593 return -1 // negative means immediately 594 } 595 596 // We might have the case of streaming for which Content-Length might be unset. 597 if res.ContentLength == -1 { 598 return -1 599 } 600 601 return p.FlushInterval 602 } 603 604 func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader, flushInterval time.Duration) error { 605 if flushInterval != 0 { 606 if wf, ok := dst.(writeFlusher); ok { 607 mlw := &maxLatencyWriter{ 608 dst: wf, 609 latency: flushInterval, 610 } 611 defer mlw.stop() 612 613 // set up initial timer so headers get flushed even if body writes are delayed 614 mlw.flushPending = true 615 mlw.t = time.AfterFunc(flushInterval, mlw.delayedFlush) 616 617 dst = mlw 618 } 619 } 620 621 var buf []byte 622 if p.BufferPool != nil { 623 buf = p.BufferPool.Get() 624 defer p.BufferPool.Put(buf) 625 } 626 _, err := p.copyBuffer(dst, src, buf) 627 return err 628 } 629 630 // copyBuffer returns any write errors or non-EOF read errors, and the amount 631 // of bytes written. 632 func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) { 633 if len(buf) == 0 { 634 buf = make([]byte, 32*1024) 635 } 636 var written int64 637 for { 638 nr, rerr := src.Read(buf) 639 if rerr != nil && rerr != io.EOF && rerr != context.Canceled { 640 p.logf("httputil: ReverseProxy read error during body copy: %v", rerr) 641 } 642 if nr > 0 { 643 nw, werr := dst.Write(buf[:nr]) 644 if nw > 0 { 645 written += int64(nw) 646 } 647 if werr != nil { 648 return written, werr 649 } 650 if nr != nw { 651 return written, io.ErrShortWrite 652 } 653 } 654 if rerr != nil { 655 if rerr == io.EOF { 656 rerr = nil 657 } 658 return written, rerr 659 } 660 } 661 } 662 663 func (p *ReverseProxy) logf(format string, args ...any) { 664 if p.ErrorLog != nil { 665 p.ErrorLog.Printf(format, args...) 666 } else { 667 log.Printf(format, args...) 668 } 669 } 670 671 type writeFlusher interface { 672 io.Writer 673 http.Flusher 674 } 675 676 type maxLatencyWriter struct { 677 dst writeFlusher 678 latency time.Duration // non-zero; negative means to flush immediately 679 680 mu sync.Mutex // protects t, flushPending, and dst.Flush 681 t *time.Timer 682 flushPending bool 683 } 684 685 func (m *maxLatencyWriter) Write(p []byte) (n int, err error) { 686 m.mu.Lock() 687 defer m.mu.Unlock() 688 n, err = m.dst.Write(p) 689 if m.latency < 0 { 690 m.dst.Flush() 691 return 692 } 693 if m.flushPending { 694 return 695 } 696 if m.t == nil { 697 m.t = time.AfterFunc(m.latency, m.delayedFlush) 698 } else { 699 m.t.Reset(m.latency) 700 } 701 m.flushPending = true 702 return 703 } 704 705 func (m *maxLatencyWriter) delayedFlush() { 706 m.mu.Lock() 707 defer m.mu.Unlock() 708 if !m.flushPending { // if stop was called but AfterFunc already started this goroutine 709 return 710 } 711 m.dst.Flush() 712 m.flushPending = false 713 } 714 715 func (m *maxLatencyWriter) stop() { 716 m.mu.Lock() 717 defer m.mu.Unlock() 718 m.flushPending = false 719 if m.t != nil { 720 m.t.Stop() 721 } 722 } 723 724 func upgradeType(h http.Header) string { 725 if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") { 726 return "" 727 } 728 return h.Get("Upgrade") 729 } 730 731 func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.Request, res *http.Response) { 732 reqUpType := upgradeType(req.Header) 733 resUpType := upgradeType(res.Header) 734 if !ascii.IsPrint(resUpType) { // We know reqUpType is ASCII, it's checked by the caller. 735 p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch to invalid protocol %q", resUpType)) 736 } 737 if !ascii.EqualFold(reqUpType, resUpType) { 738 p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType)) 739 return 740 } 741 742 hj, ok := rw.(http.Hijacker) 743 if !ok { 744 p.getErrorHandler()(rw, req, fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", rw)) 745 return 746 } 747 backConn, ok := res.Body.(io.ReadWriteCloser) 748 if !ok { 749 p.getErrorHandler()(rw, req, fmt.Errorf("internal error: 101 switching protocols response with non-writable body")) 750 return 751 } 752 753 backConnCloseCh := make(chan bool) 754 go func() { 755 // Ensure that the cancellation of a request closes the backend. 756 // See issue https://golang.org/issue/35559. 757 select { 758 case <-req.Context().Done(): 759 case <-backConnCloseCh: 760 } 761 backConn.Close() 762 }() 763 764 defer close(backConnCloseCh) 765 766 conn, brw, err := hj.Hijack() 767 if err != nil { 768 p.getErrorHandler()(rw, req, fmt.Errorf("Hijack failed on protocol switch: %v", err)) 769 return 770 } 771 defer conn.Close() 772 773 copyHeader(rw.Header(), res.Header) 774 775 res.Header = rw.Header() 776 res.Body = nil // so res.Write only writes the headers; we have res.Body in backConn above 777 if err := res.Write(brw); err != nil { 778 p.getErrorHandler()(rw, req, fmt.Errorf("response write: %v", err)) 779 return 780 } 781 if err := brw.Flush(); err != nil { 782 p.getErrorHandler()(rw, req, fmt.Errorf("response flush: %v", err)) 783 return 784 } 785 errc := make(chan error, 1) 786 spc := switchProtocolCopier{user: conn, backend: backConn} 787 go spc.copyToBackend(errc) 788 go spc.copyFromBackend(errc) 789 <-errc 790 } 791 792 // switchProtocolCopier exists so goroutines proxying data back and 793 // forth have nice names in stacks. 794 type switchProtocolCopier struct { 795 user, backend io.ReadWriter 796 } 797 798 func (c switchProtocolCopier) copyFromBackend(errc chan<- error) { 799 _, err := io.Copy(c.user, c.backend) 800 errc <- err 801 } 802 803 func (c switchProtocolCopier) copyToBackend(errc chan<- error) { 804 _, err := io.Copy(c.backend, c.user) 805 errc <- err 806 } 807 808 func cleanQueryParams(s string) string { 809 reencode := func(s string) string { 810 v, _ := url.ParseQuery(s) 811 return v.Encode() 812 } 813 for i := 0; i < len(s); { 814 switch s[i] { 815 case ';': 816 return reencode(s) 817 case '%': 818 if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) { 819 return reencode(s) 820 } 821 i += 3 822 default: 823 i++ 824 } 825 } 826 return s 827 } 828 829 func ishex(c byte) bool { 830 switch { 831 case '0' <= c && c <= '9': 832 return true 833 case 'a' <= c && c <= 'f': 834 return true 835 case 'A' <= c && c <= 'F': 836 return true 837 } 838 return false 839 }