go.etcd.io/etcd@v3.3.27+incompatible/proxy/httpproxy/reverse_test.go (about) 1 // Copyright 2015 The etcd Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package httpproxy 16 17 import ( 18 "bytes" 19 "errors" 20 "io/ioutil" 21 "net/http" 22 "net/http/httptest" 23 "net/url" 24 "reflect" 25 "testing" 26 ) 27 28 type staticRoundTripper struct { 29 res *http.Response 30 err error 31 } 32 33 func (srt *staticRoundTripper) RoundTrip(*http.Request) (*http.Response, error) { 34 return srt.res, srt.err 35 } 36 37 func TestReverseProxyServe(t *testing.T) { 38 u := url.URL{Scheme: "http", Host: "192.0.2.3:4040"} 39 40 tests := []struct { 41 eps []*endpoint 42 rt http.RoundTripper 43 want int 44 }{ 45 // no endpoints available so no requests are even made 46 { 47 eps: []*endpoint{}, 48 rt: &staticRoundTripper{ 49 res: &http.Response{ 50 StatusCode: http.StatusCreated, 51 Body: ioutil.NopCloser(&bytes.Reader{}), 52 }, 53 }, 54 want: http.StatusServiceUnavailable, 55 }, 56 57 // error is returned from one endpoint that should be available 58 { 59 eps: []*endpoint{{URL: u, Available: true}}, 60 rt: &staticRoundTripper{err: errors.New("what a bad trip")}, 61 want: http.StatusBadGateway, 62 }, 63 64 // endpoint is available and returns success 65 { 66 eps: []*endpoint{{URL: u, Available: true}}, 67 rt: &staticRoundTripper{ 68 res: &http.Response{ 69 StatusCode: http.StatusCreated, 70 Body: ioutil.NopCloser(&bytes.Reader{}), 71 Header: map[string][]string{"Content-Type": {"application/json"}}, 72 }, 73 }, 74 want: http.StatusCreated, 75 }, 76 } 77 78 for i, tt := range tests { 79 rp := reverseProxy{ 80 director: &director{ep: tt.eps}, 81 transport: tt.rt, 82 } 83 84 req, _ := http.NewRequest("GET", "http://192.0.2.2:2379", nil) 85 rr := httptest.NewRecorder() 86 rp.ServeHTTP(rr, req) 87 88 if rr.Code != tt.want { 89 t.Errorf("#%d: unexpected HTTP status code: want = %d, got = %d", i, tt.want, rr.Code) 90 } 91 if gct := rr.Header().Get("Content-Type"); gct != "application/json" { 92 t.Errorf("#%d: Content-Type = %s, want %s", i, gct, "application/json") 93 } 94 } 95 } 96 97 func TestRedirectRequest(t *testing.T) { 98 loc := url.URL{ 99 Scheme: "http", 100 Host: "bar.example.com", 101 } 102 103 req := &http.Request{ 104 Method: "GET", 105 Host: "foo.example.com", 106 URL: &url.URL{ 107 Host: "foo.example.com", 108 Path: "/v2/keys/baz", 109 }, 110 } 111 112 redirectRequest(req, loc) 113 114 want := &http.Request{ 115 Method: "GET", 116 // this field must not change 117 Host: "foo.example.com", 118 URL: &url.URL{ 119 // the Scheme field is updated to that of the provided URL 120 Scheme: "http", 121 // the Host field is updated to that of the provided URL 122 Host: "bar.example.com", 123 Path: "/v2/keys/baz", 124 }, 125 } 126 127 if !reflect.DeepEqual(want, req) { 128 t.Fatalf("HTTP request does not match expected criteria: want=%#v got=%#v", want, req) 129 } 130 } 131 132 func TestMaybeSetForwardedFor(t *testing.T) { 133 tests := []struct { 134 raddr string 135 fwdFor string 136 want string 137 }{ 138 {"192.0.2.3:8002", "", "192.0.2.3"}, 139 {"192.0.2.3:8002", "192.0.2.2", "192.0.2.2, 192.0.2.3"}, 140 {"192.0.2.3:8002", "192.0.2.1, 192.0.2.2", "192.0.2.1, 192.0.2.2, 192.0.2.3"}, 141 {"example.com:8002", "", "example.com"}, 142 143 // While these cases look valid, golang net/http will not let it happen 144 // The RemoteAddr field will always be a valid host:port 145 {":8002", "", ""}, 146 {"192.0.2.3", "", ""}, 147 148 // blatantly invalid host w/o a port 149 {"12", "", ""}, 150 {"12", "192.0.2.3", "192.0.2.3"}, 151 } 152 153 for i, tt := range tests { 154 req := &http.Request{ 155 RemoteAddr: tt.raddr, 156 Header: make(http.Header), 157 } 158 159 if tt.fwdFor != "" { 160 req.Header.Set("X-Forwarded-For", tt.fwdFor) 161 } 162 163 maybeSetForwardedFor(req) 164 got := req.Header.Get("X-Forwarded-For") 165 if tt.want != got { 166 t.Errorf("#%d: incorrect header: want = %q, got = %q", i, tt.want, got) 167 } 168 } 169 } 170 171 func TestRemoveSingleHopHeaders(t *testing.T) { 172 hdr := http.Header(map[string][]string{ 173 // single-hop headers that should be removed 174 "Connection": {"close"}, 175 "Keep-Alive": {"foo"}, 176 "Proxy-Authenticate": {"Basic realm=example.com"}, 177 "Proxy-Authorization": {"foo"}, 178 "Te": {"deflate,gzip"}, 179 "Trailers": {"ETag"}, 180 "Transfer-Encoding": {"chunked"}, 181 "Upgrade": {"WebSocket"}, 182 183 // headers that should persist 184 "Accept": {"application/json"}, 185 "X-Foo": {"Bar"}, 186 }) 187 188 removeSingleHopHeaders(&hdr) 189 190 want := http.Header(map[string][]string{ 191 "Accept": {"application/json"}, 192 "X-Foo": {"Bar"}, 193 }) 194 195 if !reflect.DeepEqual(want, hdr) { 196 t.Fatalf("unexpected result: want = %#v, got = %#v", want, hdr) 197 } 198 } 199 200 func TestCopyHeader(t *testing.T) { 201 tests := []struct { 202 src http.Header 203 dst http.Header 204 want http.Header 205 }{ 206 { 207 src: http.Header(map[string][]string{ 208 "Foo": {"bar", "baz"}, 209 }), 210 dst: http.Header(map[string][]string{}), 211 want: http.Header(map[string][]string{ 212 "Foo": {"bar", "baz"}, 213 }), 214 }, 215 { 216 src: http.Header(map[string][]string{ 217 "Foo": {"bar"}, 218 "Ping": {"pong"}, 219 }), 220 dst: http.Header(map[string][]string{}), 221 want: http.Header(map[string][]string{ 222 "Foo": {"bar"}, 223 "Ping": {"pong"}, 224 }), 225 }, 226 { 227 src: http.Header(map[string][]string{ 228 "Foo": {"bar", "baz"}, 229 }), 230 dst: http.Header(map[string][]string{ 231 "Foo": {"qux"}, 232 }), 233 want: http.Header(map[string][]string{ 234 "Foo": {"qux", "bar", "baz"}, 235 }), 236 }, 237 } 238 239 for i, tt := range tests { 240 copyHeader(tt.dst, tt.src) 241 if !reflect.DeepEqual(tt.dst, tt.want) { 242 t.Errorf("#%d: unexpected headers: want = %v, got = %v", i, tt.want, tt.dst) 243 } 244 } 245 }