gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/gmhttp/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 "fmt" 12 "io" 13 "log" 14 "net" 15 "net/textproto" 16 "net/url" 17 "strings" 18 "sync" 19 "time" 20 21 http "gitee.com/ks-custle/core-gm/gmhttp" 22 "gitee.com/ks-custle/core-gm/gmhttp/internal/ascii" 23 24 "golang.org/x/net/http/httpguts" 25 ) 26 27 // ReverseProxy is an HTTP Handler that takes an incoming request and 28 // sends it to another server, proxying the response back to the 29 // client. 30 // 31 // ReverseProxy by default sets the client IP as the value of the 32 // X-Forwarded-For header. 33 // 34 // If an X-Forwarded-For header already exists, the client IP is 35 // appended to the existing values. As a special case, if the header 36 // exists in the Request.Header map but has a nil value (such as when 37 // set by the Director func), the X-Forwarded-For header is 38 // not modified. 39 // 40 // To prevent IP spoofing, be sure to delete any pre-existing 41 // X-Forwarded-For header coming from the client or 42 // an untrusted proxy. 43 type ReverseProxy struct { 44 // Director must be a function which modifies 45 // the request into a new request to be sent 46 // using Transport. Its response is then copied 47 // back to the original client unmodified. 48 // Director must not access the provided Request 49 // after returning. 50 Director func(*http.Request) 51 52 // The transport used to perform proxy requests. 53 // If nil, http.DefaultTransport is used. 54 Transport http.RoundTripper 55 56 // FlushInterval specifies the flush interval 57 // to flush to the client while copying the 58 // response body. 59 // If zero, no periodic flushing is done. 60 // A negative value means to flush immediately 61 // after each write to the client. 62 // The FlushInterval is ignored when ReverseProxy 63 // recognizes a response as a streaming response, or 64 // if its ContentLength is -1; for such responses, writes 65 // are flushed to the client immediately. 66 FlushInterval time.Duration 67 68 // ErrorLog specifies an optional logger for errors 69 // that occur when attempting to proxy the request. 70 // If nil, logging is done via the log package's standard logger. 71 ErrorLog *log.Logger 72 73 // BufferPool optionally specifies a buffer pool to 74 // get byte slices for use by io.CopyBuffer when 75 // copying HTTP response bodies. 76 BufferPool BufferPool 77 78 // ModifyResponse is an optional function that modifies the 79 // Response from the backend. It is called if the backend 80 // returns a response at all, with any HTTP status code. 81 // If the backend is unreachable, the optional ErrorHandler is 82 // called without any call to ModifyResponse. 83 // 84 // If ModifyResponse returns an error, ErrorHandler is called 85 // with its error value. If ErrorHandler is nil, its default 86 // implementation is used. 87 ModifyResponse func(*http.Response) error 88 89 // ErrorHandler is an optional function that handles errors 90 // reaching the backend or errors from ModifyResponse. 91 // 92 // If nil, the default is to log the provided error and return 93 // a 502 Status Bad Gateway response. 94 ErrorHandler func(http.ResponseWriter, *http.Request, error) 95 } 96 97 // A BufferPool is an interface for getting and returning temporary 98 // byte slices for use by io.CopyBuffer. 99 type BufferPool interface { 100 Get() []byte 101 Put([]byte) 102 } 103 104 func singleJoiningSlash(a, b string) string { 105 aslash := strings.HasSuffix(a, "/") 106 bslash := strings.HasPrefix(b, "/") 107 switch { 108 case aslash && bslash: 109 return a + b[1:] 110 case !aslash && !bslash: 111 return a + "/" + b 112 } 113 return a + b 114 } 115 116 func joinURLPath(a, b *url.URL) (path, rawpath string) { 117 if a.RawPath == "" && b.RawPath == "" { 118 return singleJoiningSlash(a.Path, b.Path), "" 119 } 120 // Same as singleJoiningSlash, but uses EscapedPath to determine 121 // whether a slash should be added 122 apath := a.EscapedPath() 123 bpath := b.EscapedPath() 124 125 aslash := strings.HasSuffix(apath, "/") 126 bslash := strings.HasPrefix(bpath, "/") 127 128 switch { 129 case aslash && bslash: 130 return a.Path + b.Path[1:], apath + bpath[1:] 131 case !aslash && !bslash: 132 return a.Path + "/" + b.Path, apath + "/" + bpath 133 } 134 return a.Path + b.Path, apath + bpath 135 } 136 137 // NewSingleHostReverseProxy returns a new ReverseProxy that routes 138 // URLs to the scheme, host, and base path provided in target. If the 139 // target's path is "/base" and the incoming request was for "/dir", 140 // the target request will be for /base/dir. 141 // NewSingleHostReverseProxy does not rewrite the Host header. 142 // To rewrite Host headers, use ReverseProxy directly with a custom 143 // Director policy. 144 func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy { 145 targetQuery := target.RawQuery 146 director := func(req *http.Request) { 147 req.URL.Scheme = target.Scheme 148 req.URL.Host = target.Host 149 req.URL.Path, req.URL.RawPath = joinURLPath(target, req.URL) 150 if targetQuery == "" || req.URL.RawQuery == "" { 151 req.URL.RawQuery = targetQuery + req.URL.RawQuery 152 } else { 153 req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery 154 } 155 if _, ok := req.Header["User-Agent"]; !ok { 156 // explicitly disable User-Agent so it's not set to default value 157 req.Header.Set("User-Agent", "") 158 } 159 } 160 return &ReverseProxy{Director: director} 161 } 162 163 func copyHeader(dst, src http.Header) { 164 for k, vv := range src { 165 for _, v := range vv { 166 dst.Add(k, v) 167 } 168 } 169 } 170 171 // Hop-by-hop headers. These are removed when sent to the backend. 172 // As of RFC 7230, hop-by-hop headers are required to appear in the 173 // Connection header field. These are the headers defined by the 174 // obsoleted RFC 2616 (section 13.5.1) and are used for backward 175 // compatibility. 176 var hopHeaders = []string{ 177 "Connection", 178 "Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google 179 "Keep-Alive", 180 "Proxy-Authenticate", 181 "Proxy-Authorization", 182 "Te", // canonicalized version of "TE" 183 "Trailer", // not Trailers per URL above; https://www.rfc-editor.org/errata_search.php?eid=4522 184 "Transfer-Encoding", 185 "Upgrade", 186 } 187 188 //goland:noinspection GoUnusedParameter 189 func (p *ReverseProxy) defaultErrorHandler(rw http.ResponseWriter, req *http.Request, err error) { 190 p.logf("http: proxy error: %v", err) 191 rw.WriteHeader(http.StatusBadGateway) 192 } 193 194 func (p *ReverseProxy) getErrorHandler() func(http.ResponseWriter, *http.Request, error) { 195 if p.ErrorHandler != nil { 196 return p.ErrorHandler 197 } 198 return p.defaultErrorHandler 199 } 200 201 // modifyResponse conditionally runs the optional ModifyResponse hook 202 // and reports whether the request should proceed. 203 func (p *ReverseProxy) modifyResponse(rw http.ResponseWriter, res *http.Response, req *http.Request) bool { 204 if p.ModifyResponse == nil { 205 return true 206 } 207 if err := p.ModifyResponse(res); err != nil { 208 _ = res.Body.Close() 209 p.getErrorHandler()(rw, req, err) 210 return false 211 } 212 return true 213 } 214 215 func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 216 transport := p.Transport 217 if transport == nil { 218 transport = http.DefaultTransport 219 } 220 221 ctx := req.Context() 222 if cn, ok := rw.(http.CloseNotifier); ok { 223 var cancel context.CancelFunc 224 ctx, cancel = context.WithCancel(ctx) 225 defer cancel() 226 notifyChan := cn.CloseNotify() 227 go func() { 228 select { 229 case <-notifyChan: 230 cancel() 231 case <-ctx.Done(): 232 } 233 }() 234 } 235 236 outreq := req.Clone(ctx) 237 if req.ContentLength == 0 { 238 outreq.Body = nil // Issue 16036: nil Body for http.Transport retries 239 } 240 if outreq.Body != nil { 241 // Reading from the request body after returning from a handler is not 242 // allowed, and the RoundTrip goroutine that reads the Body can outlive 243 // this handler. This can lead to a crash if the handler panics (see 244 // Issue 46866). Although calling Close doesn't guarantee there isn't 245 // any Read in flight after the handle returns, in practice it's safe to 246 // read after closing it. 247 defer func(Body io.ReadCloser) { 248 _ = Body.Close() 249 }(outreq.Body) 250 } 251 if outreq.Header == nil { 252 outreq.Header = make(http.Header) // Issue 33142: historical behavior was to always allocate 253 } 254 255 p.Director(outreq) 256 outreq.Close = false 257 258 reqUpType := upgradeType(outreq.Header) 259 if !ascii.IsPrint(reqUpType) { 260 p.getErrorHandler()(rw, req, fmt.Errorf("client tried to switch to invalid protocol %q", reqUpType)) 261 return 262 } 263 removeConnectionHeaders(outreq.Header) 264 265 // Remove hop-by-hop headers to the backend. Especially 266 // important is "Connection" because we want a persistent 267 // connection, regardless of what the client sent to us. 268 for _, h := range hopHeaders { 269 outreq.Header.Del(h) 270 } 271 272 // Issue 21096: tell backend applications that care about trailer support 273 // that we support trailers. (We do, but we don't go out of our way to 274 // advertise that unless the incoming client request thought it was worth 275 // mentioning.) Note that we look at req.Header, not outreq.Header, since 276 // the latter has passed through removeConnectionHeaders. 277 if httpguts.HeaderValuesContainsToken(req.Header["Te"], "trailers") { 278 outreq.Header.Set("Te", "trailers") 279 } 280 281 // After stripping all the hop-by-hop connection headers above, add back any 282 // necessary for protocol upgrades, such as for websockets. 283 if reqUpType != "" { 284 outreq.Header.Set("Connection", "Upgrade") 285 outreq.Header.Set("Upgrade", reqUpType) 286 } 287 288 if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil { 289 // If we aren't the first proxy retain prior 290 // X-Forwarded-For information as a comma+space 291 // separated list and fold multiple headers into one. 292 prior, ok := outreq.Header["X-Forwarded-For"] 293 omit := ok && prior == nil // Issue 38079: nil now means don't populate the header 294 if len(prior) > 0 { 295 clientIP = strings.Join(prior, ", ") + ", " + clientIP 296 } 297 if !omit { 298 outreq.Header.Set("X-Forwarded-For", clientIP) 299 } 300 } 301 302 res, err := transport.RoundTrip(outreq) 303 if err != nil { 304 p.getErrorHandler()(rw, outreq, err) 305 return 306 } 307 308 // Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc) 309 if res.StatusCode == http.StatusSwitchingProtocols { 310 if !p.modifyResponse(rw, res, outreq) { 311 return 312 } 313 p.handleUpgradeResponse(rw, outreq, res) 314 return 315 } 316 317 removeConnectionHeaders(res.Header) 318 319 for _, h := range hopHeaders { 320 res.Header.Del(h) 321 } 322 323 if !p.modifyResponse(rw, res, outreq) { 324 return 325 } 326 327 copyHeader(rw.Header(), res.Header) 328 329 // The "Trailer" header isn't included in the Transport's response, 330 // at least for *http.Transport. Build it up from Trailer. 331 announcedTrailers := len(res.Trailer) 332 if announcedTrailers > 0 { 333 trailerKeys := make([]string, 0, len(res.Trailer)) 334 for k := range res.Trailer { 335 trailerKeys = append(trailerKeys, k) 336 } 337 rw.Header().Add("Trailer", strings.Join(trailerKeys, ", ")) 338 } 339 340 rw.WriteHeader(res.StatusCode) 341 342 err = p.copyResponse(rw, res.Body, p.flushInterval(res)) 343 if err != nil { 344 defer func(Body io.ReadCloser) { 345 _ = Body.Close() 346 }(res.Body) 347 // Since we're streaming the response, if we run into an error all we can do 348 // is abort the request. Issue 23643: ReverseProxy should use ErrAbortHandler 349 // on read error while copying body. 350 if !shouldPanicOnCopyError(req) { 351 p.logf("suppressing panic for copyResponse error in test; copy error: %v", err) 352 return 353 } 354 panic(http.ErrAbortHandler) 355 } 356 _ = res.Body.Close() // close now, instead of defer, to populate res.Trailer 357 358 if len(res.Trailer) > 0 { 359 // Force chunking if we saw a response trailer. 360 // This prevents net/http from calculating the length for short 361 // bodies and adding a Content-Length. 362 if fl, ok := rw.(http.Flusher); ok { 363 fl.Flush() 364 } 365 } 366 367 if len(res.Trailer) == announcedTrailers { 368 copyHeader(rw.Header(), res.Trailer) 369 return 370 } 371 372 for k, vv := range res.Trailer { 373 k = http.TrailerPrefix + k 374 for _, v := range vv { 375 rw.Header().Add(k, v) 376 } 377 } 378 } 379 380 var inOurTests bool // whether we're in our own tests 381 382 // shouldPanicOnCopyError reports whether the reverse proxy should 383 // panic with http.ErrAbortHandler. This is the right thing to do by 384 // default, but Go 1.10 and earlier did not, so existing unit tests 385 // weren't expecting panics. Only panic in our own tests, or when 386 // running under the HTTP server. 387 func shouldPanicOnCopyError(req *http.Request) bool { 388 if inOurTests { 389 // Our tests know to handle this panic. 390 return true 391 } 392 if req.Context().Value(http.ServerContextKey) != nil { 393 // We seem to be running under an HTTP server, so 394 // it'll recover the panic. 395 return true 396 } 397 // Otherwise act like Go 1.10 and earlier to not break 398 // existing tests. 399 return false 400 } 401 402 // removeConnectionHeaders removes hop-by-hop headers listed in the "Connection" header of h. 403 // See RFC 7230, section 6.1 404 func removeConnectionHeaders(h http.Header) { 405 for _, f := range h["Connection"] { 406 for _, sf := range strings.Split(f, ",") { 407 if sf = textproto.TrimString(sf); sf != "" { 408 h.Del(sf) 409 } 410 } 411 } 412 } 413 414 // flushInterval returns the p.FlushInterval value, conditionally 415 // overriding its value for a specific request/response. 416 func (p *ReverseProxy) flushInterval(res *http.Response) time.Duration { 417 resCT := res.Header.Get("Content-Type") 418 419 // For Server-Sent Events responses, flush immediately. 420 // The MIME type is defined in https://www.w3.org/TR/eventsource/#text-event-stream 421 if resCT == "text/event-stream" { 422 return -1 // negative means immediately 423 } 424 425 // We might have the case of streaming for which Content-Length might be unset. 426 if res.ContentLength == -1 { 427 return -1 428 } 429 430 return p.FlushInterval 431 } 432 433 func (p *ReverseProxy) copyResponse(dst io.Writer, src io.Reader, flushInterval time.Duration) error { 434 if flushInterval != 0 { 435 if wf, ok := dst.(writeFlusher); ok { 436 mlw := &maxLatencyWriter{ 437 dst: wf, 438 latency: flushInterval, 439 } 440 defer mlw.stop() 441 442 // set up initial timer so headers get flushed even if body writes are delayed 443 mlw.flushPending = true 444 mlw.t = time.AfterFunc(flushInterval, mlw.delayedFlush) 445 446 dst = mlw 447 } 448 } 449 450 var buf []byte 451 if p.BufferPool != nil { 452 buf = p.BufferPool.Get() 453 defer p.BufferPool.Put(buf) 454 } 455 _, err := p.copyBuffer(dst, src, buf) 456 return err 457 } 458 459 // copyBuffer returns any write errors or non-EOF read errors, and the amount 460 // of bytes written. 461 func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) { 462 if len(buf) == 0 { 463 buf = make([]byte, 32*1024) 464 } 465 var written int64 466 for { 467 nr, rerr := src.Read(buf) 468 if rerr != nil && rerr != io.EOF && rerr != context.Canceled { 469 p.logf("httputil: ReverseProxy read error during body copy: %v", rerr) 470 } 471 if nr > 0 { 472 nw, werr := dst.Write(buf[:nr]) 473 if nw > 0 { 474 written += int64(nw) 475 } 476 if werr != nil { 477 return written, werr 478 } 479 if nr != nw { 480 return written, io.ErrShortWrite 481 } 482 } 483 if rerr != nil { 484 if rerr == io.EOF { 485 rerr = nil 486 } 487 return written, rerr 488 } 489 } 490 } 491 492 func (p *ReverseProxy) logf(format string, args ...interface{}) { 493 if p.ErrorLog != nil { 494 p.ErrorLog.Printf(format, args...) 495 } else { 496 log.Printf(format, args...) 497 } 498 } 499 500 type writeFlusher interface { 501 io.Writer 502 http.Flusher 503 } 504 505 type maxLatencyWriter struct { 506 dst writeFlusher 507 latency time.Duration // non-zero; negative means to flush immediately 508 509 mu sync.Mutex // protects t, flushPending, and dst.Flush 510 t *time.Timer 511 flushPending bool 512 } 513 514 func (m *maxLatencyWriter) Write(p []byte) (n int, err error) { 515 m.mu.Lock() 516 defer m.mu.Unlock() 517 n, err = m.dst.Write(p) 518 if m.latency < 0 { 519 m.dst.Flush() 520 return 521 } 522 if m.flushPending { 523 return 524 } 525 if m.t == nil { 526 m.t = time.AfterFunc(m.latency, m.delayedFlush) 527 } else { 528 m.t.Reset(m.latency) 529 } 530 m.flushPending = true 531 return 532 } 533 534 func (m *maxLatencyWriter) delayedFlush() { 535 m.mu.Lock() 536 defer m.mu.Unlock() 537 if !m.flushPending { // if stop was called but AfterFunc already started this goroutine 538 return 539 } 540 m.dst.Flush() 541 m.flushPending = false 542 } 543 544 func (m *maxLatencyWriter) stop() { 545 m.mu.Lock() 546 defer m.mu.Unlock() 547 m.flushPending = false 548 if m.t != nil { 549 m.t.Stop() 550 } 551 } 552 553 func upgradeType(h http.Header) string { 554 if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") { 555 return "" 556 } 557 return h.Get("Upgrade") 558 } 559 560 func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.Request, res *http.Response) { 561 reqUpType := upgradeType(req.Header) 562 resUpType := upgradeType(res.Header) 563 if !ascii.IsPrint(resUpType) { // We know reqUpType is ASCII, it's checked by the caller. 564 p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch to invalid protocol %q", resUpType)) 565 } 566 if !ascii.EqualFold(reqUpType, resUpType) { 567 p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType)) 568 return 569 } 570 571 hj, ok := rw.(http.Hijacker) 572 if !ok { 573 p.getErrorHandler()(rw, req, fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", rw)) 574 return 575 } 576 backConn, ok := res.Body.(io.ReadWriteCloser) 577 if !ok { 578 p.getErrorHandler()(rw, req, fmt.Errorf("internal error: 101 switching protocols response with non-writable body")) 579 return 580 } 581 582 backConnCloseCh := make(chan bool) 583 go func() { 584 // Ensure that the cancellation of a request closes the backend. 585 // See issue https://golang.org/issue/35559. 586 select { 587 case <-req.Context().Done(): 588 case <-backConnCloseCh: 589 } 590 _ = backConn.Close() 591 }() 592 593 defer close(backConnCloseCh) 594 595 conn, brw, err := hj.Hijack() 596 if err != nil { 597 //goland:noinspection GoErrorStringFormat 598 p.getErrorHandler()(rw, req, fmt.Errorf("Hijack failed on protocol switch: %v", err)) 599 return 600 } 601 defer func(conn net.Conn) { 602 _ = conn.Close() 603 }(conn) 604 605 copyHeader(rw.Header(), res.Header) 606 607 res.Header = rw.Header() 608 res.Body = nil // so res.Write only writes the headers; we have res.Body in backConn above 609 if err := res.Write(brw); err != nil { 610 p.getErrorHandler()(rw, req, fmt.Errorf("response write: %v", err)) 611 return 612 } 613 if err := brw.Flush(); err != nil { 614 p.getErrorHandler()(rw, req, fmt.Errorf("response flush: %v", err)) 615 return 616 } 617 errc := make(chan error, 1) 618 spc := switchProtocolCopier{user: conn, backend: backConn} 619 go spc.copyToBackend(errc) 620 go spc.copyFromBackend(errc) 621 <-errc 622 return 623 } 624 625 // switchProtocolCopier exists so goroutines proxying data back and 626 // forth have nice names in stacks. 627 type switchProtocolCopier struct { 628 user, backend io.ReadWriter 629 } 630 631 func (c switchProtocolCopier) copyFromBackend(errc chan<- error) { 632 _, err := io.Copy(c.user, c.backend) 633 errc <- err 634 } 635 636 func (c switchProtocolCopier) copyToBackend(errc chan<- error) { 637 _, err := io.Copy(c.backend, c.user) 638 errc <- err 639 }