github.com/bgentry/go@v0.0.0-20150121062915-6cf5a733d54d/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 "io/ioutil" 11 "log" 12 "net/http" 13 "net/http/httptest" 14 "net/url" 15 "runtime" 16 "strings" 17 "testing" 18 "time" 19 ) 20 21 const fakeHopHeader = "X-Fake-Hop-Header-For-Test" 22 23 func init() { 24 hopHeaders = append(hopHeaders, fakeHopHeader) 25 } 26 27 func TestReverseProxy(t *testing.T) { 28 const backendResponse = "I am the backend" 29 const backendStatus = 404 30 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 31 if len(r.TransferEncoding) > 0 { 32 t.Errorf("backend got unexpected TransferEncoding: %v", r.TransferEncoding) 33 } 34 if r.Header.Get("X-Forwarded-For") == "" { 35 t.Errorf("didn't get X-Forwarded-For header") 36 } 37 if c := r.Header.Get("Connection"); c != "" { 38 t.Errorf("handler got Connection header value %q", c) 39 } 40 if c := r.Header.Get("Upgrade"); c != "" { 41 t.Errorf("handler got Upgrade header value %q", c) 42 } 43 if g, e := r.Host, "some-name"; g != e { 44 t.Errorf("backend got Host header %q, want %q", g, e) 45 } 46 w.Header().Set("X-Foo", "bar") 47 w.Header().Set("Upgrade", "foo") 48 w.Header().Set(fakeHopHeader, "foo") 49 w.Header().Add("X-Multi-Value", "foo") 50 w.Header().Add("X-Multi-Value", "bar") 51 http.SetCookie(w, &http.Cookie{Name: "flavor", Value: "chocolateChip"}) 52 w.WriteHeader(backendStatus) 53 w.Write([]byte(backendResponse)) 54 })) 55 defer backend.Close() 56 backendURL, err := url.Parse(backend.URL) 57 if err != nil { 58 t.Fatal(err) 59 } 60 proxyHandler := NewSingleHostReverseProxy(backendURL) 61 frontend := httptest.NewServer(proxyHandler) 62 defer frontend.Close() 63 64 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 65 getReq.Host = "some-name" 66 getReq.Header.Set("Connection", "close") 67 getReq.Header.Set("Upgrade", "foo") 68 getReq.Close = true 69 res, err := http.DefaultClient.Do(getReq) 70 if err != nil { 71 t.Fatalf("Get: %v", err) 72 } 73 if g, e := res.StatusCode, backendStatus; g != e { 74 t.Errorf("got res.StatusCode %d; expected %d", g, e) 75 } 76 if g, e := res.Header.Get("X-Foo"), "bar"; g != e { 77 t.Errorf("got X-Foo %q; expected %q", g, e) 78 } 79 if c := res.Header.Get(fakeHopHeader); c != "" { 80 t.Errorf("got %s header value %q", fakeHopHeader, c) 81 } 82 if g, e := len(res.Header["X-Multi-Value"]), 2; g != e { 83 t.Errorf("got %d X-Multi-Value header values; expected %d", g, e) 84 } 85 if g, e := len(res.Header["Set-Cookie"]), 1; g != e { 86 t.Fatalf("got %d SetCookies, want %d", g, e) 87 } 88 if cookie := res.Cookies()[0]; cookie.Name != "flavor" { 89 t.Errorf("unexpected cookie %q", cookie.Name) 90 } 91 bodyBytes, _ := ioutil.ReadAll(res.Body) 92 if g, e := string(bodyBytes), backendResponse; g != e { 93 t.Errorf("got body %q; expected %q", g, e) 94 } 95 } 96 97 func TestXForwardedFor(t *testing.T) { 98 const prevForwardedFor = "client ip" 99 const backendResponse = "I am the backend" 100 const backendStatus = 404 101 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 102 if r.Header.Get("X-Forwarded-For") == "" { 103 t.Errorf("didn't get X-Forwarded-For header") 104 } 105 if !strings.Contains(r.Header.Get("X-Forwarded-For"), prevForwardedFor) { 106 t.Errorf("X-Forwarded-For didn't contain prior data") 107 } 108 w.WriteHeader(backendStatus) 109 w.Write([]byte(backendResponse)) 110 })) 111 defer backend.Close() 112 backendURL, err := url.Parse(backend.URL) 113 if err != nil { 114 t.Fatal(err) 115 } 116 proxyHandler := NewSingleHostReverseProxy(backendURL) 117 frontend := httptest.NewServer(proxyHandler) 118 defer frontend.Close() 119 120 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 121 getReq.Host = "some-name" 122 getReq.Header.Set("Connection", "close") 123 getReq.Header.Set("X-Forwarded-For", prevForwardedFor) 124 getReq.Close = true 125 res, err := http.DefaultClient.Do(getReq) 126 if err != nil { 127 t.Fatalf("Get: %v", err) 128 } 129 if g, e := res.StatusCode, backendStatus; g != e { 130 t.Errorf("got res.StatusCode %d; expected %d", g, e) 131 } 132 bodyBytes, _ := ioutil.ReadAll(res.Body) 133 if g, e := string(bodyBytes), backendResponse; g != e { 134 t.Errorf("got body %q; expected %q", g, e) 135 } 136 } 137 138 var proxyQueryTests = []struct { 139 baseSuffix string // suffix to add to backend URL 140 reqSuffix string // suffix to add to frontend's request URL 141 want string // what backend should see for final request URL (without ?) 142 }{ 143 {"", "", ""}, 144 {"?sta=tic", "?us=er", "sta=tic&us=er"}, 145 {"", "?us=er", "us=er"}, 146 {"?sta=tic", "", "sta=tic"}, 147 } 148 149 func TestReverseProxyQuery(t *testing.T) { 150 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 151 w.Header().Set("X-Got-Query", r.URL.RawQuery) 152 w.Write([]byte("hi")) 153 })) 154 defer backend.Close() 155 156 for i, tt := range proxyQueryTests { 157 backendURL, err := url.Parse(backend.URL + tt.baseSuffix) 158 if err != nil { 159 t.Fatal(err) 160 } 161 frontend := httptest.NewServer(NewSingleHostReverseProxy(backendURL)) 162 req, _ := http.NewRequest("GET", frontend.URL+tt.reqSuffix, nil) 163 req.Close = true 164 res, err := http.DefaultClient.Do(req) 165 if err != nil { 166 t.Fatalf("%d. Get: %v", i, err) 167 } 168 if g, e := res.Header.Get("X-Got-Query"), tt.want; g != e { 169 t.Errorf("%d. got query %q; expected %q", i, g, e) 170 } 171 res.Body.Close() 172 frontend.Close() 173 } 174 } 175 176 func TestReverseProxyFlushInterval(t *testing.T) { 177 const expected = "hi" 178 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 179 w.Write([]byte(expected)) 180 })) 181 defer backend.Close() 182 183 backendURL, err := url.Parse(backend.URL) 184 if err != nil { 185 t.Fatal(err) 186 } 187 188 proxyHandler := NewSingleHostReverseProxy(backendURL) 189 proxyHandler.FlushInterval = time.Microsecond 190 191 done := make(chan bool) 192 onExitFlushLoop = func() { done <- true } 193 defer func() { onExitFlushLoop = nil }() 194 195 frontend := httptest.NewServer(proxyHandler) 196 defer frontend.Close() 197 198 req, _ := http.NewRequest("GET", frontend.URL, nil) 199 req.Close = true 200 res, err := http.DefaultClient.Do(req) 201 if err != nil { 202 t.Fatalf("Get: %v", err) 203 } 204 defer res.Body.Close() 205 if bodyBytes, _ := ioutil.ReadAll(res.Body); string(bodyBytes) != expected { 206 t.Errorf("got body %q; expected %q", bodyBytes, expected) 207 } 208 209 select { 210 case <-done: 211 // OK 212 case <-time.After(5 * time.Second): 213 t.Error("maxLatencyWriter flushLoop() never exited") 214 } 215 } 216 217 func TestReverseProxyCancellation(t *testing.T) { 218 if runtime.GOOS == "plan9" { 219 t.Skip("skipping test; see http://golang.org/issue/9554") 220 } 221 const backendResponse = "I am the backend" 222 223 reqInFlight := make(chan struct{}) 224 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 225 close(reqInFlight) 226 227 select { 228 case <-time.After(10 * time.Second): 229 // Note: this should only happen in broken implementations, and the 230 // closenotify case should be instantaneous. 231 t.Log("Failed to close backend connection") 232 t.Fail() 233 case <-w.(http.CloseNotifier).CloseNotify(): 234 } 235 236 w.WriteHeader(http.StatusOK) 237 w.Write([]byte(backendResponse)) 238 })) 239 240 defer backend.Close() 241 242 backend.Config.ErrorLog = log.New(ioutil.Discard, "", 0) 243 244 backendURL, err := url.Parse(backend.URL) 245 if err != nil { 246 t.Fatal(err) 247 } 248 249 proxyHandler := NewSingleHostReverseProxy(backendURL) 250 251 // Discards errors of the form: 252 // http: proxy error: read tcp 127.0.0.1:44643: use of closed network connection 253 proxyHandler.ErrorLog = log.New(ioutil.Discard, "", 0) 254 255 frontend := httptest.NewServer(proxyHandler) 256 defer frontend.Close() 257 258 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 259 go func() { 260 <-reqInFlight 261 http.DefaultTransport.(*http.Transport).CancelRequest(getReq) 262 }() 263 res, err := http.DefaultClient.Do(getReq) 264 if res != nil { 265 t.Fatal("Non-nil response") 266 } 267 if err == nil { 268 // This should be an error like: 269 // Get http://127.0.0.1:58079: read tcp 127.0.0.1:58079: 270 // use of closed network connection 271 t.Fatal("DefaultClient.Do() returned nil error") 272 } 273 }