github.com/avenga/couper@v1.12.2/handler/transport/backend.go (about) 1 package transport 2 3 import ( 4 "compress/gzip" 5 "context" 6 "encoding/base64" 7 "io" 8 "net/http" 9 "net/http/httptrace" 10 "net/url" 11 "strings" 12 "sync" 13 "time" 14 15 "github.com/hashicorp/hcl/v2" 16 "github.com/hashicorp/hcl/v2/hclsyntax" 17 "github.com/sirupsen/logrus" 18 "github.com/zclconf/go-cty/cty" 19 "go.opentelemetry.io/otel/attribute" 20 "go.opentelemetry.io/otel/metric/instrument" 21 "go.opentelemetry.io/otel/metric/unit" 22 semconv "go.opentelemetry.io/otel/semconv/v1.12.0" 23 "go.opentelemetry.io/otel/trace" 24 25 "github.com/avenga/couper/config" 26 hclbody "github.com/avenga/couper/config/body" 27 "github.com/avenga/couper/config/request" 28 "github.com/avenga/couper/errors" 29 "github.com/avenga/couper/eval" 30 "github.com/avenga/couper/eval/buffer" 31 "github.com/avenga/couper/eval/variables" 32 "github.com/avenga/couper/handler/ratelimit" 33 "github.com/avenga/couper/handler/validation" 34 "github.com/avenga/couper/internal/seetie" 35 "github.com/avenga/couper/logging" 36 "github.com/avenga/couper/server/writer" 37 "github.com/avenga/couper/telemetry" 38 "github.com/avenga/couper/telemetry/instrumentation" 39 "github.com/avenga/couper/telemetry/provider" 40 "github.com/avenga/couper/utils" 41 ) 42 43 var ( 44 _ http.RoundTripper = &Backend{} 45 _ ProbeStateChange = &Backend{} 46 _ seetie.Object = &Backend{} 47 ) 48 49 type Backend struct { 50 context *hclsyntax.Body 51 healthInfo *HealthInfo 52 healthyMu sync.RWMutex 53 logEntry *logrus.Entry 54 name string 55 openAPIValidator *validation.OpenAPI 56 requestAuthorizer []RequestAuthorizer 57 transport http.RoundTripper 58 transportConf *Config 59 transportConfResult Config 60 transportOnce sync.Once 61 upstreamLog *logging.UpstreamLog 62 } 63 64 // NewBackend creates a new <*Backend> object by the given <*Config>. 65 func NewBackend(ctx *hclsyntax.Body, tc *Config, opts *BackendOptions, log *logrus.Entry) http.RoundTripper { 66 var ( 67 healthCheck *config.HealthCheck 68 openAPI *validation.OpenAPI 69 requestAuthorizer []RequestAuthorizer 70 ) 71 72 if opts != nil { 73 healthCheck = opts.HealthCheck 74 openAPI = validation.NewOpenAPI(opts.OpenAPI) 75 requestAuthorizer = opts.RequestAuthz 76 } 77 78 backend := &Backend{ 79 context: ctx, 80 healthInfo: &HealthInfo{Healthy: true, State: StateOk.String()}, 81 logEntry: log.WithField("backend", tc.BackendName), 82 name: tc.BackendName, 83 openAPIValidator: openAPI, 84 requestAuthorizer: requestAuthorizer, 85 transportConf: tc, 86 } 87 88 backend.upstreamLog = logging.NewUpstreamLog(backend.logEntry, backend, tc.NoProxyFromEnv) 89 90 distinct := !strings.HasPrefix(tc.BackendName, "anonymous_") 91 if distinct && healthCheck != nil { 92 NewProbe(backend.logEntry, tc, healthCheck, backend) 93 } 94 95 return backend.upstreamLog 96 } 97 98 // initOnce ensures synced transport configuration. First request will setup the rate limits, origin, hostname and tls. 99 func (b *Backend) initOnce(conf *Config) { 100 if len(b.transportConf.RateLimits) > 0 { 101 b.transport = ratelimit.NewLimiter(NewTransport(conf, b.logEntry), b.transportConf.RateLimits) 102 } else { 103 b.transport = NewTransport(conf, b.logEntry) 104 } 105 106 b.healthyMu.Lock() 107 b.transportConfResult = *conf 108 healthy := b.healthInfo.Healthy 109 healthState := b.healthInfo.State 110 b.healthyMu.Unlock() 111 112 // race condition, update possible healthy backend with current origin and hostname 113 b.OnProbeChange(&HealthInfo{Healthy: healthy, State: healthState}) 114 } 115 116 // RoundTrip implements the <http.RoundTripper> interface. 117 func (b *Backend) RoundTrip(req *http.Request) (*http.Response, error) { 118 ctxBody, _ := req.Context().Value(request.BackendParams).(*hclsyntax.Body) 119 if ctxBody == nil { 120 ctxBody = b.context 121 } else { 122 ctxBody = hclbody.MergeBodies(ctxBody, b.context, false) 123 } 124 125 outreq := req.WithContext(context.WithValue(req.Context(), request.BackendName, b.name)) 126 127 // originalReq for token-request retry purposes 128 originalReq, err := b.withTokenRequest(outreq) 129 if err != nil { 130 return nil, errors.BetaBackendTokenRequest.Label(b.name).With(err) 131 } 132 133 var backendVal cty.Value 134 hclCtx := eval.ContextFromRequest(outreq).HCLContextSync() 135 if v, ok := hclCtx.Variables[variables.Backends]; ok { 136 if m, exist := v.AsValueMap()[b.name]; exist { 137 hclCtx.Variables[variables.Backend] = m 138 backendVal = m 139 } 140 } 141 142 if err = b.isUnhealthy(hclCtx, ctxBody); err != nil { 143 return &http.Response{ 144 Request: req, // provide outreq (variable) on error cases 145 }, err 146 } 147 148 // Execute before <b.evalTransport()> due to right 149 // handling of query-params in the URL attribute. 150 if err = eval.ApplyRequestContext(hclCtx, ctxBody, outreq); err != nil { 151 return nil, err 152 } 153 154 // TODO: split timing eval 155 tc, err := b.evalTransport(hclCtx, ctxBody, outreq) 156 if err != nil { 157 return nil, err 158 } 159 160 // first traffic pins the origin settings to transportConfResult 161 b.transportOnce.Do(func() { 162 b.initOnce(tc) 163 }) 164 165 // use result and apply context timings 166 b.healthyMu.RLock() 167 tconf := b.transportConfResult 168 b.healthyMu.RUnlock() 169 tconf.ConnectTimeout = tc.ConnectTimeout 170 tconf.TTFBTimeout = tc.TTFBTimeout 171 tconf.Timeout = tc.Timeout 172 173 deadlineErr := b.withTimeout(outreq, &tconf) 174 175 outreq.URL.Host = tconf.Origin 176 outreq.URL.Scheme = tconf.Scheme 177 outreq.Host = tconf.Hostname 178 179 // handler.Proxy marks proxy round-trips since we should not handle headers twice. 180 _, isProxyReq := outreq.Context().Value(request.RoundTripProxy).(bool) 181 182 if !isProxyReq { 183 RemoveConnectionHeaders(outreq.Header) 184 RemoveHopHeaders(outreq.Header) 185 } 186 187 writer.ModifyAcceptEncoding(outreq.Header) 188 189 if xff, ok := outreq.Context().Value(request.XFF).(string); ok { 190 if xff != "" { 191 outreq.Header.Set("X-Forwarded-For", xff) 192 } else { 193 outreq.Header.Del("X-Forwarded-For") 194 } 195 } 196 197 b.withBasicAuth(outreq, hclCtx, ctxBody) 198 if err = b.withPathPrefix(outreq, hclCtx, ctxBody); err != nil { 199 return nil, err 200 } 201 202 setUserAgent(outreq) 203 outreq.Close = false 204 205 if _, ok := req.Context().Value(request.WebsocketsAllowed).(bool); !ok { 206 outreq.Header.Del("Connection") 207 outreq.Header.Del("Upgrade") 208 } 209 210 var beresp *http.Response 211 if b.openAPIValidator != nil { 212 beresp, err = b.openAPIValidate(outreq, &tconf, deadlineErr) 213 } else { 214 beresp, err = b.innerRoundTrip(outreq, &tconf, deadlineErr) 215 } 216 217 if err != nil { 218 if beresp == nil { 219 beresp = &http.Response{ 220 Request: outreq, 221 } // provide outreq (variable) on error cases 222 } 223 if varSync, ok := outreq.Context().Value(request.ContextVariablesSynced).(*eval.SyncedVariables); ok { 224 varSync.SetResp(beresp) 225 } 226 return beresp, err 227 } 228 229 if retry, rerr := b.withRetryTokenRequest(outreq, beresp); rerr != nil { 230 return beresp, errors.BetaBackendTokenRequest.Label(b.name).With(rerr) 231 } else if retry { 232 return b.RoundTrip(originalReq) 233 } 234 235 if !eval.IsUpgradeResponse(outreq, beresp) { 236 beresp.Body = logging.NewBytesCountReader(beresp) 237 if err = setGzipReader(beresp); err != nil { 238 b.upstreamLog.LogEntry().WithContext(outreq.Context()).WithError(err).Error() 239 } 240 } 241 242 if !isProxyReq { 243 RemoveConnectionHeaders(beresp.Header) 244 RemoveHopHeaders(beresp.Header) 245 } 246 247 // Backend response context creates the beresp variables in first place and applies this context 248 // to the current beresp obj. Downstream response context evals reading their beresp variable values 249 // from this result. 250 evalCtx := eval.ContextFromRequest(outreq) 251 var bereqV, berespV cty.Value 252 var reqName string 253 evalCtx, reqName, bereqV, berespV = evalCtx.WithBeresp(beresp, backendVal) 254 255 clfValue, err := eval.EvalCustomLogFields(evalCtx.HCLContext(), ctxBody) 256 if err != nil { 257 logError, _ := outreq.Context().Value(request.LogCustomUpstreamError).(*error) 258 *logError = err 259 } else if clfValue != cty.NilVal { 260 logValue, _ := outreq.Context().Value(request.LogCustomUpstreamValue).(*cty.Value) 261 *logValue = clfValue 262 } 263 264 err = eval.ApplyResponseContext(evalCtx.HCLContext(), ctxBody, beresp) 265 266 if varSync, ok := outreq.Context().Value(request.ContextVariablesSynced).(*eval.SyncedVariables); ok { 267 varSync.Set(outreq.Context(), reqName, bereqV, berespV) 268 } 269 270 return beresp, err 271 } 272 273 func (b *Backend) openAPIValidate(req *http.Request, tc *Config, deadlineErr <-chan error) (*http.Response, error) { 274 requestValidationInput, err := b.openAPIValidator.ValidateRequest(req) 275 if err != nil { 276 return nil, errors.BackendOpenapiValidation.Label(b.name).With(err) 277 } 278 279 beresp, err := b.innerRoundTrip(req, tc, deadlineErr) 280 if err != nil { 281 return nil, err 282 } 283 284 if err = b.openAPIValidator.ValidateResponse(beresp, requestValidationInput); err != nil { 285 return beresp, errors.BackendOpenapiValidation.Label(b.name).With(err).Status(http.StatusBadGateway) 286 } 287 288 return beresp, nil 289 } 290 291 func (b *Backend) innerRoundTrip(req *http.Request, tc *Config, deadlineErr <-chan error) (*http.Response, error) { 292 span := trace.SpanFromContext(req.Context()) 293 span.SetAttributes(telemetry.KeyOrigin.String(tc.Origin)) 294 span.SetAttributes(semconv.HTTPClientAttributesFromHTTPRequest(req)...) 295 296 spanMsg := "backend" 297 if b.name != "" { 298 spanMsg += "." + b.name 299 } 300 301 meter := provider.Meter(instrumentation.BackendInstrumentationName) 302 counter, _ := meter.SyncInt64().Counter( 303 instrumentation.BackendRequest, 304 instrument.WithDescription(string(unit.Dimensionless)), 305 ) 306 duration, _ := meter.SyncFloat64().Histogram( 307 instrumentation.BackendRequestDuration, 308 instrument.WithDescription(string(unit.Dimensionless)), 309 ) 310 311 attrs := []attribute.KeyValue{ 312 attribute.String("backend_name", tc.BackendName), 313 attribute.String("hostname", tc.Hostname), 314 attribute.String("method", req.Method), 315 attribute.String("origin", tc.Origin), 316 } 317 318 start := time.Now() 319 span.AddEvent(spanMsg + ".request") 320 beresp, err := b.transport.RoundTrip(req) 321 span.AddEvent(spanMsg + ".response") 322 endSeconds := time.Since(start).Seconds() 323 324 statusKey := attribute.Key("code") 325 if beresp != nil { 326 attrs = append(attrs, statusKey.Int(beresp.StatusCode)) 327 } 328 329 defer counter.Add(req.Context(), 1, attrs...) 330 defer duration.Record(req.Context(), endSeconds, attrs...) 331 332 if err != nil { 333 select { 334 case derr := <-deadlineErr: 335 if derr != nil { 336 return nil, derr 337 } 338 default: 339 if _, ok := err.(*errors.Error); ok { 340 return nil, err 341 } 342 343 return nil, errors.Backend.Label(b.name).With(err) 344 } 345 } 346 347 return beresp, nil 348 } 349 350 func (b *Backend) withTokenRequest(req *http.Request) (*http.Request, error) { 351 if b.requestAuthorizer == nil { 352 return nil, nil 353 } 354 355 // Reset for upstream transport; prevent mixing values. 356 // requestAuthorizer will have their own backend configuration. 357 ctx := context.WithValue(req.Context(), request.BackendParams, nil) 358 359 var cancel context.CancelFunc 360 ctx, cancel = context.WithCancel(ctx) 361 defer cancel() 362 363 originalReq := req.Clone(req.Context()) 364 365 // WithContext() instead of Clone() due to header-map modification. 366 req = req.WithContext(ctx) 367 368 errorsCh := make(chan error, len(b.requestAuthorizer)) 369 for _, authorizer := range b.requestAuthorizer { 370 err := authorizer.GetToken(req) 371 if err != nil { 372 return originalReq, err 373 } 374 375 go func(ra RequestAuthorizer, r *http.Request) { 376 errorsCh <- ra.GetToken(r) 377 }(authorizer, req) 378 } 379 380 var err error 381 for i := 0; i < len(b.requestAuthorizer); i++ { 382 err = <-errorsCh 383 if err != nil { 384 break 385 } 386 } 387 return originalReq, err 388 } 389 390 func (b *Backend) withRetryTokenRequest(req *http.Request, res *http.Response) (bool, error) { 391 if len(b.requestAuthorizer) == 0 { 392 return false, nil 393 } 394 395 var retry bool 396 for _, ra := range b.requestAuthorizer { 397 r, err := ra.RetryWithToken(req, res) 398 if err != nil { 399 return false, err 400 } 401 if r { 402 retry = true 403 break 404 } 405 } 406 return retry, nil 407 } 408 409 func (b *Backend) withPathPrefix(req *http.Request, evalCtx *hcl.EvalContext, hclContext *hclsyntax.Body) error { 410 if pathPrefix := b.getAttribute(evalCtx, "path_prefix", hclContext); pathPrefix != "" { 411 // TODO: Check for a valid absolute path 412 if i := strings.Index(pathPrefix, "#"); i >= 0 { 413 return errors.Configuration.Messagef("path_prefix attribute: invalid fragment found in %q", pathPrefix) 414 } else if i = strings.Index(pathPrefix, "?"); i >= 0 { 415 return errors.Configuration.Messagef("path_prefix attribute: invalid query string found in %q", pathPrefix) 416 } 417 418 req.URL.Path = utils.JoinPath("/", pathPrefix, req.URL.Path) 419 } 420 421 return nil 422 } 423 424 func (b *Backend) withBasicAuth(req *http.Request, evalCtx *hcl.EvalContext, hclContext *hclsyntax.Body) { 425 if creds := b.getAttribute(evalCtx, "basic_auth", hclContext); creds != "" { 426 auth := base64.StdEncoding.EncodeToString([]byte(creds)) 427 req.Header.Set("Authorization", "Basic "+auth) 428 } 429 } 430 431 func (b *Backend) getAttribute(evalContext *hcl.EvalContext, name string, hclContext *hclsyntax.Body) string { 432 attrVal, err := eval.ValueFromBodyAttribute(evalContext, hclContext, name) 433 if err != nil { 434 b.upstreamLog.LogEntry().WithError(errors.Evaluation.Label(b.name).With(err)) 435 } 436 return seetie.ValueToString(attrVal) 437 } 438 439 func (b *Backend) withTimeout(req *http.Request, conf *Config) <-chan error { 440 timeout := conf.Timeout 441 ws := false 442 if to, ok := req.Context().Value(request.WebsocketsTimeout).(time.Duration); ok { 443 timeout = to 444 ws = true 445 } 446 447 errCh := make(chan error, 1) 448 if timeout+conf.TTFBTimeout <= 0 { 449 return errCh 450 } 451 452 ctx, cancel := context.WithCancel(context.WithValue(req.Context(), request.ConnectTimeout, conf.ConnectTimeout)) 453 454 downstreamTrace := httptrace.ContextClientTrace(ctx) // e.g. log-timings 455 456 ttfbTimeout := make(chan time.Time, 1) // size to always cleanup related go-routine 457 ttfbTimer := time.NewTimer(conf.TTFBTimeout) 458 ctxTrace := &httptrace.ClientTrace{ 459 WroteRequest: func(info httptrace.WroteRequestInfo) { 460 if downstreamTrace != nil && downstreamTrace.WroteRequest != nil { 461 downstreamTrace.WroteRequest(info) 462 } 463 464 if conf.TTFBTimeout <= 0 { 465 return 466 } 467 468 go func(c context.Context, timeoutCh chan time.Time) { 469 ttfbTimer.Reset(conf.TTFBTimeout) 470 select { 471 case <-c.Done(): 472 ttfbTimer.Stop() 473 select { 474 case <-ttfbTimer.C: 475 default: 476 } 477 case t := <-ttfbTimer.C: 478 // buffered, no select done required 479 timeoutCh <- t 480 } 481 }(ctx, ttfbTimeout) 482 }, 483 GotFirstResponseByte: func() { 484 if downstreamTrace != nil && downstreamTrace.GotFirstResponseByte != nil { 485 downstreamTrace.GotFirstResponseByte() 486 } 487 ttfbTimer.Stop() 488 }, 489 } 490 491 *req = *req.WithContext(httptrace.WithClientTrace(ctx, ctxTrace)) 492 493 go func(c context.Context, cancelFn func(), ec chan error) { 494 defer cancelFn() 495 deadline := make(<-chan time.Time) 496 if timeout > 0 { 497 deadlineTimer := time.NewTimer(timeout) 498 deadline = deadlineTimer.C 499 defer deadlineTimer.Stop() 500 } 501 select { 502 case <-deadline: 503 if ws { 504 ec <- errors.BackendTimeout.Label(b.name).Message("websockets: deadline exceeded") 505 return 506 } 507 ec <- errors.BackendTimeout.Label(b.name).Message("deadline exceeded") 508 return 509 case <-ttfbTimeout: 510 ec <- errors.BackendTimeout.Label(b.name).Message("timeout awaiting response headers") 511 case <-c.Done(): 512 return 513 } 514 }(ctx, cancel, errCh) 515 return errCh 516 } 517 518 func (b *Backend) evalTransport(httpCtx *hcl.EvalContext, params *hclsyntax.Body, req *http.Request) (*Config, error) { 519 log := b.upstreamLog.LogEntry() 520 521 var origin, hostname, proxyURL string 522 var connectTimeout, ttfbTimeout, timeout string 523 type pair struct { 524 attrName string 525 target *string 526 } 527 528 for _, p := range []pair{ 529 {"origin", &origin}, 530 {"hostname", &hostname}, 531 {"proxy", &proxyURL}, 532 // dynamic timings 533 {"connect_timeout", &connectTimeout}, 534 {"ttfb_timeout", &ttfbTimeout}, 535 {"timeout", &timeout}, 536 } { 537 if v, err := eval.ValueFromBodyAttribute(httpCtx, params, p.attrName); err != nil { 538 log.WithError(errors.Evaluation.Label(b.name).With(err)).Error() 539 } else if v != cty.NilVal { 540 *p.target = seetie.ValueToString(v) 541 } 542 } 543 544 originURL, parseErr := url.Parse(origin) 545 if parseErr != nil { 546 return nil, errors.Configuration.Label(b.name).With(parseErr) 547 } else if strings.HasPrefix(originURL.Host, originURL.Scheme+":") { 548 return nil, errors.Configuration.Label(b.name). 549 Messagef("invalid url: %s", originURL.String()) 550 } else if origin == "" { 551 originURL = req.URL 552 } 553 554 if hostname == "" { 555 hostname = originURL.Host 556 } 557 558 if !originURL.IsAbs() || originURL.Hostname() == "" { 559 return nil, errors.Configuration.Label(b.name). 560 Messagef("the origin attribute has to contain an absolute URL with a valid hostname: %q", origin) 561 } 562 563 return b.transportConf. 564 WithTarget(originURL.Scheme, originURL.Host, hostname, proxyURL). 565 WithTimings(connectTimeout, ttfbTimeout, timeout, log), nil 566 } 567 568 func (b *Backend) isUnhealthy(ctx *hcl.EvalContext, params *hclsyntax.Body) error { 569 val, err := eval.ValueFromBodyAttribute(ctx, params, "use_when_unhealthy") 570 if err != nil { 571 return err 572 } 573 574 var useUnhealthy bool 575 if val.Type() == cty.Bool { 576 useUnhealthy = val.True() 577 } // else not set 578 579 b.healthyMu.RLock() 580 defer b.healthyMu.RUnlock() 581 582 if b.healthInfo.Healthy || useUnhealthy { 583 return nil 584 } 585 586 return errors.BackendUnhealthy 587 } 588 589 func (b *Backend) OnProbeChange(info *HealthInfo) { 590 b.healthyMu.Lock() 591 b.healthInfo = info 592 b.healthyMu.Unlock() 593 } 594 595 func (b *Backend) Value() cty.Value { 596 b.healthyMu.RLock() 597 defer b.healthyMu.RUnlock() 598 599 var tokens map[string]interface{} 600 for _, auth := range b.requestAuthorizer { 601 if name, v := auth.value(); v != "" { 602 if tokens == nil { 603 tokens = make(map[string]interface{}) 604 } 605 tokens[name] = v 606 } 607 } 608 609 result := map[string]interface{}{ 610 "health": map[string]interface{}{ 611 "healthy": b.healthInfo.Healthy, 612 "error": b.healthInfo.Error, 613 "state": b.healthInfo.State, 614 }, 615 "hostname": b.transportConfResult.Hostname, 616 "name": b.name, // mandatory 617 "origin": b.transportConfResult.Origin, 618 "connect_timeout": b.transportConfResult.ConnectTimeout.String(), 619 "ttfb_timeout": b.transportConfResult.TTFBTimeout.String(), 620 "timeout": b.transportConfResult.Timeout.String(), 621 } 622 623 if tokens != nil { 624 result["beta_tokens"] = tokens 625 if token, ok := tokens["default"]; ok { 626 result["beta_token"] = token 627 } 628 } 629 630 return seetie.GoToValue(result) 631 } 632 633 // setUserAgent sets an empty one if none is present or empty 634 // to prevent the go http defaultUA gets written. 635 func setUserAgent(outreq *http.Request) { 636 if ua := outreq.Header.Get("User-Agent"); ua == "" { 637 outreq.Header.Set("User-Agent", "") 638 } 639 } 640 641 // setGzipReader will set the gzip.Reader for Content-Encoding gzip. 642 // Invalid header reads will reset the response.Body and return the related error. 643 func setGzipReader(beresp *http.Response) error { 644 if strings.ToLower(beresp.Header.Get(writer.ContentEncodingHeader)) != writer.GzipName { 645 return nil 646 } 647 648 bufOpt := beresp.Request.Context().Value(request.BufferOptions).(buffer.Option) 649 if !bufOpt.Response() { 650 return nil 651 } 652 653 var src io.Reader 654 src, err := gzip.NewReader(beresp.Body) 655 if err != nil { 656 return errors.Backend.With(err).Message("body reset") 657 } 658 659 beresp.Header.Del(writer.ContentEncodingHeader) 660 beresp.Header.Del("Content-Length") 661 beresp.Body = eval.NewReadCloser(src, beresp.Body) 662 return nil 663 } 664 665 // RemoveConnectionHeaders removes hop-by-hop headers listed in the "Connection" header of h. 666 // See RFC 7230, section 6.1 667 func RemoveConnectionHeaders(h http.Header) { 668 for _, f := range h["Connection"] { 669 for _, sf := range strings.Split(f, ",") { 670 if sf = strings.TrimSpace(sf); sf != "" { 671 h.Del(sf) 672 } 673 } 674 } 675 } 676 677 func RemoveHopHeaders(header http.Header) { 678 for _, h := range HopHeaders { 679 hv := header.Get(h) 680 if hv == "" { 681 continue 682 } 683 if h == "Te" && hv == "trailers" { 684 // Issue 21096: tell backend applications that 685 // care about trailer support that we support 686 // trailers. (We do, but we don't go out of 687 // our way to advertise that unless the 688 // incoming client request thought it was 689 // worth mentioning) 690 continue 691 } 692 header.Del(h) 693 } 694 } 695 696 // HopHeaders Hop-by-hop headers. These are removed when sent to the backend. 697 // As of RFC 7230, hop-by-hop headers are required to appear in the 698 // Connection header field. These are the headers defined by the 699 // obsoleted RFC 2616 (section 13.5.1) and are used for backward 700 // compatibility. 701 var HopHeaders = []string{ 702 "Connection", 703 "Proxy-Connection", // non-standard but still sent by libcurl and rejected by e.g. google 704 "Keep-Alive", 705 "Proxy-Authenticate", 706 "Proxy-Authorization", 707 "Te", // canonicalized version of "TE" 708 "Trailer", // not Trailers per URL above; https://www.rfc-editor.org/errata_search.php?eid=4522 709 "Transfer-Encoding", 710 "Upgrade", 711 }