-
Notifications
You must be signed in to change notification settings - Fork 996
fix(instrumentation-fetch): handle HeadersInit tuple arrays in _addHeaders #6341
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
base: main
Are you sure you want to change the base?
fix(instrumentation-fetch): handle HeadersInit tuple arrays in _addHeaders #6341
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #6341 +/- ##
==========================================
- Coverage 95.50% 95.50% -0.01%
==========================================
Files 365 365
Lines 11609 11604 -5
Branches 2677 2673 -4
==========================================
- Hits 11087 11082 -5
Misses 522 522
🚀 New features to boost your workflow:
|
Is that an allowed usage for headers? https://developer.mozilla.org/en-US/docs/Web/API/RequestInit#headers suggests it is not legal. |
|
From trying to parse the fetch spec (e.g. https://fetch.spec.whatwg.org/#typedefdef-headersinit) my head is spinning and I don't know what is meant to be legal. |
|
undici's fetch() appears to allow it -- from reading its types. |
|
And using
|
|
@trentm The tuple is bizarre but it works. I've never seen it in the wild though 🧐 |
|
this reminds me of open-telemetry/opentelemetry-js-contrib#2875 |
| } else if (options.headers instanceof Map) { | ||
| propagation.inject(context.active(), options.headers, { | ||
| set: (h, k, v) => h.set(k, typeof v === 'string' ? v : String(v)), | ||
| // Merge user headers second so they take precedence on conflicts |
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.
There is no reference on the spec of what should take precedence. See the spec issue that was motivated by the same situation in undici instrumentation.
I do not have a preference in which one takes precedence because each option has a use case. At least we should be consistent and do not override the headers in the if block above.
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.
Crazy how complex this gets. Sorry for the wordiness, I'm thinking out loud.
- The instr was already inconsistent in the precedence it was giving. The existing
options instanceof Requestandoptions.headers instanceof Headersandoptions.headers instanceof Mapbranches were already giving precedence to the newpropagation.injected headers. The finalelseblock was givin precedence to the user-provided headers. - The fetch spec (or at least from reading MDN) saw that
Request.heaadersis read-only, so the firstif (options instanceof Request)block has to behave as it currently is (precedence to the injected headers). (Well, this could all be refactored to completely replace the wholeRequest, but that's probably crazy and has its own issues.) So, if consistency in the precedence is a concern, then I think we should have all the inject'ing blocks just add to the existing headers (precedence to the injected headers). - In all cases, except
options instanceof Requestwhereoptions.headersproperty is read-only, it might be nice to avoid mutating the given headers object. Instrumentation-http avoids mutating the headers, because of AWS S3 behavior changes when using the auto instrumentation opentelemetry-js-contrib#1609 (comment) For this reason, I like the idea of creating a new Headers object. - (It might be nice to avoid mutating the whole options object, in case user code is re-using it, but I think that would be for a separate issue/PR.)
- Why a Headers object instead of not just an object? Because it is native to
fetch()and I assume will do the best job of compatibly handing whatever weird thing is passed asoptions.headers.
Doing ^^ gives a change that is almost exactly #6194
Then there is whether to "break" requestHook, because now request.headers is a Headers instance where it was typically (but not always) an Object instance.
- Returning a
Headersinstance still fits theFetchRequestHookFunctiontype, FWIW. - I noticed an interesting thing in instrumentation-undici: It explicitly runs the
requestHookbefore injection, so "no hook can tamper" with the injected headers. https://github.com/open-telemetry/opentelemetry-js-contrib/blob/97d9ef01008d47df6e2f56eb06908cda23ba6fe6/packages/instrumentation-undici/src/undici.ts#L329-L340
Options:
- Break
requestHookusage that was assuming request.headers would be an Object -- typically because it was an object on the way in, so somewhat reasonable assumption. - Run requestHook before injection so workaround the issue.
- Do the extra work of converting back to an Object. Note that this will also possibly break a user that assumed
options.headerswould be a Headers if it passed a Headers in, or a Map if it passed a Map in.
I'm leaning towards option 2, because that seems a weaselly way out. :)
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 think it should match undici behavior by calling requestHook before propagation injection. I implemented option 2 and kept the Headers type rather than converting to a plain object since it's a valid HeadersInit type. Thoughts?

Reconstruction of #6194
Which problem is this PR solving?
When
HeadersInitis passed as an array of tuples:The
_addHeadersmethod corrupts headers into{"0": "Content-Type", "1": "application/json"}becauseObject.assign()treats arrays as objects with numeric keys.Short description of the changes
Headersconstructor internally to correctly parse allHeadersInittypes (undefined, Headers, Record, and tuple arrays)requestHookusersType of change
How Has This Been Tested?
Checklist: