github.com/mh-cbon/go@v0.0.0-20160603070303-9e112a3fe4c0/src/net/http/httputil/reverseproxy_test.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 // Reverse proxy tests. 6 7 package httputil 8 9 import ( 10 "bufio" 11 "bytes" 12 "io" 13 "io/ioutil" 14 "log" 15 "net/http" 16 "net/http/httptest" 17 "net/url" 18 "reflect" 19 "strconv" 20 "strings" 21 "sync" 22 "testing" 23 "time" 24 ) 25 26 const fakeHopHeader = "X-Fake-Hop-Header-For-Test" 27 28 func init() { 29 hopHeaders = append(hopHeaders, fakeHopHeader) 30 } 31 32 func TestReverseProxy(t *testing.T) { 33 const backendResponse = "I am the backend" 34 const backendStatus = 404 35 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 36 if r.Method == "GET" && r.FormValue("mode") == "hangup" { 37 c, _, _ := w.(http.Hijacker).Hijack() 38 c.Close() 39 return 40 } 41 if len(r.TransferEncoding) > 0 { 42 t.Errorf("backend got unexpected TransferEncoding: %v", r.TransferEncoding) 43 } 44 if r.Header.Get("X-Forwarded-For") == "" { 45 t.Errorf("didn't get X-Forwarded-For header") 46 } 47 if c := r.Header.Get("Connection"); c != "" { 48 t.Errorf("handler got Connection header value %q", c) 49 } 50 if c := r.Header.Get("Upgrade"); c != "" { 51 t.Errorf("handler got Upgrade header value %q", c) 52 } 53 if c := r.Header.Get("Proxy-Connection"); c != "" { 54 t.Errorf("handler got Proxy-Connection header value %q", c) 55 } 56 if g, e := r.Host, "some-name"; g != e { 57 t.Errorf("backend got Host header %q, want %q", g, e) 58 } 59 w.Header().Set("Trailers", "not a special header field name") 60 w.Header().Set("Trailer", "X-Trailer") 61 w.Header().Set("X-Foo", "bar") 62 w.Header().Set("Upgrade", "foo") 63 w.Header().Set(fakeHopHeader, "foo") 64 w.Header().Add("X-Multi-Value", "foo") 65 w.Header().Add("X-Multi-Value", "bar") 66 http.SetCookie(w, &http.Cookie{Name: "flavor", Value: "chocolateChip"}) 67 w.WriteHeader(backendStatus) 68 w.Write([]byte(backendResponse)) 69 w.Header().Set("X-Trailer", "trailer_value") 70 })) 71 defer backend.Close() 72 backendURL, err := url.Parse(backend.URL) 73 if err != nil { 74 t.Fatal(err) 75 } 76 proxyHandler := NewSingleHostReverseProxy(backendURL) 77 proxyHandler.ErrorLog = log.New(ioutil.Discard, "", 0) // quiet for tests 78 frontend := httptest.NewServer(proxyHandler) 79 defer frontend.Close() 80 81 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 82 getReq.Host = "some-name" 83 getReq.Header.Set("Connection", "close") 84 getReq.Header.Set("Proxy-Connection", "should be deleted") 85 getReq.Header.Set("Upgrade", "foo") 86 getReq.Close = true 87 res, err := http.DefaultClient.Do(getReq) 88 if err != nil { 89 t.Fatalf("Get: %v", err) 90 } 91 if g, e := res.StatusCode, backendStatus; g != e { 92 t.Errorf("got res.StatusCode %d; expected %d", g, e) 93 } 94 if g, e := res.Header.Get("X-Foo"), "bar"; g != e { 95 t.Errorf("got X-Foo %q; expected %q", g, e) 96 } 97 if c := res.Header.Get(fakeHopHeader); c != "" { 98 t.Errorf("got %s header value %q", fakeHopHeader, c) 99 } 100 if g, e := res.Header.Get("Trailers"), "not a special header field name"; g != e { 101 t.Errorf("header Trailers = %q; want %q", g, e) 102 } 103 if g, e := len(res.Header["X-Multi-Value"]), 2; g != e { 104 t.Errorf("got %d X-Multi-Value header values; expected %d", g, e) 105 } 106 if g, e := len(res.Header["Set-Cookie"]), 1; g != e { 107 t.Fatalf("got %d SetCookies, want %d", g, e) 108 } 109 if g, e := res.Trailer, (http.Header{"X-Trailer": nil}); !reflect.DeepEqual(g, e) { 110 t.Errorf("before reading body, Trailer = %#v; want %#v", g, e) 111 } 112 if cookie := res.Cookies()[0]; cookie.Name != "flavor" { 113 t.Errorf("unexpected cookie %q", cookie.Name) 114 } 115 bodyBytes, _ := ioutil.ReadAll(res.Body) 116 if g, e := string(bodyBytes), backendResponse; g != e { 117 t.Errorf("got body %q; expected %q", g, e) 118 } 119 if g, e := res.Trailer.Get("X-Trailer"), "trailer_value"; g != e { 120 t.Errorf("Trailer(X-Trailer) = %q ; want %q", g, e) 121 } 122 123 // Test that a backend failing to be reached or one which doesn't return 124 // a response results in a StatusBadGateway. 125 getReq, _ = http.NewRequest("GET", frontend.URL+"/?mode=hangup", nil) 126 getReq.Close = true 127 res, err = http.DefaultClient.Do(getReq) 128 if err != nil { 129 t.Fatal(err) 130 } 131 res.Body.Close() 132 if res.StatusCode != http.StatusBadGateway { 133 t.Errorf("request to bad proxy = %v; want 502 StatusBadGateway", res.Status) 134 } 135 136 } 137 138 func TestXForwardedFor(t *testing.T) { 139 const prevForwardedFor = "client ip" 140 const backendResponse = "I am the backend" 141 const backendStatus = 404 142 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 143 if r.Header.Get("X-Forwarded-For") == "" { 144 t.Errorf("didn't get X-Forwarded-For header") 145 } 146 if !strings.Contains(r.Header.Get("X-Forwarded-For"), prevForwardedFor) { 147 t.Errorf("X-Forwarded-For didn't contain prior data") 148 } 149 w.WriteHeader(backendStatus) 150 w.Write([]byte(backendResponse)) 151 })) 152 defer backend.Close() 153 backendURL, err := url.Parse(backend.URL) 154 if err != nil { 155 t.Fatal(err) 156 } 157 proxyHandler := NewSingleHostReverseProxy(backendURL) 158 frontend := httptest.NewServer(proxyHandler) 159 defer frontend.Close() 160 161 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 162 getReq.Host = "some-name" 163 getReq.Header.Set("Connection", "close") 164 getReq.Header.Set("X-Forwarded-For", prevForwardedFor) 165 getReq.Close = true 166 res, err := http.DefaultClient.Do(getReq) 167 if err != nil { 168 t.Fatalf("Get: %v", err) 169 } 170 if g, e := res.StatusCode, backendStatus; g != e { 171 t.Errorf("got res.StatusCode %d; expected %d", g, e) 172 } 173 bodyBytes, _ := ioutil.ReadAll(res.Body) 174 if g, e := string(bodyBytes), backendResponse; g != e { 175 t.Errorf("got body %q; expected %q", g, e) 176 } 177 } 178 179 var proxyQueryTests = []struct { 180 baseSuffix string // suffix to add to backend URL 181 reqSuffix string // suffix to add to frontend's request URL 182 want string // what backend should see for final request URL (without ?) 183 }{ 184 {"", "", ""}, 185 {"?sta=tic", "?us=er", "sta=tic&us=er"}, 186 {"", "?us=er", "us=er"}, 187 {"?sta=tic", "", "sta=tic"}, 188 } 189 190 func TestReverseProxyQuery(t *testing.T) { 191 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 192 w.Header().Set("X-Got-Query", r.URL.RawQuery) 193 w.Write([]byte("hi")) 194 })) 195 defer backend.Close() 196 197 for i, tt := range proxyQueryTests { 198 backendURL, err := url.Parse(backend.URL + tt.baseSuffix) 199 if err != nil { 200 t.Fatal(err) 201 } 202 frontend := httptest.NewServer(NewSingleHostReverseProxy(backendURL)) 203 req, _ := http.NewRequest("GET", frontend.URL+tt.reqSuffix, nil) 204 req.Close = true 205 res, err := http.DefaultClient.Do(req) 206 if err != nil { 207 t.Fatalf("%d. Get: %v", i, err) 208 } 209 if g, e := res.Header.Get("X-Got-Query"), tt.want; g != e { 210 t.Errorf("%d. got query %q; expected %q", i, g, e) 211 } 212 res.Body.Close() 213 frontend.Close() 214 } 215 } 216 217 func TestReverseProxyFlushInterval(t *testing.T) { 218 const expected = "hi" 219 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 220 w.Write([]byte(expected)) 221 })) 222 defer backend.Close() 223 224 backendURL, err := url.Parse(backend.URL) 225 if err != nil { 226 t.Fatal(err) 227 } 228 229 proxyHandler := NewSingleHostReverseProxy(backendURL) 230 proxyHandler.FlushInterval = time.Microsecond 231 232 done := make(chan bool) 233 onExitFlushLoop = func() { done <- true } 234 defer func() { onExitFlushLoop = nil }() 235 236 frontend := httptest.NewServer(proxyHandler) 237 defer frontend.Close() 238 239 req, _ := http.NewRequest("GET", frontend.URL, nil) 240 req.Close = true 241 res, err := http.DefaultClient.Do(req) 242 if err != nil { 243 t.Fatalf("Get: %v", err) 244 } 245 defer res.Body.Close() 246 if bodyBytes, _ := ioutil.ReadAll(res.Body); string(bodyBytes) != expected { 247 t.Errorf("got body %q; expected %q", bodyBytes, expected) 248 } 249 250 select { 251 case <-done: 252 // OK 253 case <-time.After(5 * time.Second): 254 t.Error("maxLatencyWriter flushLoop() never exited") 255 } 256 } 257 258 func TestReverseProxyCancelation(t *testing.T) { 259 const backendResponse = "I am the backend" 260 261 reqInFlight := make(chan struct{}) 262 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 263 close(reqInFlight) 264 265 select { 266 case <-time.After(10 * time.Second): 267 // Note: this should only happen in broken implementations, and the 268 // closenotify case should be instantaneous. 269 t.Log("Failed to close backend connection") 270 t.Fail() 271 case <-w.(http.CloseNotifier).CloseNotify(): 272 } 273 274 w.WriteHeader(http.StatusOK) 275 w.Write([]byte(backendResponse)) 276 })) 277 278 defer backend.Close() 279 280 backend.Config.ErrorLog = log.New(ioutil.Discard, "", 0) 281 282 backendURL, err := url.Parse(backend.URL) 283 if err != nil { 284 t.Fatal(err) 285 } 286 287 proxyHandler := NewSingleHostReverseProxy(backendURL) 288 289 // Discards errors of the form: 290 // http: proxy error: read tcp 127.0.0.1:44643: use of closed network connection 291 proxyHandler.ErrorLog = log.New(ioutil.Discard, "", 0) 292 293 frontend := httptest.NewServer(proxyHandler) 294 defer frontend.Close() 295 296 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 297 go func() { 298 <-reqInFlight 299 http.DefaultTransport.(*http.Transport).CancelRequest(getReq) 300 }() 301 res, err := http.DefaultClient.Do(getReq) 302 if res != nil { 303 t.Fatal("Non-nil response") 304 } 305 if err == nil { 306 // This should be an error like: 307 // Get http://127.0.0.1:58079: read tcp 127.0.0.1:58079: 308 // use of closed network connection 309 t.Fatal("DefaultClient.Do() returned nil error") 310 } 311 } 312 313 func req(t *testing.T, v string) *http.Request { 314 req, err := http.ReadRequest(bufio.NewReader(strings.NewReader(v))) 315 if err != nil { 316 t.Fatal(err) 317 } 318 return req 319 } 320 321 // Issue 12344 322 func TestNilBody(t *testing.T) { 323 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 324 w.Write([]byte("hi")) 325 })) 326 defer backend.Close() 327 328 frontend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 329 backURL, _ := url.Parse(backend.URL) 330 rp := NewSingleHostReverseProxy(backURL) 331 r := req(t, "GET / HTTP/1.0\r\n\r\n") 332 r.Body = nil // this accidentally worked in Go 1.4 and below, so keep it working 333 rp.ServeHTTP(w, r) 334 })) 335 defer frontend.Close() 336 337 res, err := http.Get(frontend.URL) 338 if err != nil { 339 t.Fatal(err) 340 } 341 defer res.Body.Close() 342 slurp, err := ioutil.ReadAll(res.Body) 343 if err != nil { 344 t.Fatal(err) 345 } 346 if string(slurp) != "hi" { 347 t.Errorf("Got %q; want %q", slurp, "hi") 348 } 349 } 350 351 // Issue 15524 352 func TestUserAgentHeader(t *testing.T) { 353 const explicitUA = "explicit UA" 354 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 355 if r.URL.Path == "/noua" { 356 if c := r.Header.Get("User-Agent"); c != "" { 357 t.Errorf("handler got non-empty User-Agent header %q", c) 358 } 359 return 360 } 361 if c := r.Header.Get("User-Agent"); c != explicitUA { 362 t.Errorf("handler got unexpected User-Agent header %q", c) 363 } 364 })) 365 defer backend.Close() 366 backendURL, err := url.Parse(backend.URL) 367 if err != nil { 368 t.Fatal(err) 369 } 370 proxyHandler := NewSingleHostReverseProxy(backendURL) 371 proxyHandler.ErrorLog = log.New(ioutil.Discard, "", 0) // quiet for tests 372 frontend := httptest.NewServer(proxyHandler) 373 defer frontend.Close() 374 375 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 376 getReq.Header.Set("User-Agent", explicitUA) 377 getReq.Close = true 378 res, err := http.DefaultClient.Do(getReq) 379 if err != nil { 380 t.Fatalf("Get: %v", err) 381 } 382 res.Body.Close() 383 384 getReq, _ = http.NewRequest("GET", frontend.URL+"/noua", nil) 385 getReq.Header.Set("User-Agent", "") 386 getReq.Close = true 387 res, err = http.DefaultClient.Do(getReq) 388 if err != nil { 389 t.Fatalf("Get: %v", err) 390 } 391 res.Body.Close() 392 } 393 394 type bufferPool struct { 395 get func() []byte 396 put func([]byte) 397 } 398 399 func (bp bufferPool) Get() []byte { return bp.get() } 400 func (bp bufferPool) Put(v []byte) { bp.put(v) } 401 402 func TestReverseProxyGetPutBuffer(t *testing.T) { 403 const msg = "hi" 404 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 405 io.WriteString(w, msg) 406 })) 407 defer backend.Close() 408 409 backendURL, err := url.Parse(backend.URL) 410 if err != nil { 411 t.Fatal(err) 412 } 413 414 var ( 415 mu sync.Mutex 416 log []string 417 ) 418 addLog := func(event string) { 419 mu.Lock() 420 defer mu.Unlock() 421 log = append(log, event) 422 } 423 rp := NewSingleHostReverseProxy(backendURL) 424 const size = 1234 425 rp.BufferPool = bufferPool{ 426 get: func() []byte { 427 addLog("getBuf") 428 return make([]byte, size) 429 }, 430 put: func(p []byte) { 431 addLog("putBuf-" + strconv.Itoa(len(p))) 432 }, 433 } 434 frontend := httptest.NewServer(rp) 435 defer frontend.Close() 436 437 req, _ := http.NewRequest("GET", frontend.URL, nil) 438 req.Close = true 439 res, err := http.DefaultClient.Do(req) 440 if err != nil { 441 t.Fatalf("Get: %v", err) 442 } 443 slurp, err := ioutil.ReadAll(res.Body) 444 res.Body.Close() 445 if err != nil { 446 t.Fatalf("reading body: %v", err) 447 } 448 if string(slurp) != msg { 449 t.Errorf("msg = %q; want %q", slurp, msg) 450 } 451 wantLog := []string{"getBuf", "putBuf-" + strconv.Itoa(size)} 452 mu.Lock() 453 defer mu.Unlock() 454 if !reflect.DeepEqual(log, wantLog) { 455 t.Errorf("Log events = %q; want %q", log, wantLog) 456 } 457 } 458 459 func TestReverseProxy_Post(t *testing.T) { 460 const backendResponse = "I am the backend" 461 const backendStatus = 200 462 var requestBody = bytes.Repeat([]byte("a"), 1<<20) 463 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 464 slurp, err := ioutil.ReadAll(r.Body) 465 if err != nil { 466 t.Errorf("Backend body read = %v", err) 467 } 468 if len(slurp) != len(requestBody) { 469 t.Errorf("Backend read %d request body bytes; want %d", len(slurp), len(requestBody)) 470 } 471 if !bytes.Equal(slurp, requestBody) { 472 t.Error("Backend read wrong request body.") // 1MB; omitting details 473 } 474 w.Write([]byte(backendResponse)) 475 })) 476 defer backend.Close() 477 backendURL, err := url.Parse(backend.URL) 478 if err != nil { 479 t.Fatal(err) 480 } 481 proxyHandler := NewSingleHostReverseProxy(backendURL) 482 frontend := httptest.NewServer(proxyHandler) 483 defer frontend.Close() 484 485 postReq, _ := http.NewRequest("POST", frontend.URL, bytes.NewReader(requestBody)) 486 res, err := http.DefaultClient.Do(postReq) 487 if err != nil { 488 t.Fatalf("Do: %v", err) 489 } 490 if g, e := res.StatusCode, backendStatus; g != e { 491 t.Errorf("got res.StatusCode %d; expected %d", g, e) 492 } 493 bodyBytes, _ := ioutil.ReadAll(res.Body) 494 if g, e := string(bodyBytes), backendResponse; g != e { 495 t.Errorf("got body %q; expected %q", g, e) 496 } 497 }