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