@@ -8,6 +8,7 @@ package httputil
8
8
9
9
import (
10
10
"context"
11
+ "errors"
11
12
"fmt"
12
13
"io"
13
14
"log"
@@ -24,33 +25,118 @@ import (
24
25
"golang.org/x/net/http/httpguts"
25
26
)
26
27
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.
28
+ // A ProxyRequest contains a request to be rewritten by a ReverseProxy.
29
+ type ProxyRequest struct {
30
+ // In is the request received by the proxy.
31
+ // The Rewrite function must not modify In.
32
+ In * http.Request
33
+
34
+ // Out is the request which will be sent by the proxy.
35
+ // The Rewrite function may modify or replace this request.
36
+ // Hop-by-hop headers are removed from this request
37
+ // before Rewrite is called.
38
+ Out * http.Request
39
+ }
40
+
41
+ // SetURL routes the outbound request to the scheme, host, and base path
42
+ // provided in target. If the target's path is "/base" and the incoming
43
+ // request was for "/dir", the target request will be for "/base/dir".
30
44
//
31
- // ReverseProxy by default sets
32
- // - the X-Forwarded-For header to the client IP address;
33
- // - the X-Forwarded-Host header to the host of the original client
34
- // request; and
35
- // - the X-Forwarded-Proto header to "https" if the client request
36
- // was made on a TLS-enabled connection or "http" otherwise.
45
+ // SetURL rewrites the outbound Host header to match the target's host.
46
+ // To preserve the inbound request's Host header (the default behavior
47
+ // of NewSingleHostReverseProxy):
37
48
//
38
- // If an X-Forwarded-For header already exists, the client IP is
39
- // appended to the existing values.
49
+ // rewriteFunc := func(r *httputil.ProxyRequest) {
50
+ // r.SetURL(url)
51
+ // r.Out.Host = r.In.Host
52
+ // }
53
+ func (r * ProxyRequest ) SetURL (target * url.URL ) {
54
+ rewriteRequestURL (r .Out , target )
55
+ r .Out .Host = ""
56
+ }
57
+
58
+ // SetXForwarded sets the X-Forwarded-For, X-Forwarded-Host, and
59
+ // X-Forwarded-Proto headers of the outbound request.
40
60
//
41
- // If a header exists in the Request.Header map but has a nil value
42
- // (such as when set by the Director func), it is not modified.
61
+ // - The X-Forwarded-For header is set to the client IP address.
62
+ // - The X-Forwarded-Host header is set to the host name requested
63
+ // by the client.
64
+ // - The X-Forwarded-Proto header is set to "http" or "https", depending
65
+ // on whether the inbound request was made on a TLS-enabled connection.
43
66
//
44
- // To prevent IP spoofing, be sure to delete any pre-existing
45
- // X-Forwarded-For header coming from the client or
46
- // an untrusted proxy.
67
+ // If the outbound request contains an existing X-Forwarded-For header,
68
+ // SetXForwarded appends the client IP address to it. To append to the
69
+ // inbound request's X-Forwarded-For header (the default behavior of
70
+ // ReverseProxy when using a Director function), copy the header
71
+ // from the inbound request before calling SetXForwarded:
72
+ //
73
+ // rewriteFunc := func(r *httputil.ProxyRequest) {
74
+ // r.Out.Header["X-Forwarded-For"] = r.In.Header["X-Forwarded-For"]
75
+ // r.SetXForwarded()
76
+ // }
77
+ func (r * ProxyRequest ) SetXForwarded () {
78
+ clientIP , _ , err := net .SplitHostPort (r .In .RemoteAddr )
79
+ if err == nil {
80
+ prior := r .Out .Header ["X-Forwarded-For" ]
81
+ if len (prior ) > 0 {
82
+ clientIP = strings .Join (prior , ", " ) + ", " + clientIP
83
+ }
84
+ r .Out .Header .Set ("X-Forwarded-For" , clientIP )
85
+ } else {
86
+ r .Out .Header .Del ("X-Forwarded-For" )
87
+ }
88
+ r .Out .Header .Set ("X-Forwarded-Host" , r .In .Host )
89
+ if r .In .TLS == nil {
90
+ r .Out .Header .Set ("X-Forwarded-Proto" , "http" )
91
+ } else {
92
+ r .Out .Header .Set ("X-Forwarded-Proto" , "https" )
93
+ }
94
+ }
95
+
96
+ // ReverseProxy is an HTTP Handler that takes an incoming request and
97
+ // sends it to another server, proxying the response back to the
98
+ // client.
47
99
type ReverseProxy struct {
48
- // Director must be a function which modifies
100
+ // Rewrite must be a function which modifies
101
+ // the request into a new request to be sent
102
+ // using Transport. Its response is then copied
103
+ // back to the original client unmodified.
104
+ // Rewrite must not access the provided ProxyRequest
105
+ // or its contents after returning.
106
+ //
107
+ // The Forwarded, X-Forwarded, X-Forwarded-Host,
108
+ // and X-Forwarded-Proto headers are removed from the
109
+ // outbound request before Rewrite is called. See also
110
+ // the ProxyRequest.SetXForwarded method.
111
+ //
112
+ // At most one of Rewrite or Director may be set.
113
+ Rewrite func (* ProxyRequest )
114
+
115
+ // Director is a function which modifies the
49
116
// the request into a new request to be sent
50
117
// using Transport. Its response is then copied
51
118
// back to the original client unmodified.
52
119
// Director must not access the provided Request
53
120
// after returning.
121
+ //
122
+ // By default, the X-Forwarded-For, X-Forwarded-Host, and
123
+ // X-Forwarded-Proto headers of the ourgoing request are
124
+ // set as by the ProxyRequest.SetXForwarded function.
125
+ //
126
+ // If an X-Forwarded-For header already exists, the client IP is
127
+ // appended to the existing values. To prevent IP spoofing, be
128
+ // sure to delete any pre-existing X-Forwarded-For header
129
+ // coming from the client or an untrusted proxy.
130
+ //
131
+ // If a header exists in the Request.Header map but has a nil value
132
+ // (such as when set by the Director func), it is not modified.
133
+ //
134
+ // Hop-by-hop headers are removed from the request after
135
+ // Director returns, which can remove headers added by
136
+ // Director. Use a Rewrite function instead to ensure
137
+ // modifications to the request are preserved.
138
+ //
139
+ // At most one of Rewrite or Director may be set.
54
140
Director func (* http.Request )
55
141
56
142
// The transport used to perform proxy requests.
@@ -142,24 +228,41 @@ func joinURLPath(a, b *url.URL) (path, rawpath string) {
142
228
// URLs to the scheme, host, and base path provided in target. If the
143
229
// target's path is "/base" and the incoming request was for "/dir",
144
230
// the target request will be for /base/dir.
231
+ //
145
232
// NewSingleHostReverseProxy does not rewrite the Host header.
146
- // To rewrite Host headers, use ReverseProxy directly with a custom
147
- // Director policy.
233
+ //
234
+ // To customize the ReverseProxy behavior beyond what
235
+ // NewSingleHostReverseProxy provides, use ReverseProxy directly
236
+ // with a Rewrite function. The ProxyRequest SetURL method
237
+ // may be used to route the outbound request. (Note that SetURL,
238
+ // unlike NewSingleHostReverseProxy, rewrites the Host header
239
+ // of the outbound request by default.)
240
+ //
241
+ // proxy := &ReverseProxy{
242
+ // Rewrite: func(r *ProxyRequest) {
243
+ // r.SetURL(target)
244
+ // r.Out.Host = r.In.Host // if desired
245
+ // }
246
+ // }
148
247
func NewSingleHostReverseProxy (target * url.URL ) * ReverseProxy {
149
- targetQuery := target .RawQuery
150
248
director := func (req * http.Request ) {
151
- req .URL .Scheme = target .Scheme
152
- req .URL .Host = target .Host
153
- req .URL .Path , req .URL .RawPath = joinURLPath (target , req .URL )
154
- if targetQuery == "" || req .URL .RawQuery == "" {
155
- req .URL .RawQuery = targetQuery + req .URL .RawQuery
156
- } else {
157
- req .URL .RawQuery = targetQuery + "&" + req .URL .RawQuery
158
- }
249
+ rewriteRequestURL (req , target )
159
250
}
160
251
return & ReverseProxy {Director : director }
161
252
}
162
253
254
+ func rewriteRequestURL (req * http.Request , target * url.URL ) {
255
+ targetQuery := target .RawQuery
256
+ req .URL .Scheme = target .Scheme
257
+ req .URL .Host = target .Host
258
+ req .URL .Path , req .URL .RawPath = joinURLPath (target , req .URL )
259
+ if targetQuery == "" || req .URL .RawQuery == "" {
260
+ req .URL .RawQuery = targetQuery + req .URL .RawQuery
261
+ } else {
262
+ req .URL .RawQuery = targetQuery + "&" + req .URL .RawQuery
263
+ }
264
+ }
265
+
163
266
func copyHeader (dst , src http.Header ) {
164
267
for k , vv := range src {
165
268
for _ , v := range vv {
@@ -260,28 +363,28 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
260
363
outreq .Header = make (http.Header ) // Issue 33142: historical behavior was to always allocate
261
364
}
262
365
263
- p .Director (outreq )
366
+ if (p .Director != nil ) == (p .Rewrite != nil ) {
367
+ p .getErrorHandler ()(rw , req , errors .New ("ReverseProxy must have exactly one of Director or Rewrite set" ))
368
+ return
369
+ }
370
+
371
+ if p .Director != nil {
372
+ p .Director (outreq )
373
+ }
264
374
outreq .Close = false
265
375
266
376
reqUpType := upgradeType (outreq .Header )
267
377
if ! ascii .IsPrint (reqUpType ) {
268
378
p .getErrorHandler ()(rw , req , fmt .Errorf ("client tried to switch to invalid protocol %q" , reqUpType ))
269
379
return
270
380
}
271
- removeConnectionHeaders (outreq .Header )
272
-
273
- // Remove hop-by-hop headers to the backend. Especially
274
- // important is "Connection" because we want a persistent
275
- // connection, regardless of what the client sent to us.
276
- for _ , h := range hopHeaders {
277
- outreq .Header .Del (h )
278
- }
381
+ removeHopByHopHeaders (outreq .Header )
279
382
280
383
// Issue 21096: tell backend applications that care about trailer support
281
384
// that we support trailers. (We do, but we don't go out of our way to
282
385
// advertise that unless the incoming client request thought it was worth
283
386
// mentioning.) Note that we look at req.Header, not outreq.Header, since
284
- // the latter has passed through removeConnectionHeaders .
387
+ // the latter has passed through removeHopByHopHeaders .
285
388
if httpguts .HeaderValuesContainsToken (req .Header ["Te" ], "trailers" ) {
286
389
outreq .Header .Set ("Te" , "trailers" )
287
390
}
@@ -293,27 +396,44 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
293
396
outreq .Header .Set ("Upgrade" , reqUpType )
294
397
}
295
398
296
- if clientIP , _ , err := net .SplitHostPort (req .RemoteAddr ); err == nil {
297
- // If we aren't the first proxy retain prior
298
- // X-Forwarded-For information as a comma+space
299
- // separated list and fold multiple headers into one.
300
- prior , ok := outreq .Header ["X-Forwarded-For" ]
301
- omit := ok && prior == nil // Issue 38079: nil now means don't populate the header
302
- if len (prior ) > 0 {
303
- clientIP = strings .Join (prior , ", " ) + ", " + clientIP
399
+ if p .Rewrite != nil {
400
+ // Strip client-provided forwarding headers.
401
+ // The Rewrite func may use SetXForwarded to set new values
402
+ // for these or copy the previous values from the inbound request.
403
+ outreq .Header .Del ("Forwarded" )
404
+ outreq .Header .Del ("X-Forwarded-For" )
405
+ outreq .Header .Del ("X-Forwarded-Host" )
406
+ outreq .Header .Del ("X-Forwarded-Proto" )
407
+
408
+ pr := & ProxyRequest {
409
+ In : req ,
410
+ Out : outreq ,
304
411
}
305
- if ! omit {
306
- outreq .Header .Set ("X-Forwarded-For" , clientIP )
412
+ p .Rewrite (pr )
413
+ outreq = pr .Out
414
+ } else {
415
+ if clientIP , _ , err := net .SplitHostPort (req .RemoteAddr ); err == nil {
416
+ // If we aren't the first proxy retain prior
417
+ // X-Forwarded-For information as a comma+space
418
+ // separated list and fold multiple headers into one.
419
+ prior , ok := outreq .Header ["X-Forwarded-For" ]
420
+ omit := ok && prior == nil // Issue 38079: nil now means don't populate the header
421
+ if len (prior ) > 0 {
422
+ clientIP = strings .Join (prior , ", " ) + ", " + clientIP
423
+ }
424
+ if ! omit {
425
+ outreq .Header .Set ("X-Forwarded-For" , clientIP )
426
+ }
307
427
}
308
- }
309
- if prior , ok := outreq .Header [ "X-Forwarded-Host" ]; ! ( ok && prior == nil ) {
310
- outreq . Header . Set ( "X-Forwarded-Host" , req . Host )
311
- }
312
- if prior , ok := outreq . Header [ "X-Forwarded-Proto" ]; ! ( ok && prior == nil ) {
313
- if req . TLS == nil {
314
- outreq . Header . Set ( "X-Forwarded-Proto" , "http" )
315
- } else {
316
- outreq . Header . Set ( "X-Forwarded-Proto" , "https" )
428
+ if prior , ok := outreq . Header [ "X-Forwarded-Host" ]; ! ( ok && prior == nil ) {
429
+ outreq .Header . Set ( "X-Forwarded-Host" , req . Host )
430
+ }
431
+ if prior , ok := outreq . Header [ "X-Forwarded-Proto" ]; ! ( ok && prior == nil ) {
432
+ if req . TLS == nil {
433
+ outreq . Header . Set ( "X-Forwarded-Proto" , "http" )
434
+ } else {
435
+ outreq . Header . Set ( "X-Forwarded-Proto" , "https" )
436
+ }
317
437
}
318
438
}
319
439
@@ -323,6 +443,12 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
323
443
outreq .Header .Set ("User-Agent" , "" )
324
444
}
325
445
446
+ if _ , ok := outreq .Header ["User-Agent" ]; ! ok {
447
+ // If the outbound request doesn't have a User-Agent header set,
448
+ // don't send the default Go HTTP client User-Agent.
449
+ outreq .Header .Set ("User-Agent" , "" )
450
+ }
451
+
326
452
res , err := transport .RoundTrip (outreq )
327
453
if err != nil {
328
454
p .getErrorHandler ()(rw , outreq , err )
@@ -338,11 +464,7 @@ func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
338
464
return
339
465
}
340
466
341
- removeConnectionHeaders (res .Header )
342
-
343
- for _ , h := range hopHeaders {
344
- res .Header .Del (h )
345
- }
467
+ removeHopByHopHeaders (res .Header )
346
468
347
469
if ! p .modifyResponse (rw , res , outreq ) {
348
470
return
@@ -421,16 +543,22 @@ func shouldPanicOnCopyError(req *http.Request) bool {
421
543
return false
422
544
}
423
545
424
- // removeConnectionHeaders removes hop-by-hop headers listed in the "Connection" header of h .
425
- // See RFC 7230, section 6.1
426
- func removeConnectionHeaders ( h http. Header ) {
546
+ // removeHopByHopHeaders removes hop-by-hop headers.
547
+ func removeHopByHopHeaders ( h http. Header ) {
548
+ // RFC 7230, section 6.1: Remove headers listed in the "Connection" header.
427
549
for _ , f := range h ["Connection" ] {
428
550
for _ , sf := range strings .Split (f , "," ) {
429
551
if sf = textproto .TrimString (sf ); sf != "" {
430
552
h .Del (sf )
431
553
}
432
554
}
433
555
}
556
+ // RFC 2616, section 13.5.1: Remove a set of known hop-by-hop headers.
557
+ // This behavior is superseded by the RFC 7230 Connection header, but
558
+ // preserve it for backwards compatibility.
559
+ for _ , f := range hopHeaders {
560
+ h .Del (f )
561
+ }
434
562
}
435
563
436
564
// flushInterval returns the p.FlushInterval value, conditionally
0 commit comments