github.com/llimllib/devd@v0.0.0-20230426145215-4d29fc25f909/reverseproxy/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 reverseproxy 8 9 import ( 10 "io" 11 "net/http" 12 "net/http/httptest" 13 "net/url" 14 "strings" 15 "testing" 16 "time" 17 18 "github.com/llimllib/devd/inject" 19 ) 20 21 func TestReverseProxy(t *testing.T) { 22 const backendResponse = "I am the backend" 23 const backendStatus = 404 24 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 25 if len(r.TransferEncoding) > 0 { 26 t.Errorf("backend got unexpected TransferEncoding: %v", r.TransferEncoding) 27 } 28 if r.Header.Get("X-Forwarded-For") == "" { 29 t.Errorf("didn't get X-Forwarded-For header") 30 } 31 if c := r.Header.Get("Connection"); c != "" { 32 t.Errorf("handler got Connection header value %q", c) 33 } 34 if c := r.Header.Get("Upgrade"); c != "" { 35 t.Errorf("handler got Upgrade header value %q", c) 36 } 37 if g, e := r.Host, "some-name"; g == e { 38 t.Errorf("backend got original Host header %q, expected over-written", g) 39 } 40 if acceptEncoding := r.Header.Get("Accept-Encoding"); acceptEncoding != "identity" { 41 t.Errorf( 42 "backend got unexpected or no Accept-Encoding header: %q, expected \"identity\"", 43 acceptEncoding, 44 ) 45 } 46 w.Header().Set("X-Foo", "bar") 47 http.SetCookie(w, &http.Cookie{Name: "flavor", Value: "chocolateChip"}) 48 w.WriteHeader(backendStatus) 49 if _, err := w.Write([]byte(backendResponse)); err != nil { 50 t.Errorf("unable to write response") 51 } 52 })) 53 defer backend.Close() 54 backendURL, err := url.Parse(backend.URL) 55 if err != nil { 56 t.Fatal(err) 57 } 58 proxyHandler := NewSingleHostReverseProxy(backendURL, inject.CopyInject{}) 59 frontend := httptest.NewServer(proxyHandler) 60 defer frontend.Close() 61 62 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 63 getReq.Host = "some-name" 64 getReq.Header.Set("Connection", "close") 65 getReq.Header.Set("Upgrade", "foo") 66 getReq.Close = true 67 res, err := http.DefaultClient.Do(getReq) 68 if err != nil { 69 t.Fatalf("Get: %v", err) 70 } 71 if g, e := res.StatusCode, backendStatus; g != e { 72 t.Errorf("got res.StatusCode %d; expected %d", g, e) 73 } 74 if g, e := res.Header.Get("X-Foo"), "bar"; g != e { 75 t.Errorf("got X-Foo %q; expected %q", g, e) 76 } 77 if g, e := len(res.Header["Set-Cookie"]), 1; g != e { 78 t.Fatalf("got %d SetCookies, want %d", g, e) 79 } 80 if cookie := res.Cookies()[0]; cookie.Name != "flavor" { 81 t.Errorf("unexpected cookie %q", cookie.Name) 82 } 83 bodyBytes, _ := io.ReadAll(res.Body) 84 if g, e := string(bodyBytes), backendResponse; g != e { 85 t.Errorf("got body %q; expected %q", g, e) 86 } 87 } 88 89 func TestXForwardedFor(t *testing.T) { 90 const prevForwardedFor = "client ip" 91 const backendResponse = "I am the backend" 92 const backendStatus = 404 93 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 94 if r.Header.Get("X-Forwarded-For") == "" { 95 t.Errorf("didn't get X-Forwarded-For header") 96 } 97 if !strings.Contains(r.Header.Get("X-Forwarded-For"), prevForwardedFor) { 98 t.Errorf("X-Forwarded-For didn't contain prior data") 99 } 100 w.WriteHeader(backendStatus) 101 if _, err := w.Write([]byte(backendResponse)); err != nil { 102 t.Errorf("unable to write response") 103 } 104 })) 105 defer backend.Close() 106 backendURL, err := url.Parse(backend.URL) 107 if err != nil { 108 t.Fatal(err) 109 } 110 proxyHandler := NewSingleHostReverseProxy(backendURL, inject.CopyInject{}) 111 frontend := httptest.NewServer(proxyHandler) 112 defer frontend.Close() 113 114 getReq, _ := http.NewRequest("GET", frontend.URL, nil) 115 getReq.Host = "some-name" 116 getReq.Header.Set("Connection", "close") 117 getReq.Header.Set("X-Forwarded-For", prevForwardedFor) 118 getReq.Close = true 119 res, err := http.DefaultClient.Do(getReq) 120 if err != nil { 121 t.Fatalf("Get: %v", err) 122 } 123 if g, e := res.StatusCode, backendStatus; g != e { 124 t.Errorf("got res.StatusCode %d; expected %d", g, e) 125 } 126 bodyBytes, _ := io.ReadAll(res.Body) 127 if g, e := string(bodyBytes), backendResponse; g != e { 128 t.Errorf("got body %q; expected %q", g, e) 129 } 130 } 131 132 var proxyQueryTests = []struct { 133 baseSuffix string // suffix to add to backend URL 134 reqSuffix string // suffix to add to frontend's request URL 135 want string // what backend should see for final request URL (without ?) 136 }{ 137 {"", "", ""}, 138 {"?sta=tic", "?us=er", "sta=tic&us=er"}, 139 {"", "?us=er", "us=er"}, 140 {"?sta=tic", "", "sta=tic"}, 141 } 142 143 func TestReverseProxyQuery(t *testing.T) { 144 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 145 w.Header().Set("X-Got-Query", r.URL.RawQuery) 146 if _, err := w.Write([]byte("hi")); err != nil { 147 t.Errorf("unable to write hi") 148 } 149 })) 150 defer backend.Close() 151 152 for i, tt := range proxyQueryTests { 153 backendURL, err := url.Parse(backend.URL + tt.baseSuffix) 154 if err != nil { 155 t.Fatal(err) 156 } 157 frontend := httptest.NewServer(NewSingleHostReverseProxy(backendURL, inject.CopyInject{})) 158 req, _ := http.NewRequest("GET", frontend.URL+tt.reqSuffix, nil) 159 req.Close = true 160 res, err := http.DefaultClient.Do(req) 161 if err != nil { 162 t.Fatalf("%d. Get: %v", i, err) 163 } 164 if g, e := res.Header.Get("X-Got-Query"), tt.want; g != e { 165 t.Errorf("%d. got query %q; expected %q", i, g, e) 166 } 167 res.Body.Close() 168 frontend.Close() 169 } 170 } 171 172 func TestReverseProxyFlushInterval(t *testing.T) { 173 const expected = "hi" 174 backend := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { 175 if _, err := w.Write([]byte(expected)); err != nil { 176 t.Errorf("unable to write %s", expected) 177 } 178 })) 179 defer backend.Close() 180 181 backendURL, err := url.Parse(backend.URL) 182 if err != nil { 183 t.Fatal(err) 184 } 185 186 proxyHandler := NewSingleHostReverseProxy(backendURL, inject.CopyInject{}) 187 proxyHandler.FlushInterval = time.Microsecond 188 189 done := make(chan bool) 190 onExitFlushLoop = func() { done <- true } 191 defer func() { onExitFlushLoop = nil }() 192 193 frontend := httptest.NewServer(proxyHandler) 194 defer frontend.Close() 195 196 req, _ := http.NewRequest("GET", frontend.URL, nil) 197 req.Close = true 198 res, err := http.DefaultClient.Do(req) 199 if err != nil { 200 t.Fatalf("Get: %v", err) 201 } 202 defer res.Body.Close() 203 if bodyBytes, _ := io.ReadAll(res.Body); string(bodyBytes) != expected { 204 t.Errorf("got body %q; expected %q", bodyBytes, expected) 205 } 206 207 select { 208 case <-done: 209 // OK 210 case <-time.After(5 * time.Second): 211 t.Error("maxLatencyWriter flushLoop() never exited") 212 } 213 }