Skip to content

Commit 01c1c86

Browse files
committed
fix(instr-undici): fix headers parsing
1 parent 96880ea commit 01c1c86

File tree

1 file changed

+31
-26
lines changed
  • plugins/node/instrumentation-undici/src

1 file changed

+31
-26
lines changed

plugins/node/instrumentation-undici/src/undici.ts

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -165,25 +165,24 @@ export class UndiciInstrumentation extends InstrumentationBase<UndiciInstrumenta
165165
});
166166
}
167167

168-
/**
169-
* Yield an object { key, value } for each header in the request. Skips
170-
* likely-invalid headers. Multi-valued headers are passed through.
171-
*/
172-
private *parseRequestHeaders(
173-
request: UndiciRequest
174-
): Generator<{ key: string; value: string | string[] }> {
168+
private parseRequestHeaders(request: UndiciRequest) {
169+
const result = new Map<string, string | string[]>();
170+
175171
if (Array.isArray(request.headers)) {
176172
// headers are an array [k1, v2, k2, v2] (undici v6+)
173+
// values could be string or a string[] for multiple values
177174
for (let i = 0; i < request.headers.length; i += 2) {
178175
const key = request.headers[i];
179-
if (typeof key !== 'string') {
180-
// Shouldn't happen, but the types don't know that, and let's be safe
181-
continue;
176+
const value = request.headers[i + 1];
177+
178+
// Key should always be a string, but the types don't know that, and let's be safe
179+
if (typeof key === 'string') {
180+
result.set(key.toLowerCase(), value);
182181
}
183-
yield { key, value: request.headers[i + 1] };
184182
}
185183
} else if (typeof request.headers === 'string') {
186184
// headers are a raw string (undici v5)
185+
// headers could be repeated in several lines for multiple values
187186
const headers = request.headers.split('\r\n');
188187
for (const line of headers) {
189188
if (!line) {
@@ -194,11 +193,20 @@ export class UndiciInstrumentation extends InstrumentationBase<UndiciInstrumenta
194193
// Invalid header? Probably this can't happen, but again let's be safe.
195194
continue;
196195
}
197-
const key = line.substring(0, colonIndex);
196+
const key = line.substring(0, colonIndex).toLowerCase();
198197
const value = line.substring(colonIndex + 1).trim();
199-
yield { key, value };
198+
const allValues = result.get(key);
199+
200+
if (allValues && Array.isArray(allValues)) {
201+
allValues.push(value);
202+
} else if (allValues) {
203+
result.set(key, [allValues, value]);
204+
} else {
205+
result.set(key, value);
206+
}
200207
}
201208
}
209+
return result;
202210
}
203211

204212
// This is the 1st message we receive for each request (fired after request creation). Here we will
@@ -254,17 +262,14 @@ export class UndiciInstrumentation extends InstrumentationBase<UndiciInstrumenta
254262
}
255263

256264
// Get user agent from headers
257-
for (const { key, value } of this.parseRequestHeaders(request)) {
258-
if (key.toLowerCase() === 'user-agent') {
259-
// user-agent should only appear once per the spec, but the library doesn't
260-
// prevent passing it multiple times, so we handle that to be safe.
261-
// we will pick last value set
262-
const userAgent = Array.isArray(value)
263-
? value[value.length - 1]
264-
: value;
265-
attributes[SemanticAttributes.USER_AGENT_ORIGINAL] = userAgent;
266-
break;
267-
}
265+
const headersMap = this.parseRequestHeaders(request);
266+
const userAgentValues = headersMap.get('user-agent');
267+
268+
if (userAgentValues) {
269+
const userAgent = Array.isArray(userAgentValues)
270+
? userAgentValues[userAgentValues.length - 1]
271+
: userAgentValues;
272+
attributes[SemanticAttributes.USER_AGENT_ORIGINAL] = userAgent;
268273
}
269274

270275
// Get attributes from the hook if present
@@ -357,9 +362,9 @@ export class UndiciInstrumentation extends InstrumentationBase<UndiciInstrumenta
357362
const headersToAttribs = new Set(
358363
config.headersToSpanAttributes.requestHeaders.map(n => n.toLowerCase())
359364
);
365+
const headersMap = this.parseRequestHeaders(request);
360366

361-
for (const { key, value } of this.parseRequestHeaders(request)) {
362-
const name = key.toLowerCase();
367+
for (const [name, value] of headersMap.entries()) {
363368
if (headersToAttribs.has(name)) {
364369
const attrValue = Array.isArray(value) ? value.join(', ') : value;
365370
spanAttributes[`http.request.header.${name}`] = attrValue.trim();

0 commit comments

Comments
 (0)