-
Notifications
You must be signed in to change notification settings - Fork 126
transport/http: make inbound header preallocation configurable #2536
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 4 commits
88aa711
eb72adf
bdbf8aa
99eea9f
f096634
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -48,6 +48,89 @@ func popHeader(h http.Header, n string) string { | |
| return v | ||
| } | ||
|
|
||
| func inboundHeaderCapacity( | ||
| strategy HeaderPreallocationStrategy, | ||
| headers http.Header, | ||
| grabHeaders []headerPreallocationGrabHeader, | ||
| ) int { | ||
| switch strategy { | ||
| case "", HeaderPreallocationUnfiltered: | ||
| return len(headers) | ||
| case HeaderPreallocationScan: | ||
| return scannedInboundHeaderCapacity(headers, grabHeaders) | ||
| case HeaderPreallocationDisabled: | ||
| return 0 | ||
| default: | ||
| // Inbounds validate the strategy before installing the handler. | ||
|
bananacocodrilo marked this conversation as resolved.
Outdated
|
||
| return 0 | ||
| } | ||
| } | ||
|
|
||
| type headerPreallocationGrabHeader struct { | ||
| key string | ||
| canonicalKey string | ||
| } | ||
|
|
||
| func newHeaderPreallocationGrabHeaders( | ||
| grabHeaders map[string]struct{}, | ||
| ) []headerPreallocationGrabHeader { | ||
| headers := make([]headerPreallocationGrabHeader, 0, len(grabHeaders)) | ||
| for key := range grabHeaders { | ||
| headers = append(headers, headerPreallocationGrabHeader{ | ||
| key: key, | ||
| canonicalKey: http.CanonicalHeaderKey(key), | ||
| }) | ||
| } | ||
| return headers | ||
| } | ||
|
|
||
| func scannedInboundHeaderCapacity( | ||
| headers http.Header, | ||
| grabHeaders []headerPreallocationGrabHeader, | ||
| ) int { | ||
| capacity := 0 | ||
| for key, values := range headers { | ||
| if len(values) == 0 { | ||
| continue | ||
| } | ||
|
|
||
| if hasPrefixFold(key, ApplicationHeaderPrefix) || | ||
| isTracingHeader(key) || | ||
| isProxyHeader(key) { | ||
| capacity++ | ||
| } | ||
| } | ||
|
|
||
| for _, header := range grabHeaders { | ||
| if isTracingHeader(header.key) || isProxyHeader(header.key) { | ||
| continue | ||
| } | ||
| if headerValue(headers, header.canonicalKey, header.key) != "" { | ||
| capacity++ | ||
| } | ||
| } | ||
|
|
||
| // Raw and prefixed forms can produce the same canonical transport key. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. should we move this comment to line 108? |
||
| // Count both forms: the estimate is an upper bound for items and may be | ||
| // required by originalItems, which preserves both original key forms. | ||
| return capacity | ||
| } | ||
|
|
||
| func headerValue(headers http.Header, canonicalKey, lowerKey string) string { | ||
| if values, ok := headers[canonicalKey]; ok { | ||
| if len(values) > 0 { | ||
| return values[0] | ||
| } | ||
| return "" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there possibility that value for canonicalKey is empty, but not for lowerKey? In this case we bypass the lowerKey one
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was! |
||
| } | ||
| if lowerKey != canonicalKey { | ||
| if values := headers[lowerKey]; len(values) > 0 { | ||
| return values[0] | ||
| } | ||
| } | ||
| return "" | ||
| } | ||
|
|
||
| // handler adapts a transport.Handler into a handler for net/http. | ||
| type handler struct { | ||
| router transport.Router | ||
|
|
@@ -58,6 +141,8 @@ type handler struct { | |
| transport *Transport | ||
| overrideOriginalItemWithCanonicalizedKey bool | ||
| headerCaseMapping map[string][]string | ||
| headerPreallocationStrategy HeaderPreallocationStrategy | ||
| headerPreallocationGrabHeaders []headerPreallocationGrabHeader | ||
| // duplicate header counter vector | ||
| duplicateHeaderCounterVec *metrics.CounterVector | ||
| } | ||
|
|
@@ -123,7 +208,11 @@ func (h handler) callHandler(responseWriter *responseWriter, req *http.Request, | |
| callerProcedure := popHeader(req.Header, CallerProcedureHeader) | ||
| ttl := popHeader(req.Header, TTLMSHeader) | ||
|
|
||
| transportHeader := transport.NewHeadersWithCapacity(len(req.Header)) | ||
| transportHeader := transport.NewHeadersWithCapacity(inboundHeaderCapacity( | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. inboundHeaderCapacity returns 0 for Disabled option, and we end up having an empty map without item or originalItem keys.
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. transport.Headers.With lazily allocates both maps when the first header is added. No headers ends with no allocations. |
||
| h.headerPreallocationStrategy, | ||
| req.Header, | ||
| h.headerPreallocationGrabHeaders, | ||
| )) | ||
| if h.overrideOriginalItemWithCanonicalizedKey { | ||
| transportHeader = transportHeader.EnableOverrideOriginalItemsWithCanonicalizedKeys() | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is this "" option for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok because it is the default option
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm removing it and making it so "no config" gets set to unfiltered to make it more clear