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  }