istio.io/istio@v0.0.0-20240520182934-d79c90f27776/pkg/test/echo/server/endpoint/http.go (about) 1 // Copyright Istio 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 endpoint 16 17 import ( 18 "bytes" 19 "context" 20 "crypto/tls" 21 "fmt" 22 "math/rand" 23 "net" 24 "net/http" 25 "os" 26 "sort" 27 "strconv" 28 "strings" 29 "time" 30 31 "github.com/google/uuid" 32 "github.com/gorilla/websocket" 33 "golang.org/x/net/http2" 34 35 "istio.io/istio/pkg/h2c" 36 "istio.io/istio/pkg/test/echo" 37 "istio.io/istio/pkg/test/echo/common" 38 "istio.io/istio/pkg/test/util/retry" 39 ) 40 41 const ( 42 readyTimeout = 10 * time.Second 43 readyInterval = 2 * time.Second 44 ) 45 46 var webSocketUpgrader = websocket.Upgrader{ 47 CheckOrigin: func(r *http.Request) bool { 48 // allow all connections by default 49 return true 50 }, 51 } 52 53 var _ Instance = &httpInstance{} 54 55 type httpInstance struct { 56 Config 57 server *http.Server 58 } 59 60 func newHTTP(config Config) Instance { 61 return &httpInstance{ 62 Config: config, 63 } 64 } 65 66 func (s *httpInstance) GetConfig() Config { 67 return s.Config 68 } 69 70 func (s *httpInstance) Start(onReady OnReadyFunc) error { 71 h2s := &http2.Server{ 72 IdleTimeout: idleTimeout, 73 } 74 75 s.server = &http.Server{ 76 IdleTimeout: idleTimeout, 77 Handler: h2c.NewHandler(&httpHandler{ 78 Config: s.Config, 79 }, h2s), 80 } 81 82 var listener net.Listener 83 var port int 84 var err error 85 if s.isUDS() { 86 port = 0 87 listener, err = listenOnUDS(s.UDSServer) 88 } else if s.Port.TLS { 89 cert, cerr := tls.LoadX509KeyPair(s.TLSCert, s.TLSKey) 90 if cerr != nil { 91 return fmt.Errorf("could not load TLS keys: %v", cerr) 92 } 93 nextProtos := []string{"h2", "http/1.1", "http/1.0"} 94 if s.DisableALPN { 95 nextProtos = nil 96 } 97 config := &tls.Config{ 98 Certificates: []tls.Certificate{cert}, 99 NextProtos: nextProtos, 100 GetConfigForClient: func(info *tls.ClientHelloInfo) (*tls.Config, error) { 101 // There isn't a way to pass through all ALPNs presented by the client down to the 102 // HTTP server to return in the response. However, for debugging, we can at least log 103 // them at this level. 104 epLog.Infof("TLS connection with alpn: %v", info.SupportedProtos) 105 return nil, nil 106 }, 107 MinVersion: tls.VersionTLS12, 108 } 109 // Listen on the given port and update the port if it changed from what was passed in. 110 listener, port, err = listenOnAddressTLS(s.ListenerIP, s.Port.Port, config) 111 // Store the actual listening port back to the argument. 112 s.Port.Port = port 113 } else { 114 // Listen on the given port and update the port if it changed from what was passed in. 115 listener, port, err = listenOnAddress(s.ListenerIP, s.Port.Port) 116 // Store the actual listening port back to the argument. 117 s.Port.Port = port 118 } 119 120 if err != nil { 121 return err 122 } 123 124 if s.isUDS() { 125 epLog.Infof("Listening HTTP/1.1 on %v\n", s.UDSServer) 126 } else if s.Port.TLS { 127 s.server.Addr = fmt.Sprintf(":%d", port) 128 epLog.Infof("Listening HTTPS/1.1 on %v\n", port) 129 } else { 130 s.server.Addr = fmt.Sprintf(":%d", port) 131 epLog.Infof("Listening HTTP/1.1 on %v\n", port) 132 } 133 134 // Start serving HTTP traffic. 135 go func() { 136 err := s.server.Serve(listener) 137 epLog.Warnf("Port %d listener terminated with error: %v", port, err) 138 }() 139 140 // Notify the WaitGroup once the port has transitioned to ready. 141 go s.awaitReady(onReady, listener.Addr().String()) 142 143 return nil 144 } 145 146 func (s *httpInstance) isUDS() bool { 147 return s.UDSServer != "" 148 } 149 150 func (s *httpInstance) awaitReady(onReady OnReadyFunc, address string) { 151 defer onReady() 152 153 client := http.Client{} 154 var url string 155 if s.isUDS() { 156 url = "http://unix/" + s.UDSServer 157 client.Transport = &http.Transport{ 158 DialContext: func(_ context.Context, _, _ string) (net.Conn, error) { 159 return net.Dial("unix", s.UDSServer) 160 }, 161 } 162 } else if s.Port.TLS { 163 url = fmt.Sprintf("https://%s", address) 164 client.Transport = &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} // nolint: gosec // test only code 165 } else { 166 url = fmt.Sprintf("http://%s", address) 167 } 168 169 err := retry.UntilSuccess(func() error { 170 resp, err := client.Get(url) 171 if err != nil { 172 return err 173 } 174 defer resp.Body.Close() 175 176 // The handler applies server readiness when handling HTTP requests. Since the 177 // server won't become ready until all endpoints (including this one) report 178 // ready, the handler will return 503. This means that the endpoint is now ready. 179 if resp.StatusCode != http.StatusServiceUnavailable { 180 return fmt.Errorf("unexpected status code %d", resp.StatusCode) 181 } 182 183 // Server is up now, we're ready. 184 return nil 185 }, retry.Timeout(readyTimeout), retry.Delay(readyInterval)) 186 187 if err != nil { 188 epLog.Errorf("readiness failed for endpoint %s: %v", url, err) 189 } else { 190 epLog.Infof("ready for HTTP endpoint %s", url) 191 } 192 } 193 194 func (s *httpInstance) Close() error { 195 if s.server != nil { 196 return s.server.Close() 197 } 198 return nil 199 } 200 201 type httpHandler struct { 202 Config 203 } 204 205 // Imagine a pie of different flavors. 206 // The flavors are the HTTP response codes. 207 // The chance of a particular flavor is ( slices / sum of slices ). 208 type codeAndSlices struct { 209 httpResponseCode int 210 slices int 211 } 212 213 func (h *httpHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 214 id := uuid.New() 215 remoteAddr, _, err := net.SplitHostPort(r.RemoteAddr) 216 if err != nil { 217 epLog.Warnf("failed to get host from remote address: %s", err) 218 } 219 epLog.WithLabels("remoteAddr", remoteAddr, "method", r.Method, "url", r.URL, "host", r.Host, "headers", r.Header, "id", id).Infof("%v Request", r.Proto) 220 if h.Port == nil { 221 defer common.Metrics.HTTPRequests.With(common.PortLabel.Value("uds")).Increment() 222 } else { 223 defer common.Metrics.HTTPRequests.With(common.PortLabel.Value(strconv.Itoa(h.Port.Port))).Increment() 224 } 225 if !h.IsServerReady() { 226 // Handle readiness probe failure. 227 epLog.Infof("HTTP service not ready, returning 503") 228 w.WriteHeader(http.StatusServiceUnavailable) 229 return 230 } 231 232 if common.IsWebSocketRequest(r) { 233 h.webSocketEcho(w, r) 234 } else { 235 h.echo(w, r, id) 236 } 237 } 238 239 // nolint: interfacer 240 func writeError(out *bytes.Buffer, msg string) { 241 epLog.Warn(msg) 242 _, _ = out.WriteString(msg + "\n") 243 } 244 245 func (h *httpHandler) echo(w http.ResponseWriter, r *http.Request, id uuid.UUID) { 246 body := bytes.Buffer{} 247 248 if err := r.ParseForm(); err != nil { 249 writeError(&body, "ParseForm() error: "+err.Error()) 250 } 251 252 // If the request has form ?delay=[:duration] wait for duration 253 // For example, ?delay=10s will cause the response to wait 10s before responding 254 if err := delayResponse(r); err != nil { 255 writeError(&body, "error delaying response error: "+err.Error()) 256 } 257 258 // If the request has form ?headers=name:value[,name:value]* return those headers in response 259 if err := setHeaderResponseFromHeaders(r, w); err != nil { 260 writeError(&body, "response headers error: "+err.Error()) 261 } 262 263 // If the request has form ?codes=code[:chance][,code[:chance]]* return those codes, rather than 200 264 // For example, ?codes=500:1,200:1 returns 500 1/2 times and 200 1/2 times 265 // For example, ?codes=500:90,200:10 returns 500 90% of times and 200 10% of times 266 code, err := setResponseFromCodes(r, w) 267 if err != nil { 268 writeError(&body, "codes error: "+err.Error()) 269 } 270 271 h.addResponsePayload(r, &body) 272 273 w.Header().Set("Content-Type", "application/text") 274 if _, err := w.Write(body.Bytes()); err != nil { 275 epLog.Warn(err) 276 } 277 epLog.WithLabels("code", code, "headers", w.Header(), "id", id).Infof("%v Response", r.Proto) 278 } 279 280 func (h *httpHandler) webSocketEcho(w http.ResponseWriter, r *http.Request) { 281 // adapted from https://github.com/gorilla/websocket/blob/master/examples/echo/server.go 282 // First send upgrade headers 283 c, err := webSocketUpgrader.Upgrade(w, r, nil) 284 if err != nil { 285 epLog.Warn("websocket-echo upgrade failed: " + err.Error()) 286 return 287 } 288 289 defer func() { _ = c.Close() }() 290 291 // ping 292 mt, message, err := c.ReadMessage() 293 if err != nil { 294 epLog.Warn("websocket-echo read failed: " + err.Error()) 295 return 296 } 297 298 body := bytes.Buffer{} 299 h.addResponsePayload(r, &body) 300 body.Write(message) 301 302 echo.StatusCodeField.Write(&body, strconv.Itoa(http.StatusOK)) 303 304 // pong 305 err = c.WriteMessage(mt, body.Bytes()) 306 if err != nil { 307 writeError(&body, "websocket-echo write failed: "+err.Error()) 308 return 309 } 310 } 311 312 // nolint: interfacer 313 func (h *httpHandler) addResponsePayload(r *http.Request, body *bytes.Buffer) { 314 port := "" 315 if h.Port != nil { 316 port = strconv.Itoa(h.Port.Port) 317 } 318 319 echo.ServiceVersionField.Write(body, h.Version) 320 echo.ServicePortField.Write(body, port) 321 echo.HostField.Write(body, r.Host) 322 // Use raw path, we don't want golang normalizing anything since we use this for testing purposes 323 echo.URLField.Write(body, r.RequestURI) 324 echo.ClusterField.WriteNonEmpty(body, h.Cluster) 325 echo.IstioVersionField.WriteNonEmpty(body, h.IstioVersion) 326 echo.NamespaceField.WriteNonEmpty(body, h.Namespace) 327 328 echo.MethodField.Write(body, r.Method) 329 echo.ProtocolField.Write(body, r.Proto) 330 ip, _, _ := net.SplitHostPort(r.RemoteAddr) 331 echo.IPField.Write(body, ip) 332 333 // Note: since this is the NegotiatedProtocol, it will be set to empty if the client sends an ALPN 334 // not supported by the server (ie one of h2,http/1.1,http/1.0) 335 var alpn string 336 if r.TLS != nil { 337 alpn = r.TLS.NegotiatedProtocol 338 } 339 echo.AlpnField.WriteNonEmpty(body, alpn) 340 341 var keys []string 342 for k := range r.Header { 343 keys = append(keys, k) 344 } 345 sort.Strings(keys) 346 for _, key := range keys { 347 values := r.Header[key] 348 for _, value := range values { 349 echo.RequestHeaderField.WriteKeyValue(body, key, value) 350 } 351 } 352 353 if hostname, err := os.Hostname(); err == nil { 354 echo.HostnameField.Write(body, hostname) 355 } 356 } 357 358 func delayResponse(request *http.Request) error { 359 d := request.FormValue("delay") 360 if len(d) == 0 { 361 return nil 362 } 363 364 t, err := time.ParseDuration(d) 365 if err != nil { 366 return err 367 } 368 time.Sleep(t) 369 return nil 370 } 371 372 func setHeaderResponseFromHeaders(request *http.Request, response http.ResponseWriter) error { 373 s := request.FormValue("headers") 374 if len(s) == 0 { 375 return nil 376 } 377 responseHeaders := strings.Split(s, ",") 378 for _, responseHeader := range responseHeaders { 379 parts := strings.Split(responseHeader, ":") 380 // require name:value format 381 if len(parts) != 2 { 382 return fmt.Errorf("invalid %q (want name:value)", responseHeader) 383 } 384 name := parts[0] 385 value := parts[1] 386 // Avoid using .Set() to allow users to pass non-canonical forms 387 response.Header()[name] = []string{value} 388 } 389 return nil 390 } 391 392 func setResponseFromCodes(request *http.Request, response http.ResponseWriter) (int, error) { 393 responseCodes := request.FormValue("codes") 394 395 codes, err := validateCodes(responseCodes) 396 if err != nil { 397 return 0, err 398 } 399 400 // Choose a random "slice" from a pie 401 totalSlices := 0 402 for _, flavor := range codes { 403 totalSlices += flavor.slices 404 } 405 // nolint: gosec 406 // Test only code 407 slice := rand.Intn(totalSlices) 408 409 // What flavor is that slice? 410 responseCode := codes[len(codes)-1].httpResponseCode // Assume the last slice 411 position := 0 412 for n, flavor := range codes { 413 if position > slice { 414 responseCode = codes[n-1].httpResponseCode // No, use an earlier slice 415 break 416 } 417 position += flavor.slices 418 } 419 420 response.WriteHeader(responseCode) 421 return responseCode, nil 422 } 423 424 // codes must be comma-separated HTTP response code, colon, positive integer 425 func validateCodes(codestrings string) ([]codeAndSlices, error) { 426 if codestrings == "" { 427 // Consider no codes to be "200:1" -- return HTTP 200 100% of the time. 428 codestrings = strconv.Itoa(http.StatusOK) + ":1" 429 } 430 431 aCodestrings := strings.Split(codestrings, ",") 432 codes := make([]codeAndSlices, len(aCodestrings)) 433 434 for i, codestring := range aCodestrings { 435 codeAndSlice, err := validateCodeAndSlices(codestring) 436 if err != nil { 437 return []codeAndSlices{{http.StatusBadRequest, 1}}, err 438 } 439 codes[i] = codeAndSlice 440 } 441 442 return codes, nil 443 } 444 445 // code must be HTTP response code 446 func validateCodeAndSlices(codecount string) (codeAndSlices, error) { 447 flavor := strings.Split(codecount, ":") 448 449 // Demand code or code:number 450 if len(flavor) == 0 || len(flavor) > 2 { 451 return codeAndSlices{http.StatusBadRequest, 9999}, 452 fmt.Errorf("invalid %q (want code or code:count)", codecount) 453 } 454 455 n, err := strconv.Atoi(flavor[0]) 456 if err != nil { 457 return codeAndSlices{http.StatusBadRequest, 9999}, err 458 } 459 460 if n < http.StatusOK || n >= 600 { 461 return codeAndSlices{http.StatusBadRequest, 9999}, 462 fmt.Errorf("invalid HTTP response code %v", n) 463 } 464 465 count := 1 466 if len(flavor) > 1 { 467 count, err = strconv.Atoi(flavor[1]) 468 if err != nil { 469 return codeAndSlices{http.StatusBadRequest, 9999}, err 470 } 471 if count < 0 { 472 return codeAndSlices{http.StatusBadRequest, 9999}, 473 fmt.Errorf("invalid count %v", count) 474 } 475 } 476 477 return codeAndSlices{n, count}, nil 478 }