github.com/yandex/pandora@v0.5.32/components/guns/http/http_test.go (about) 1 package phttp 2 3 import ( 4 "crypto/tls" 5 "net/http" 6 "net/http/httptest" 7 "strings" 8 "testing" 9 10 "github.com/stretchr/testify/require" 11 ammomock "github.com/yandex/pandora/components/guns/http/mocks" 12 "github.com/yandex/pandora/core/aggregator/netsample" 13 "github.com/yandex/pandora/core/config" 14 "go.uber.org/atomic" 15 "go.uber.org/zap" 16 "golang.org/x/net/http2" 17 ) 18 19 func TestBaseGun_GunClientConfig_decode(t *testing.T) { 20 conf := DefaultHTTPGunConfig() 21 data := map[interface{}]interface{}{ 22 "target": "test-trbo01e.haze.yandex.net:3000", 23 } 24 err := config.DecodeAndValidate(data, &conf) 25 require.NoError(t, err) 26 } 27 28 func TestBaseGun_integration(t *testing.T) { 29 const host = "example.com" 30 const path = "/smth" 31 expectedReq, err := http.NewRequest("GET", "http://"+host+path, nil) 32 expectedReq.Host = "" // Important. Ammo may have empty host. 33 require.NoError(t, err) 34 var actualReq *http.Request 35 server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 36 rw.WriteHeader(http.StatusOK) 37 actualReq = req 38 })) 39 defer server.Close() 40 log := zap.NewNop() 41 conf := DefaultHTTPGunConfig() 42 conf.Target = host + ":80" 43 targetResolved := strings.TrimPrefix(server.URL, "http://") 44 results := &netsample.TestAggregator{} 45 conf.TargetResolved = targetResolved 46 httpGun := NewHTTP1Gun(conf, log) 47 _ = httpGun.Bind(results, testDeps()) 48 49 am := newAmmoReq(t, expectedReq) 50 httpGun.Shoot(am) 51 require.NoError(t, results.Samples[0].Err()) 52 require.NotNil(t, actualReq) 53 54 require.Equal(t, actualReq.Method, "GET") 55 require.Equal(t, actualReq.Proto, "HTTP/1.1") 56 require.Equal(t, actualReq.Host, host) 57 require.NotNil(t, actualReq.URL) 58 require.Empty(t, actualReq.URL.Host) 59 require.Equal(t, actualReq.URL.Path, path) 60 } 61 62 func TestHTTP(t *testing.T) { 63 tests := []struct { 64 name string 65 https bool 66 }{ 67 { 68 name: "http ok", 69 https: false, 70 }, 71 { 72 name: "https ok", 73 https: true, 74 }, 75 } 76 for _, tt := range tests { 77 t.Run(tt.name, func(t *testing.T) { 78 var isServed atomic.Bool 79 server := httptest.NewUnstartedServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 80 require.Empty(t, req.Header.Get("Accept-Encoding")) 81 rw.WriteHeader(http.StatusOK) 82 isServed.Store(true) 83 })) 84 if tt.https { 85 server.StartTLS() 86 } else { 87 server.Start() 88 } 89 defer server.Close() 90 log := zap.NewNop() 91 conf := DefaultHTTPGunConfig() 92 conf.Target = server.Listener.Addr().String() 93 conf.SSL = tt.https 94 conf.TargetResolved = conf.Target 95 gun := NewHTTP1Gun(conf, log) 96 var aggr netsample.TestAggregator 97 _ = gun.Bind(&aggr, testDeps()) 98 gun.Shoot(newAmmoURL(t, "/")) 99 100 require.Equal(t, len(aggr.Samples), 1) 101 require.Equal(t, aggr.Samples[0].ProtoCode(), http.StatusOK) 102 require.True(t, isServed.Load()) 103 }) 104 } 105 } 106 107 func TestHTTP_Redirect(t *testing.T) { 108 tests := []struct { 109 name string 110 redirect bool 111 }{ 112 { 113 name: "not follow redirects by default", 114 redirect: false, 115 }, 116 { 117 name: "follow redirects if option set", 118 redirect: true, 119 }, 120 } 121 for _, tt := range tests { 122 t.Run(tt.name, func(t *testing.T) { 123 server := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 124 if req.URL.Path == "/redirect" { 125 rw.Header().Add("Location", "/") 126 rw.WriteHeader(http.StatusMovedPermanently) 127 } else { 128 rw.WriteHeader(http.StatusOK) 129 } 130 })) 131 defer server.Close() 132 log := zap.NewNop() 133 conf := DefaultHTTPGunConfig() 134 conf.Target = server.Listener.Addr().String() 135 conf.Client.Redirect = tt.redirect 136 conf.TargetResolved = conf.Target 137 gun := NewHTTP1Gun(conf, log) 138 var aggr netsample.TestAggregator 139 _ = gun.Bind(&aggr, testDeps()) 140 gun.Shoot(newAmmoURL(t, "/redirect")) 141 142 require.Equal(t, len(aggr.Samples), 1) 143 expectedCode := http.StatusMovedPermanently 144 if tt.redirect { 145 expectedCode = http.StatusOK 146 } 147 require.Equal(t, aggr.Samples[0].ProtoCode(), expectedCode) 148 }) 149 } 150 } 151 152 func TestHTTP_notSupportHTTP2(t *testing.T) { 153 server := newHTTP2TestServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 154 if isHTTP2Request(req) { 155 rw.WriteHeader(http.StatusForbidden) 156 } else { 157 rw.WriteHeader(http.StatusOK) 158 } 159 })) 160 defer server.Close() 161 162 // Test, that configured server serves HTTP2 well. 163 http2OnlyClient := http.Client{ 164 Transport: &http2.Transport{ 165 TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 166 }} 167 res, err := http2OnlyClient.Get(server.URL) 168 require.NoError(t, err) 169 require.Equal(t, res.StatusCode, http.StatusForbidden) 170 171 log := zap.NewNop() 172 conf := DefaultHTTPGunConfig() 173 conf.Target = server.Listener.Addr().String() 174 conf.SSL = true 175 conf.TargetResolved = conf.Target 176 gun := NewHTTP1Gun(conf, log) 177 var results netsample.TestAggregator 178 _ = gun.Bind(&results, testDeps()) 179 gun.Shoot(newAmmoURL(t, "/")) 180 181 require.Equal(t, len(results.Samples), 1) 182 require.Equal(t, results.Samples[0].ProtoCode(), http.StatusOK) 183 } 184 185 func TestHTTP2(t *testing.T) { 186 t.Run("HTTP/2 ok", func(t *testing.T) { 187 server := newHTTP2TestServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 188 if isHTTP2Request(req) { 189 rw.WriteHeader(http.StatusOK) 190 } else { 191 rw.WriteHeader(http.StatusForbidden) 192 } 193 })) 194 defer server.Close() 195 log := zap.NewNop() 196 conf := DefaultHTTP2GunConfig() 197 conf.Target = server.Listener.Addr().String() 198 conf.TargetResolved = conf.Target 199 gun, _ := NewHTTP2Gun(conf, log) 200 var results netsample.TestAggregator 201 _ = gun.Bind(&results, testDeps()) 202 gun.Shoot(newAmmoURL(t, "/")) 203 require.Equal(t, results.Samples[0].ProtoCode(), http.StatusOK) 204 }) 205 206 t.Run("HTTP/1.1 panic", func(t *testing.T) { 207 server := httptest.NewTLSServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 208 zap.S().Info("Served") 209 })) 210 defer server.Close() 211 log := zap.NewNop() 212 conf := DefaultHTTP2GunConfig() 213 conf.Target = server.Listener.Addr().String() 214 conf.TargetResolved = conf.Target 215 gun, _ := NewHTTP2Gun(conf, log) 216 var results netsample.TestAggregator 217 _ = gun.Bind(&results, testDeps()) 218 var r interface{} 219 func() { 220 defer func() { 221 r = recover() 222 }() 223 gun.Shoot(newAmmoURL(t, "/")) 224 }() 225 require.NotNil(t, r) 226 require.Contains(t, r, notHTTP2PanicMsg) 227 }) 228 229 t.Run("no SSL construction fails", func(t *testing.T) { 230 server := httptest.NewTLSServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 231 zap.S().Info("Served") 232 })) 233 defer server.Close() 234 log := zap.NewNop() 235 conf := DefaultHTTP2GunConfig() 236 conf.Target = server.Listener.Addr().String() 237 conf.SSL = false 238 conf.TargetResolved = conf.Target 239 _, err := NewHTTP2Gun(conf, log) 240 require.Error(t, err) 241 }) 242 } 243 244 func newAmmoURL(t *testing.T, url string) Ammo { 245 req, err := http.NewRequest("GET", url, nil) 246 require.NoError(t, err) 247 return newAmmoReq(t, req) 248 } 249 250 func newAmmoReq(t *testing.T, req *http.Request) Ammo { 251 ammo := ammomock.NewAmmo(t) 252 ammo.On("IsInvalid").Return(false).Once() 253 ammo.On("Request").Return(req, netsample.Acquire("REQUEST")).Once() 254 return ammo 255 } 256 257 func isHTTP2Request(req *http.Request) bool { 258 return checkHTTP2(req.TLS) == nil 259 } 260 261 func newHTTP2TestServer(handler http.Handler) *httptest.Server { 262 server := httptest.NewUnstartedServer(handler) 263 _ = http2.ConfigureServer(server.Config, nil) 264 server.TLS = server.Config.TLSConfig // StartTLS takes TLS configuration from that field. 265 server.StartTLS() 266 return server 267 }