k8s.io/client-go@v0.31.1/transport/round_trippers.go (about) 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 }