...

Source file src/k8s.io/client-go/transport/round_trippers.go

Documentation: k8s.io/client-go/transport

     1  /*
     2  Copyright 2015 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package transport
    18  
    19  import (
    20  	"crypto/tls"
    21  	"fmt"
    22  	"net/http"
    23  	"net/http/httptrace"
    24  	"strings"
    25  	"sync"
    26  	"time"
    27  
    28  	"golang.org/x/oauth2"
    29  
    30  	utilnet "k8s.io/apimachinery/pkg/util/net"
    31  	"k8s.io/klog/v2"
    32  )
    33  
    34  // HTTPWrappersForConfig wraps a round tripper with any relevant layered
    35  // behavior from the config. Exposed to allow more clients that need HTTP-like
    36  // behavior but then must hijack the underlying connection (like WebSocket or
    37  // HTTP2 clients). Pure HTTP clients should use the RoundTripper returned from
    38  // New.
    39  func HTTPWrappersForConfig(config *Config, rt http.RoundTripper) (http.RoundTripper, error) {
    40  	if config.WrapTransport != nil {
    41  		rt = config.WrapTransport(rt)
    42  	}
    43  
    44  	rt = DebugWrappers(rt)
    45  
    46  	// Set authentication wrappers
    47  	switch {
    48  	case config.HasBasicAuth() && config.HasTokenAuth():
    49  		return nil, fmt.Errorf("username/password or bearer token may be set, but not both")
    50  	case config.HasTokenAuth():
    51  		var err error
    52  		rt, err = NewBearerAuthWithRefreshRoundTripper(config.BearerToken, config.BearerTokenFile, rt)
    53  		if err != nil {
    54  			return nil, err
    55  		}
    56  	case config.HasBasicAuth():
    57  		rt = NewBasicAuthRoundTripper(config.Username, config.Password, rt)
    58  	}
    59  	if len(config.UserAgent) > 0 {
    60  		rt = NewUserAgentRoundTripper(config.UserAgent, rt)
    61  	}
    62  	if len(config.Impersonate.UserName) > 0 ||
    63  		len(config.Impersonate.UID) > 0 ||
    64  		len(config.Impersonate.Groups) > 0 ||
    65  		len(config.Impersonate.Extra) > 0 {
    66  		rt = NewImpersonatingRoundTripper(config.Impersonate, rt)
    67  	}
    68  	return rt, nil
    69  }
    70  
    71  // DebugWrappers wraps a round tripper and logs based on the current log level.
    72  func DebugWrappers(rt http.RoundTripper) http.RoundTripper {
    73  	switch {
    74  	case bool(klog.V(9).Enabled()):
    75  		rt = NewDebuggingRoundTripper(rt, DebugCurlCommand, DebugURLTiming, DebugDetailedTiming, DebugResponseHeaders)
    76  	case bool(klog.V(8).Enabled()):
    77  		rt = NewDebuggingRoundTripper(rt, DebugJustURL, DebugRequestHeaders, DebugResponseStatus, DebugResponseHeaders)
    78  	case bool(klog.V(7).Enabled()):
    79  		rt = NewDebuggingRoundTripper(rt, DebugJustURL, DebugRequestHeaders, DebugResponseStatus)
    80  	case bool(klog.V(6).Enabled()):
    81  		rt = NewDebuggingRoundTripper(rt, DebugURLTiming)
    82  	}
    83  
    84  	return rt
    85  }
    86  
    87  type authProxyRoundTripper struct {
    88  	username string
    89  	groups   []string
    90  	extra    map[string][]string
    91  
    92  	rt http.RoundTripper
    93  }
    94  
    95  var _ utilnet.RoundTripperWrapper = &authProxyRoundTripper{}
    96  
    97  // NewAuthProxyRoundTripper provides a roundtripper which will add auth proxy fields to requests for
    98  // authentication terminating proxy cases
    99  // assuming you pull the user from the context:
   100  // username is the user.Info.GetName() of the user
   101  // groups is the user.Info.GetGroups() of the user
   102  // extra is the user.Info.GetExtra() of the user
   103  // extra can contain any additional information that the authenticator
   104  // thought was interesting, for example authorization scopes.
   105  // In order to faithfully round-trip through an impersonation flow, these keys
   106  // MUST be lowercase.
   107  func NewAuthProxyRoundTripper(username string, groups []string, extra map[string][]string, rt http.RoundTripper) http.RoundTripper {
   108  	return &authProxyRoundTripper{
   109  		username: username,
   110  		groups:   groups,
   111  		extra:    extra,
   112  		rt:       rt,
   113  	}
   114  }
   115  
   116  func (rt *authProxyRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   117  	req = utilnet.CloneRequest(req)
   118  	SetAuthProxyHeaders(req, rt.username, rt.groups, rt.extra)
   119  
   120  	return rt.rt.RoundTrip(req)
   121  }
   122  
   123  // SetAuthProxyHeaders stomps the auth proxy header fields.  It mutates its argument.
   124  func SetAuthProxyHeaders(req *http.Request, username string, groups []string, extra map[string][]string) {
   125  	req.Header.Del("X-Remote-User")
   126  	req.Header.Del("X-Remote-Group")
   127  	for key := range req.Header {
   128  		if strings.HasPrefix(strings.ToLower(key), strings.ToLower("X-Remote-Extra-")) {
   129  			req.Header.Del(key)
   130  		}
   131  	}
   132  
   133  	req.Header.Set("X-Remote-User", username)
   134  	for _, group := range groups {
   135  		req.Header.Add("X-Remote-Group", group)
   136  	}
   137  	for key, values := range extra {
   138  		for _, value := range values {
   139  			req.Header.Add("X-Remote-Extra-"+headerKeyEscape(key), value)
   140  		}
   141  	}
   142  }
   143  
   144  func (rt *authProxyRoundTripper) CancelRequest(req *http.Request) {
   145  	tryCancelRequest(rt.WrappedRoundTripper(), req)
   146  }
   147  
   148  func (rt *authProxyRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
   149  
   150  type userAgentRoundTripper struct {
   151  	agent string
   152  	rt    http.RoundTripper
   153  }
   154  
   155  var _ utilnet.RoundTripperWrapper = &userAgentRoundTripper{}
   156  
   157  // NewUserAgentRoundTripper will add User-Agent header to a request unless it has already been set.
   158  func NewUserAgentRoundTripper(agent string, rt http.RoundTripper) http.RoundTripper {
   159  	return &userAgentRoundTripper{agent, rt}
   160  }
   161  
   162  func (rt *userAgentRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   163  	if len(req.Header.Get("User-Agent")) != 0 {
   164  		return rt.rt.RoundTrip(req)
   165  	}
   166  	req = utilnet.CloneRequest(req)
   167  	req.Header.Set("User-Agent", rt.agent)
   168  	return rt.rt.RoundTrip(req)
   169  }
   170  
   171  func (rt *userAgentRoundTripper) CancelRequest(req *http.Request) {
   172  	tryCancelRequest(rt.WrappedRoundTripper(), req)
   173  }
   174  
   175  func (rt *userAgentRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
   176  
   177  type basicAuthRoundTripper struct {
   178  	username string
   179  	password string `datapolicy:"password"`
   180  	rt       http.RoundTripper
   181  }
   182  
   183  var _ utilnet.RoundTripperWrapper = &basicAuthRoundTripper{}
   184  
   185  // NewBasicAuthRoundTripper will apply a BASIC auth authorization header to a
   186  // request unless it has already been set.
   187  func NewBasicAuthRoundTripper(username, password string, rt http.RoundTripper) http.RoundTripper {
   188  	return &basicAuthRoundTripper{username, password, rt}
   189  }
   190  
   191  func (rt *basicAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   192  	if len(req.Header.Get("Authorization")) != 0 {
   193  		return rt.rt.RoundTrip(req)
   194  	}
   195  	req = utilnet.CloneRequest(req)
   196  	req.SetBasicAuth(rt.username, rt.password)
   197  	return rt.rt.RoundTrip(req)
   198  }
   199  
   200  func (rt *basicAuthRoundTripper) CancelRequest(req *http.Request) {
   201  	tryCancelRequest(rt.WrappedRoundTripper(), req)
   202  }
   203  
   204  func (rt *basicAuthRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
   205  
   206  // These correspond to the headers used in pkg/apis/authentication.  We don't want the package dependency,
   207  // but you must not change the values.
   208  const (
   209  	// ImpersonateUserHeader is used to impersonate a particular user during an API server request
   210  	ImpersonateUserHeader = "Impersonate-User"
   211  
   212  	// ImpersonateUIDHeader is used to impersonate a particular UID during an API server request
   213  	ImpersonateUIDHeader = "Impersonate-Uid"
   214  
   215  	// ImpersonateGroupHeader is used to impersonate a particular group during an API server request.
   216  	// It can be repeated multiplied times for multiple groups.
   217  	ImpersonateGroupHeader = "Impersonate-Group"
   218  
   219  	// ImpersonateUserExtraHeaderPrefix is a prefix for a header used to impersonate an entry in the
   220  	// extra map[string][]string for user.Info.  The key for the `extra` map is suffix.
   221  	// The same key can be repeated multiple times to have multiple elements in the slice under a single key.
   222  	// For instance:
   223  	// Impersonate-Extra-Foo: one
   224  	// Impersonate-Extra-Foo: two
   225  	// results in extra["Foo"] = []string{"one", "two"}
   226  	ImpersonateUserExtraHeaderPrefix = "Impersonate-Extra-"
   227  )
   228  
   229  type impersonatingRoundTripper struct {
   230  	impersonate ImpersonationConfig
   231  	delegate    http.RoundTripper
   232  }
   233  
   234  var _ utilnet.RoundTripperWrapper = &impersonatingRoundTripper{}
   235  
   236  // NewImpersonatingRoundTripper will add an Act-As header to a request unless it has already been set.
   237  func NewImpersonatingRoundTripper(impersonate ImpersonationConfig, delegate http.RoundTripper) http.RoundTripper {
   238  	return &impersonatingRoundTripper{impersonate, delegate}
   239  }
   240  
   241  func (rt *impersonatingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   242  	// use the user header as marker for the rest.
   243  	if len(req.Header.Get(ImpersonateUserHeader)) != 0 {
   244  		return rt.delegate.RoundTrip(req)
   245  	}
   246  	req = utilnet.CloneRequest(req)
   247  	req.Header.Set(ImpersonateUserHeader, rt.impersonate.UserName)
   248  	if rt.impersonate.UID != "" {
   249  		req.Header.Set(ImpersonateUIDHeader, rt.impersonate.UID)
   250  	}
   251  	for _, group := range rt.impersonate.Groups {
   252  		req.Header.Add(ImpersonateGroupHeader, group)
   253  	}
   254  	for k, vv := range rt.impersonate.Extra {
   255  		for _, v := range vv {
   256  			req.Header.Add(ImpersonateUserExtraHeaderPrefix+headerKeyEscape(k), v)
   257  		}
   258  	}
   259  
   260  	return rt.delegate.RoundTrip(req)
   261  }
   262  
   263  func (rt *impersonatingRoundTripper) CancelRequest(req *http.Request) {
   264  	tryCancelRequest(rt.WrappedRoundTripper(), req)
   265  }
   266  
   267  func (rt *impersonatingRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.delegate }
   268  
   269  type bearerAuthRoundTripper struct {
   270  	bearer string
   271  	source oauth2.TokenSource
   272  	rt     http.RoundTripper
   273  }
   274  
   275  var _ utilnet.RoundTripperWrapper = &bearerAuthRoundTripper{}
   276  
   277  // NewBearerAuthRoundTripper adds the provided bearer token to a request
   278  // unless the authorization header has already been set.
   279  func NewBearerAuthRoundTripper(bearer string, rt http.RoundTripper) http.RoundTripper {
   280  	return &bearerAuthRoundTripper{bearer, nil, rt}
   281  }
   282  
   283  // NewBearerAuthWithRefreshRoundTripper adds the provided bearer token to a request
   284  // unless the authorization header has already been set.
   285  // If tokenFile is non-empty, it is periodically read,
   286  // and the last successfully read content is used as the bearer token.
   287  // If tokenFile is non-empty and bearer is empty, the tokenFile is read
   288  // immediately to populate the initial bearer token.
   289  func NewBearerAuthWithRefreshRoundTripper(bearer string, tokenFile string, rt http.RoundTripper) (http.RoundTripper, error) {
   290  	if len(tokenFile) == 0 {
   291  		return &bearerAuthRoundTripper{bearer, nil, rt}, nil
   292  	}
   293  	source := NewCachedFileTokenSource(tokenFile)
   294  	if len(bearer) == 0 {
   295  		token, err := source.Token()
   296  		if err != nil {
   297  			return nil, err
   298  		}
   299  		bearer = token.AccessToken
   300  	}
   301  	return &bearerAuthRoundTripper{bearer, source, rt}, nil
   302  }
   303  
   304  func (rt *bearerAuthRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   305  	if len(req.Header.Get("Authorization")) != 0 {
   306  		return rt.rt.RoundTrip(req)
   307  	}
   308  
   309  	req = utilnet.CloneRequest(req)
   310  	token := rt.bearer
   311  	if rt.source != nil {
   312  		if refreshedToken, err := rt.source.Token(); err == nil {
   313  			token = refreshedToken.AccessToken
   314  		}
   315  	}
   316  	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
   317  	return rt.rt.RoundTrip(req)
   318  }
   319  
   320  func (rt *bearerAuthRoundTripper) CancelRequest(req *http.Request) {
   321  	tryCancelRequest(rt.WrappedRoundTripper(), req)
   322  }
   323  
   324  func (rt *bearerAuthRoundTripper) WrappedRoundTripper() http.RoundTripper { return rt.rt }
   325  
   326  // requestInfo keeps track of information about a request/response combination
   327  type requestInfo struct {
   328  	RequestHeaders http.Header `datapolicy:"token"`
   329  	RequestVerb    string
   330  	RequestURL     string
   331  
   332  	ResponseStatus  string
   333  	ResponseHeaders http.Header
   334  	ResponseErr     error
   335  
   336  	muTrace          sync.Mutex // Protect trace fields
   337  	DNSLookup        time.Duration
   338  	Dialing          time.Duration
   339  	GetConnection    time.Duration
   340  	TLSHandshake     time.Duration
   341  	ServerProcessing time.Duration
   342  	ConnectionReused bool
   343  
   344  	Duration time.Duration
   345  }
   346  
   347  // newRequestInfo creates a new RequestInfo based on an http request
   348  func newRequestInfo(req *http.Request) *requestInfo {
   349  	return &requestInfo{
   350  		RequestURL:     req.URL.String(),
   351  		RequestVerb:    req.Method,
   352  		RequestHeaders: req.Header,
   353  	}
   354  }
   355  
   356  // complete adds information about the response to the requestInfo
   357  func (r *requestInfo) complete(response *http.Response, err error) {
   358  	if err != nil {
   359  		r.ResponseErr = err
   360  		return
   361  	}
   362  	r.ResponseStatus = response.Status
   363  	r.ResponseHeaders = response.Header
   364  }
   365  
   366  // toCurl returns a string that can be run as a command in a terminal (minus the body)
   367  func (r *requestInfo) toCurl() string {
   368  	headers := ""
   369  	for key, values := range r.RequestHeaders {
   370  		for _, value := range values {
   371  			value = maskValue(key, value)
   372  			headers += fmt.Sprintf(` -H %q`, fmt.Sprintf("%s: %s", key, value))
   373  		}
   374  	}
   375  
   376  	return fmt.Sprintf("curl -v -X%s %s '%s'", r.RequestVerb, headers, r.RequestURL)
   377  }
   378  
   379  // debuggingRoundTripper will display information about the requests passing
   380  // through it based on what is configured
   381  type debuggingRoundTripper struct {
   382  	delegatedRoundTripper http.RoundTripper
   383  	levels                map[DebugLevel]bool
   384  }
   385  
   386  var _ utilnet.RoundTripperWrapper = &debuggingRoundTripper{}
   387  
   388  // DebugLevel is used to enable debugging of certain
   389  // HTTP requests and responses fields via the debuggingRoundTripper.
   390  type DebugLevel int
   391  
   392  const (
   393  	// DebugJustURL will add to the debug output HTTP requests method and url.
   394  	DebugJustURL DebugLevel = iota
   395  	// DebugURLTiming will add to the debug output the duration of HTTP requests.
   396  	DebugURLTiming
   397  	// DebugCurlCommand will add to the debug output the curl command equivalent to the
   398  	// HTTP request.
   399  	DebugCurlCommand
   400  	// DebugRequestHeaders will add to the debug output the HTTP requests headers.
   401  	DebugRequestHeaders
   402  	// DebugResponseStatus will add to the debug output the HTTP response status.
   403  	DebugResponseStatus
   404  	// DebugResponseHeaders will add to the debug output the HTTP response headers.
   405  	DebugResponseHeaders
   406  	// DebugDetailedTiming will add to the debug output the duration of the HTTP requests events.
   407  	DebugDetailedTiming
   408  )
   409  
   410  // NewDebuggingRoundTripper allows to display in the logs output debug information
   411  // on the API requests performed by the client.
   412  func NewDebuggingRoundTripper(rt http.RoundTripper, levels ...DebugLevel) http.RoundTripper {
   413  	drt := &debuggingRoundTripper{
   414  		delegatedRoundTripper: rt,
   415  		levels:                make(map[DebugLevel]bool, len(levels)),
   416  	}
   417  	for _, v := range levels {
   418  		drt.levels[v] = true
   419  	}
   420  	return drt
   421  }
   422  
   423  func (rt *debuggingRoundTripper) CancelRequest(req *http.Request) {
   424  	tryCancelRequest(rt.WrappedRoundTripper(), req)
   425  }
   426  
   427  var knownAuthTypes = map[string]bool{
   428  	"bearer":    true,
   429  	"basic":     true,
   430  	"negotiate": true,
   431  }
   432  
   433  // maskValue masks credential content from authorization headers
   434  // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
   435  func maskValue(key string, value string) string {
   436  	if !strings.EqualFold(key, "Authorization") {
   437  		return value
   438  	}
   439  	if len(value) == 0 {
   440  		return ""
   441  	}
   442  	var authType string
   443  	if i := strings.Index(value, " "); i > 0 {
   444  		authType = value[0:i]
   445  	} else {
   446  		authType = value
   447  	}
   448  	if !knownAuthTypes[strings.ToLower(authType)] {
   449  		return "<masked>"
   450  	}
   451  	if len(value) > len(authType)+1 {
   452  		value = authType + " <masked>"
   453  	} else {
   454  		value = authType
   455  	}
   456  	return value
   457  }
   458  
   459  func (rt *debuggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
   460  	reqInfo := newRequestInfo(req)
   461  
   462  	if rt.levels[DebugJustURL] {
   463  		klog.Infof("%s %s", reqInfo.RequestVerb, reqInfo.RequestURL)
   464  	}
   465  	if rt.levels[DebugCurlCommand] {
   466  		klog.Infof("%s", reqInfo.toCurl())
   467  	}
   468  	if rt.levels[DebugRequestHeaders] {
   469  		klog.Info("Request Headers:")
   470  		for key, values := range reqInfo.RequestHeaders {
   471  			for _, value := range values {
   472  				value = maskValue(key, value)
   473  				klog.Infof("    %s: %s", key, value)
   474  			}
   475  		}
   476  	}
   477  
   478  	startTime := time.Now()
   479  
   480  	if rt.levels[DebugDetailedTiming] {
   481  		var getConn, dnsStart, dialStart, tlsStart, serverStart time.Time
   482  		var host string
   483  		trace := &httptrace.ClientTrace{
   484  			// DNS
   485  			DNSStart: func(info httptrace.DNSStartInfo) {
   486  				reqInfo.muTrace.Lock()
   487  				defer reqInfo.muTrace.Unlock()
   488  				dnsStart = time.Now()
   489  				host = info.Host
   490  			},
   491  			DNSDone: func(info httptrace.DNSDoneInfo) {
   492  				reqInfo.muTrace.Lock()
   493  				defer reqInfo.muTrace.Unlock()
   494  				reqInfo.DNSLookup = time.Since(dnsStart)
   495  				klog.Infof("HTTP Trace: DNS Lookup for %s resolved to %v", host, info.Addrs)
   496  			},
   497  			// Dial
   498  			ConnectStart: func(network, addr string) {
   499  				reqInfo.muTrace.Lock()
   500  				defer reqInfo.muTrace.Unlock()
   501  				dialStart = time.Now()
   502  			},
   503  			ConnectDone: func(network, addr string, err error) {
   504  				reqInfo.muTrace.Lock()
   505  				defer reqInfo.muTrace.Unlock()
   506  				reqInfo.Dialing = time.Since(dialStart)
   507  				if err != nil {
   508  					klog.Infof("HTTP Trace: Dial to %s:%s failed: %v", network, addr, err)
   509  				} else {
   510  					klog.Infof("HTTP Trace: Dial to %s:%s succeed", network, addr)
   511  				}
   512  			},
   513  			// TLS
   514  			TLSHandshakeStart: func() {
   515  				tlsStart = time.Now()
   516  			},
   517  			TLSHandshakeDone: func(_ tls.ConnectionState, _ error) {
   518  				reqInfo.muTrace.Lock()
   519  				defer reqInfo.muTrace.Unlock()
   520  				reqInfo.TLSHandshake = time.Since(tlsStart)
   521  			},
   522  			// Connection (it can be DNS + Dial or just the time to get one from the connection pool)
   523  			GetConn: func(hostPort string) {
   524  				getConn = time.Now()
   525  			},
   526  			GotConn: func(info httptrace.GotConnInfo) {
   527  				reqInfo.muTrace.Lock()
   528  				defer reqInfo.muTrace.Unlock()
   529  				reqInfo.GetConnection = time.Since(getConn)
   530  				reqInfo.ConnectionReused = info.Reused
   531  			},
   532  			// Server Processing (time since we wrote the request until first byte is received)
   533  			WroteRequest: func(info httptrace.WroteRequestInfo) {
   534  				reqInfo.muTrace.Lock()
   535  				defer reqInfo.muTrace.Unlock()
   536  				serverStart = time.Now()
   537  			},
   538  			GotFirstResponseByte: func() {
   539  				reqInfo.muTrace.Lock()
   540  				defer reqInfo.muTrace.Unlock()
   541  				reqInfo.ServerProcessing = time.Since(serverStart)
   542  			},
   543  		}
   544  		req = req.WithContext(httptrace.WithClientTrace(req.Context(), trace))
   545  	}
   546  
   547  	response, err := rt.delegatedRoundTripper.RoundTrip(req)
   548  	reqInfo.Duration = time.Since(startTime)
   549  
   550  	reqInfo.complete(response, err)
   551  
   552  	if rt.levels[DebugURLTiming] {
   553  		klog.Infof("%s %s %s in %d milliseconds", reqInfo.RequestVerb, reqInfo.RequestURL, reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
   554  	}
   555  	if rt.levels[DebugDetailedTiming] {
   556  		stats := ""
   557  		if !reqInfo.ConnectionReused {
   558  			stats += fmt.Sprintf(`DNSLookup %d ms Dial %d ms TLSHandshake %d ms`,
   559  				reqInfo.DNSLookup.Nanoseconds()/int64(time.Millisecond),
   560  				reqInfo.Dialing.Nanoseconds()/int64(time.Millisecond),
   561  				reqInfo.TLSHandshake.Nanoseconds()/int64(time.Millisecond),
   562  			)
   563  		} else {
   564  			stats += fmt.Sprintf(`GetConnection %d ms`, reqInfo.GetConnection.Nanoseconds()/int64(time.Millisecond))
   565  		}
   566  		if reqInfo.ServerProcessing != 0 {
   567  			stats += fmt.Sprintf(` ServerProcessing %d ms`, reqInfo.ServerProcessing.Nanoseconds()/int64(time.Millisecond))
   568  		}
   569  		stats += fmt.Sprintf(` Duration %d ms`, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
   570  		klog.Infof("HTTP Statistics: %s", stats)
   571  	}
   572  
   573  	if rt.levels[DebugResponseStatus] {
   574  		klog.Infof("Response Status: %s in %d milliseconds", reqInfo.ResponseStatus, reqInfo.Duration.Nanoseconds()/int64(time.Millisecond))
   575  	}
   576  	if rt.levels[DebugResponseHeaders] {
   577  		klog.Info("Response Headers:")
   578  		for key, values := range reqInfo.ResponseHeaders {
   579  			for _, value := range values {
   580  				klog.Infof("    %s: %s", key, value)
   581  			}
   582  		}
   583  	}
   584  
   585  	return response, err
   586  }
   587  
   588  func (rt *debuggingRoundTripper) WrappedRoundTripper() http.RoundTripper {
   589  	return rt.delegatedRoundTripper
   590  }
   591  
   592  func legalHeaderByte(b byte) bool {
   593  	return int(b) < len(legalHeaderKeyBytes) && legalHeaderKeyBytes[b]
   594  }
   595  
   596  func shouldEscape(b byte) bool {
   597  	// url.PathUnescape() returns an error if any '%' is not followed by two
   598  	// hexadecimal digits, so we'll intentionally encode it.
   599  	return !legalHeaderByte(b) || b == '%'
   600  }
   601  
   602  func headerKeyEscape(key string) string {
   603  	buf := strings.Builder{}
   604  	for i := 0; i < len(key); i++ {
   605  		b := key[i]
   606  		if shouldEscape(b) {
   607  			// %-encode bytes that should be escaped:
   608  			// https://tools.ietf.org/html/rfc3986#section-2.1
   609  			fmt.Fprintf(&buf, "%%%02X", b)
   610  			continue
   611  		}
   612  		buf.WriteByte(b)
   613  	}
   614  	return buf.String()
   615  }
   616  
   617  // legalHeaderKeyBytes was copied from net/http/lex.go's isTokenTable.
   618  // See https://httpwg.github.io/specs/rfc7230.html#rule.token.separators
   619  var legalHeaderKeyBytes = [127]bool{
   620  	'%':  true,
   621  	'!':  true,
   622  	'#':  true,
   623  	'$':  true,
   624  	'&':  true,
   625  	'\'': true,
   626  	'*':  true,
   627  	'+':  true,
   628  	'-':  true,
   629  	'.':  true,
   630  	'0':  true,
   631  	'1':  true,
   632  	'2':  true,
   633  	'3':  true,
   634  	'4':  true,
   635  	'5':  true,
   636  	'6':  true,
   637  	'7':  true,
   638  	'8':  true,
   639  	'9':  true,
   640  	'A':  true,
   641  	'B':  true,
   642  	'C':  true,
   643  	'D':  true,
   644  	'E':  true,
   645  	'F':  true,
   646  	'G':  true,
   647  	'H':  true,
   648  	'I':  true,
   649  	'J':  true,
   650  	'K':  true,
   651  	'L':  true,
   652  	'M':  true,
   653  	'N':  true,
   654  	'O':  true,
   655  	'P':  true,
   656  	'Q':  true,
   657  	'R':  true,
   658  	'S':  true,
   659  	'T':  true,
   660  	'U':  true,
   661  	'W':  true,
   662  	'V':  true,
   663  	'X':  true,
   664  	'Y':  true,
   665  	'Z':  true,
   666  	'^':  true,
   667  	'_':  true,
   668  	'`':  true,
   669  	'a':  true,
   670  	'b':  true,
   671  	'c':  true,
   672  	'd':  true,
   673  	'e':  true,
   674  	'f':  true,
   675  	'g':  true,
   676  	'h':  true,
   677  	'i':  true,
   678  	'j':  true,
   679  	'k':  true,
   680  	'l':  true,
   681  	'm':  true,
   682  	'n':  true,
   683  	'o':  true,
   684  	'p':  true,
   685  	'q':  true,
   686  	'r':  true,
   687  	's':  true,
   688  	't':  true,
   689  	'u':  true,
   690  	'v':  true,
   691  	'w':  true,
   692  	'x':  true,
   693  	'y':  true,
   694  	'z':  true,
   695  	'|':  true,
   696  	'~':  true,
   697  }
   698  

View as plain text