1
2
3
4
5
6
7 package httputil
8
9 import (
10 "context"
11 "errors"
12 "fmt"
13 "internal/godebug"
14 "io"
15 "log"
16 "mime"
17 "net"
18 "net/http"
19 "net/http/httptrace"
20 "net/http/internal/ascii"
21 "net/textproto"
22 "net/url"
23 "strings"
24 "sync"
25 "time"
26
27 "golang.org/x/net/http/httpguts"
28 )
29
30
31 type ProxyRequest struct {
32
33
34 In *http.Request
35
36
37
38
39
40 Out *http.Request
41 }
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57 func (r *ProxyRequest) SetURL(target *url.URL) {
58 rewriteRequestURL(r.Out, target)
59 r.Out.Host = ""
60 }
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81 func (r *ProxyRequest) SetXForwarded() {
82 clientIP, _, err := net.SplitHostPort(r.In.RemoteAddr)
83 if err == nil {
84 prior := r.Out.Header["X-Forwarded-For"]
85 if len(prior) > 0 {
86 clientIP = strings.Join(prior, ", ") + ", " + clientIP
87 }
88 r.Out.Header.Set("X-Forwarded-For", clientIP)
89 } else {
90 r.Out.Header.Del("X-Forwarded-For")
91 }
92 r.Out.Header.Set("X-Forwarded-Host", r.In.Host)
93 if r.In.TLS == nil {
94 r.Out.Header.Set("X-Forwarded-Proto", "http")
95 } else {
96 r.Out.Header.Set("X-Forwarded-Proto", "https")
97 }
98 }
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113 type ReverseProxy struct {
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135 Rewrite func(*ProxyRequest)
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165 Director func(*http.Request)
166
167
168
169 Transport http.RoundTripper
170
171
172
173
174
175
176
177
178
179
180
181 FlushInterval time.Duration
182
183
184
185
186 ErrorLog *log.Logger
187
188
189
190
191 BufferPool BufferPool
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206 ModifyResponse func(*http.Response) error
207
208
209
210
211
212
213 ErrorHandler func(http.ResponseWriter, *http.Request, error)
214 }
215
216
217
218 type BufferPool interface {
219 Get() []byte
220 Put([]byte)
221 }
222
223 func singleJoiningSlash(a, b string) string {
224 aslash := strings.HasSuffix(a, "/")
225 bslash := strings.HasPrefix(b, "/")
226 switch {
227 case aslash && bslash:
228 return a + b[1:]
229 case !aslash && !bslash:
230 return a + "/" + b
231 }
232 return a + b
233 }
234
235 func joinURLPath(a, b *url.URL) (path, rawpath string) {
236 if a.RawPath == "" && b.RawPath == "" {
237 return singleJoiningSlash(a.Path, b.Path), ""
238 }
239
240
241 apath := a.EscapedPath()
242 bpath := b.EscapedPath()
243
244 aslash := strings.HasSuffix(apath, "/")
245 bslash := strings.HasPrefix(bpath, "/")
246
247 switch {
248 case aslash && bslash:
249 return a.Path + b.Path[1:], apath + bpath[1:]
250 case !aslash && !bslash:
251 return a.Path + "/" + b.Path, apath + "/" + bpath
252 }
253 return a.Path + b.Path, apath + bpath
254 }
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276 func NewSingleHostReverseProxy(target *url.URL) *ReverseProxy {
277 director := func(req *http.Request) {
278 rewriteRequestURL(req, target)
279 }
280 return &ReverseProxy{Director: director}
281 }
282
283 func rewriteRequestURL(req *http.Request, target *url.URL) {
284 targetQuery := target.RawQuery
285 req.URL.Scheme = target.Scheme
286 req.URL.Host = target.Host
287 req.URL.Path, req.URL.RawPath = joinURLPath(target, req.URL)
288 if targetQuery == "" || req.URL.RawQuery == "" {
289 req.URL.RawQuery = targetQuery + req.URL.RawQuery
290 } else {
291 req.URL.RawQuery = targetQuery + "&" + req.URL.RawQuery
292 }
293 }
294
295 func copyHeader(dst, src http.Header) {
296 for k, vv := range src {
297 for _, v := range vv {
298 dst.Add(k, v)
299 }
300 }
301 }
302
303
304
305
306
307
308 var hopHeaders = []string{
309 "Connection",
310 "Proxy-Connection",
311 "Keep-Alive",
312 "Proxy-Authenticate",
313 "Proxy-Authorization",
314 "Te",
315 "Trailer",
316 "Transfer-Encoding",
317 "Upgrade",
318 }
319
320 func (p *ReverseProxy) defaultErrorHandler(rw http.ResponseWriter, req *http.Request, err error) {
321 p.logf("http: proxy error: %v", err)
322 rw.WriteHeader(http.StatusBadGateway)
323 }
324
325 func (p *ReverseProxy) getErrorHandler() func(http.ResponseWriter, *http.Request, error) {
326 if p.ErrorHandler != nil {
327 return p.ErrorHandler
328 }
329 return p.defaultErrorHandler
330 }
331
332
333
334 func (p *ReverseProxy) modifyResponse(rw http.ResponseWriter, res *http.Response, req *http.Request) bool {
335 if p.ModifyResponse == nil {
336 return true
337 }
338 if err := p.ModifyResponse(res); err != nil {
339 res.Body.Close()
340 p.getErrorHandler()(rw, req, err)
341 return false
342 }
343 return true
344 }
345
346 func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
347 transport := p.Transport
348 if transport == nil {
349 transport = http.DefaultTransport
350 }
351
352 ctx := req.Context()
353 if ctx.Done() != nil {
354
355
356
357
358
359
360
361
362
363
364 } else if cn, ok := rw.(http.CloseNotifier); ok {
365 var cancel context.CancelFunc
366 ctx, cancel = context.WithCancel(ctx)
367 defer cancel()
368 notifyChan := cn.CloseNotify()
369 go func() {
370 select {
371 case <-notifyChan:
372 cancel()
373 case <-ctx.Done():
374 }
375 }()
376 }
377
378 outreq := req.Clone(ctx)
379 if req.ContentLength == 0 {
380 outreq.Body = nil
381 }
382 if outreq.Body != nil {
383
384
385
386
387
388
389 defer outreq.Body.Close()
390 }
391 if outreq.Header == nil {
392 outreq.Header = make(http.Header)
393 }
394
395 if (p.Director != nil) == (p.Rewrite != nil) {
396 p.getErrorHandler()(rw, req, errors.New("ReverseProxy must have exactly one of Director or Rewrite set"))
397 return
398 }
399
400 if p.Director != nil {
401 p.Director(outreq)
402 if outreq.Form != nil {
403 outreq.URL.RawQuery = cleanQueryParams(outreq.URL.RawQuery)
404 }
405 }
406 outreq.Close = false
407
408 reqUpType := upgradeType(outreq.Header)
409 if !ascii.IsPrint(reqUpType) {
410 p.getErrorHandler()(rw, req, fmt.Errorf("client tried to switch to invalid protocol %q", reqUpType))
411 return
412 }
413 removeHopByHopHeaders(outreq.Header)
414
415
416
417
418
419
420 if httpguts.HeaderValuesContainsToken(req.Header["Te"], "trailers") {
421 outreq.Header.Set("Te", "trailers")
422 }
423
424
425
426 if reqUpType != "" {
427 outreq.Header.Set("Connection", "Upgrade")
428 outreq.Header.Set("Upgrade", reqUpType)
429 }
430
431 if p.Rewrite != nil {
432
433
434
435 outreq.Header.Del("Forwarded")
436 outreq.Header.Del("X-Forwarded-For")
437 outreq.Header.Del("X-Forwarded-Host")
438 outreq.Header.Del("X-Forwarded-Proto")
439
440
441 outreq.URL.RawQuery = cleanQueryParams(outreq.URL.RawQuery)
442
443 pr := &ProxyRequest{
444 In: req,
445 Out: outreq,
446 }
447 p.Rewrite(pr)
448 outreq = pr.Out
449 } else {
450 if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
451
452
453
454 prior, ok := outreq.Header["X-Forwarded-For"]
455 omit := ok && prior == nil
456 if len(prior) > 0 {
457 clientIP = strings.Join(prior, ", ") + ", " + clientIP
458 }
459 if !omit {
460 outreq.Header.Set("X-Forwarded-For", clientIP)
461 }
462 }
463 }
464
465 if _, ok := outreq.Header["User-Agent"]; !ok {
466
467
468 outreq.Header.Set("User-Agent", "")
469 }
470
471 var (
472 roundTripMutex sync.Mutex
473 roundTripDone bool
474 )
475 trace := &httptrace.ClientTrace{
476 Got1xxResponse: func(code int, header textproto.MIMEHeader) error {
477 roundTripMutex.Lock()
478 defer roundTripMutex.Unlock()
479 if roundTripDone {
480
481
482 return nil
483 }
484 h := rw.Header()
485 copyHeader(h, http.Header(header))
486 rw.WriteHeader(code)
487
488
489 clear(h)
490 return nil
491 },
492 }
493 outreq = outreq.WithContext(httptrace.WithClientTrace(outreq.Context(), trace))
494
495 res, err := transport.RoundTrip(outreq)
496 roundTripMutex.Lock()
497 roundTripDone = true
498 roundTripMutex.Unlock()
499 if err != nil {
500 p.getErrorHandler()(rw, outreq, err)
501 return
502 }
503
504
505 if res.StatusCode == http.StatusSwitchingProtocols {
506 if !p.modifyResponse(rw, res, outreq) {
507 return
508 }
509 p.handleUpgradeResponse(rw, outreq, res)
510 return
511 }
512
513 removeHopByHopHeaders(res.Header)
514
515 if !p.modifyResponse(rw, res, outreq) {
516 return
517 }
518
519 copyHeader(rw.Header(), res.Header)
520
521
522
523 announcedTrailers := len(res.Trailer)
524 if announcedTrailers > 0 {
525 trailerKeys := make([]string, 0, len(res.Trailer))
526 for k := range res.Trailer {
527 trailerKeys = append(trailerKeys, k)
528 }
529 rw.Header().Add("Trailer", strings.Join(trailerKeys, ", "))
530 }
531
532 rw.WriteHeader(res.StatusCode)
533
534 err = p.copyResponse(rw, res.Body, p.flushInterval(res))
535 if err != nil {
536 defer res.Body.Close()
537
538
539
540 if !shouldPanicOnCopyError(req) {
541 p.logf("suppressing panic for copyResponse error in test; copy error: %v", err)
542 return
543 }
544 panic(http.ErrAbortHandler)
545 }
546 res.Body.Close()
547
548 if len(res.Trailer) > 0 {
549
550
551
552 http.NewResponseController(rw).Flush()
553 }
554
555 if len(res.Trailer) == announcedTrailers {
556 copyHeader(rw.Header(), res.Trailer)
557 return
558 }
559
560 for k, vv := range res.Trailer {
561 k = http.TrailerPrefix + k
562 for _, v := range vv {
563 rw.Header().Add(k, v)
564 }
565 }
566 }
567
568 var inOurTests bool
569
570
571
572
573
574
575 func shouldPanicOnCopyError(req *http.Request) bool {
576 if inOurTests {
577
578 return true
579 }
580 if req.Context().Value(http.ServerContextKey) != nil {
581
582
583 return true
584 }
585
586
587 return false
588 }
589
590
591 func removeHopByHopHeaders(h http.Header) {
592
593 for _, f := range h["Connection"] {
594 for sf := range strings.SplitSeq(f, ",") {
595 if sf = textproto.TrimString(sf); sf != "" {
596 h.Del(sf)
597 }
598 }
599 }
600
601
602
603 for _, f := range hopHeaders {
604 h.Del(f)
605 }
606 }
607
608
609
610 func (p *ReverseProxy) flushInterval(res *http.Response) time.Duration {
611 resCT := res.Header.Get("Content-Type")
612
613
614
615 if baseCT, _, _ := mime.ParseMediaType(resCT); baseCT == "text/event-stream" {
616 return -1
617 }
618
619
620 if res.ContentLength == -1 {
621 return -1
622 }
623
624 return p.FlushInterval
625 }
626
627 func (p *ReverseProxy) copyResponse(dst http.ResponseWriter, src io.Reader, flushInterval time.Duration) error {
628 var w io.Writer = dst
629
630 if flushInterval != 0 {
631 mlw := &maxLatencyWriter{
632 dst: dst,
633 flush: http.NewResponseController(dst).Flush,
634 latency: flushInterval,
635 }
636 defer mlw.stop()
637
638
639 mlw.flushPending = true
640 mlw.t = time.AfterFunc(flushInterval, mlw.delayedFlush)
641
642 w = mlw
643 }
644
645 var buf []byte
646 if p.BufferPool != nil {
647 buf = p.BufferPool.Get()
648 defer p.BufferPool.Put(buf)
649 }
650 _, err := p.copyBuffer(w, src, buf)
651 return err
652 }
653
654
655
656 func (p *ReverseProxy) copyBuffer(dst io.Writer, src io.Reader, buf []byte) (int64, error) {
657 if len(buf) == 0 {
658 buf = make([]byte, 32*1024)
659 }
660 var written int64
661 for {
662 nr, rerr := src.Read(buf)
663 if rerr != nil && rerr != io.EOF && rerr != context.Canceled {
664 p.logf("httputil: ReverseProxy read error during body copy: %v", rerr)
665 }
666 if nr > 0 {
667 nw, werr := dst.Write(buf[:nr])
668 if nw > 0 {
669 written += int64(nw)
670 }
671 if werr != nil {
672 return written, werr
673 }
674 if nr != nw {
675 return written, io.ErrShortWrite
676 }
677 }
678 if rerr != nil {
679 if rerr == io.EOF {
680 rerr = nil
681 }
682 return written, rerr
683 }
684 }
685 }
686
687 func (p *ReverseProxy) logf(format string, args ...any) {
688 if p.ErrorLog != nil {
689 p.ErrorLog.Printf(format, args...)
690 } else {
691 log.Printf(format, args...)
692 }
693 }
694
695 type maxLatencyWriter struct {
696 dst io.Writer
697 flush func() error
698 latency time.Duration
699
700 mu sync.Mutex
701 t *time.Timer
702 flushPending bool
703 }
704
705 func (m *maxLatencyWriter) Write(p []byte) (n int, err error) {
706 m.mu.Lock()
707 defer m.mu.Unlock()
708 n, err = m.dst.Write(p)
709 if m.latency < 0 {
710 m.flush()
711 return
712 }
713 if m.flushPending {
714 return
715 }
716 if m.t == nil {
717 m.t = time.AfterFunc(m.latency, m.delayedFlush)
718 } else {
719 m.t.Reset(m.latency)
720 }
721 m.flushPending = true
722 return
723 }
724
725 func (m *maxLatencyWriter) delayedFlush() {
726 m.mu.Lock()
727 defer m.mu.Unlock()
728 if !m.flushPending {
729 return
730 }
731 m.flush()
732 m.flushPending = false
733 }
734
735 func (m *maxLatencyWriter) stop() {
736 m.mu.Lock()
737 defer m.mu.Unlock()
738 m.flushPending = false
739 if m.t != nil {
740 m.t.Stop()
741 }
742 }
743
744 func upgradeType(h http.Header) string {
745 if !httpguts.HeaderValuesContainsToken(h["Connection"], "Upgrade") {
746 return ""
747 }
748 return h.Get("Upgrade")
749 }
750
751 func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.Request, res *http.Response) {
752 reqUpType := upgradeType(req.Header)
753 resUpType := upgradeType(res.Header)
754 if !ascii.IsPrint(resUpType) {
755 p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch to invalid protocol %q", resUpType))
756 return
757 }
758 if !ascii.EqualFold(reqUpType, resUpType) {
759 p.getErrorHandler()(rw, req, fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType))
760 return
761 }
762
763 backConn, ok := res.Body.(io.ReadWriteCloser)
764 if !ok {
765 p.getErrorHandler()(rw, req, fmt.Errorf("internal error: 101 switching protocols response with non-writable body"))
766 return
767 }
768
769 rc := http.NewResponseController(rw)
770 conn, brw, hijackErr := rc.Hijack()
771 if errors.Is(hijackErr, http.ErrNotSupported) {
772 p.getErrorHandler()(rw, req, fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", rw))
773 return
774 }
775
776 backConnCloseCh := make(chan bool)
777 go func() {
778
779
780 select {
781 case <-req.Context().Done():
782 case <-backConnCloseCh:
783 }
784 backConn.Close()
785 }()
786 defer close(backConnCloseCh)
787
788 if hijackErr != nil {
789 p.getErrorHandler()(rw, req, fmt.Errorf("Hijack failed on protocol switch: %v", hijackErr))
790 return
791 }
792 defer conn.Close()
793
794 copyHeader(rw.Header(), res.Header)
795
796 res.Header = rw.Header()
797 res.Body = nil
798 if err := res.Write(brw); err != nil {
799 p.getErrorHandler()(rw, req, fmt.Errorf("response write: %v", err))
800 return
801 }
802 if err := brw.Flush(); err != nil {
803 p.getErrorHandler()(rw, req, fmt.Errorf("response flush: %v", err))
804 return
805 }
806 errc := make(chan error, 1)
807 spc := switchProtocolCopier{user: conn, backend: backConn}
808 go spc.copyToBackend(errc)
809 go spc.copyFromBackend(errc)
810
811
812
813 err := <-errc
814 if err == nil {
815 err = <-errc
816 }
817 }
818
819 var errCopyDone = errors.New("hijacked connection copy complete")
820
821
822
823 type switchProtocolCopier struct {
824 user, backend io.ReadWriter
825 }
826
827 func (c switchProtocolCopier) copyFromBackend(errc chan<- error) {
828 if _, err := io.Copy(c.user, c.backend); err != nil {
829 errc <- err
830 return
831 }
832
833
834 if wc, ok := c.user.(interface{ CloseWrite() error }); ok {
835 errc <- wc.CloseWrite()
836 return
837 }
838
839 errc <- errCopyDone
840 }
841
842 func (c switchProtocolCopier) copyToBackend(errc chan<- error) {
843 if _, err := io.Copy(c.backend, c.user); err != nil {
844 errc <- err
845 return
846 }
847
848
849 if wc, ok := c.backend.(interface{ CloseWrite() error }); ok {
850 errc <- wc.CloseWrite()
851 return
852 }
853
854 errc <- errCopyDone
855 }
856
857 var urlmaxqueryparams = godebug.New("urlmaxqueryparams")
858
859
860 const defaultMaxParams = 10000
861
862 func cleanQueryParams(s string) string {
863 reencode := func(s string) string {
864 v, _ := url.ParseQuery(s)
865 return v.Encode()
866 }
867 if urlmaxqueryparams.Value() != "" {
868
869 return reencode(s)
870 }
871 if numParams := strings.Count(s, "&") + 1; numParams > defaultMaxParams {
872
873 return reencode(s)
874 }
875 for i := 0; i < len(s); {
876 switch s[i] {
877 case ';':
878 return reencode(s)
879 case '%':
880 if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) {
881 return reencode(s)
882 }
883 i += 3
884 default:
885 i++
886 }
887 }
888 return s
889 }
890
891 func ishex(c byte) bool {
892 switch {
893 case '0' <= c && c <= '9':
894 return true
895 case 'a' <= c && c <= 'f':
896 return true
897 case 'A' <= c && c <= 'F':
898 return true
899 }
900 return false
901 }
902
View as plain text