Skip to content

Commit 8d53f05

Browse files
committed
always convert string headers to array headers (and back)
1 parent 5e81f57 commit 8d53f05

File tree

1 file changed

+52
-74
lines changed

1 file changed

+52
-74
lines changed

packages/node-core/src/utils/outgoingFetchRequest.ts

Lines changed: 52 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -45,68 +45,74 @@ export function addTracePropagationHeadersToFetchRequest(
4545

4646
const { 'sentry-trace': sentryTrace, baggage, traceparent } = addedHeaders;
4747

48+
const requestHeaders = Array.isArray(request.headers) ? request.headers : stringToArrayHeaders(request.headers);
49+
4850
// OTel's UndiciInstrumentation calls propagation.inject() which unconditionally
4951
// appends headers to the request. When the user also sets headers via getTraceData(),
5052
// this results in duplicate sentry-trace and baggage entries.
5153
// We clean these up before applying our own logic.
52-
_deduplicateHeaders(request);
54+
_deduplicateArrayHeaders(requestHeaders);
5355

5456
// We do not want to overwrite existing headers here
5557
// If the core UndiciInstrumentation is registered, it will already have set the headers
5658
// We do not want to add any then
57-
if (Array.isArray(request.headers)) {
58-
const requestHeaders = request.headers;
59-
60-
const hasExistingSentryTraceHeader = requestHeaders.includes(SENTRY_TRACE_HEADER);
59+
const hasExistingSentryTraceHeader = requestHeaders.includes(SENTRY_TRACE_HEADER);
6160

62-
// We do not want to set any headers if we already have an existing sentry-trace header.
63-
// This is still the source of truth, otherwise we risk mixing up baggage and sentry-trace values.
64-
if (!hasExistingSentryTraceHeader) {
65-
if (sentryTrace) {
66-
requestHeaders.push(SENTRY_TRACE_HEADER, sentryTrace);
67-
}
61+
// We do not want to set any headers if we already have an existing sentry-trace header.
62+
// sentry-trace is still the source of truth, otherwise we risk mixing up baggage and sentry-trace values.
63+
if (!hasExistingSentryTraceHeader) {
64+
if (sentryTrace) {
65+
requestHeaders.push(SENTRY_TRACE_HEADER, sentryTrace);
66+
}
6867

69-
if (traceparent && !requestHeaders.includes('traceparent')) {
70-
requestHeaders.push('traceparent', traceparent);
71-
}
68+
if (traceparent && !requestHeaders.includes('traceparent')) {
69+
requestHeaders.push('traceparent', traceparent);
70+
}
7271

73-
// For baggage, we make sure to merge this into a possibly existing header
74-
const existingBaggagePos = requestHeaders.findIndex(header => header === SENTRY_BAGGAGE_HEADER);
75-
if (baggage && existingBaggagePos === -1) {
76-
requestHeaders.push(SENTRY_BAGGAGE_HEADER, baggage);
77-
} else if (baggage) {
78-
// headers in format [key_0, value_0, key_1, value_1, ...], hence the +1 here
79-
const existingBaggage = requestHeaders[existingBaggagePos + 1];
80-
const merged = mergeBaggageHeaders(existingBaggage, baggage);
81-
if (merged) {
82-
requestHeaders[existingBaggagePos + 1] = merged;
83-
}
72+
// For baggage, we make sure to merge this into a possibly existing header
73+
const existingBaggagePos = requestHeaders.findIndex(header => header === SENTRY_BAGGAGE_HEADER);
74+
if (baggage && existingBaggagePos === -1) {
75+
requestHeaders.push(SENTRY_BAGGAGE_HEADER, baggage);
76+
} else if (baggage) {
77+
// headers in format [key_0, value_0, key_1, value_1, ...], hence the +1 here
78+
const existingBaggage = requestHeaders[existingBaggagePos + 1];
79+
const merged = mergeBaggageHeaders(existingBaggage, baggage);
80+
if (merged) {
81+
requestHeaders[existingBaggagePos + 1] = merged;
8482
}
8583
}
86-
} else {
87-
// We do not want to overwrite existing header here, if it was already set
88-
const hasExistingSentryTraceHeader = request.headers.includes(`${SENTRY_TRACE_HEADER}:`);
84+
}
8985

90-
if (!hasExistingSentryTraceHeader) {
91-
if (sentryTrace) {
92-
request.headers += `${SENTRY_TRACE_HEADER}: ${sentryTrace}\r\n`;
93-
}
86+
if (!Array.isArray(request.headers)) {
87+
// For orginal string request headers, we need to wrote them back to the request
88+
request.headers = arrayToStringHeaders(requestHeaders);
89+
}
90+
}
9491

95-
if (traceparent && !request.headers.includes('traceparent:')) {
96-
request.headers += `traceparent: ${traceparent}\r\n`;
92+
function stringToArrayHeaders(requestHeaders: string): string[] {
93+
const headersArray = requestHeaders.split('\r\n');
94+
const headers: string[] = [];
95+
for (const header of headersArray) {
96+
try {
97+
const [key, value] = header.split(':').map(part => part.trim());
98+
if (key != null && value != null) {
99+
headers.push(key, value);
97100
}
101+
} catch {}
102+
}
103+
return headers;
104+
}
98105

99-
const existingBaggage = request.headers.match(BAGGAGE_HEADER_REGEX)?.[1];
100-
if (baggage && !existingBaggage) {
101-
request.headers += `${SENTRY_BAGGAGE_HEADER}: ${baggage}\r\n`;
102-
} else if (baggage) {
103-
const merged = mergeBaggageHeaders(existingBaggage, baggage);
104-
if (merged) {
105-
request.headers = request.headers.replace(BAGGAGE_HEADER_REGEX, `baggage: ${merged}\r\n`);
106-
}
107-
}
108-
}
106+
function arrayToStringHeaders(headers: string[]): string {
107+
const headerPairs: string[] = [];
108+
for (let i = 0; i < headers.length; i += 2) {
109+
headerPairs.push(`${headers[i]}: ${headers[i + 1]}`);
110+
}
111+
if (!headerPairs.length) {
112+
return '';
109113
}
114+
115+
return headerPairs.join('\r\n').concat('\r\n');
110116
}
111117

112118
/**
@@ -115,37 +121,9 @@ export function addTracePropagationHeadersToFetchRequest(
115121
* OTel's UndiciInstrumentation unconditionally appends headers via propagation.inject(),
116122
* which can create duplicates when the user has already set these headers (e.g. via getTraceData()).
117123
* For sentry-trace, we keep the first occurrence (user-set).
118-
* For baggage, we merge all occurrences into one to preserve both sentry and non-sentry entries.
124+
* For baggage, we merge all occurrences into one header to preserve non-sentry entries. For Sentry
125+
* entries, we keep the first occurance.
119126
*/
120-
function _deduplicateHeaders(request: UndiciRequest): void {
121-
if (Array.isArray(request.headers)) {
122-
_deduplicateArrayHeaders(request.headers);
123-
} else {
124-
const headersArray = request.headers.split('\r\n');
125-
const headers: string[] = [];
126-
for (const header of headersArray) {
127-
try {
128-
const [key, value] = header.split(':').map(part => part.trim());
129-
if (key != null && value != null) {
130-
headers.push(key, value);
131-
}
132-
} catch {
133-
continue;
134-
}
135-
}
136-
137-
_deduplicateArrayHeaders(headers);
138-
139-
const headerPairs: string[] = [];
140-
for (let i = 0; i < headers.length; i += 2) {
141-
headerPairs.push(`${headers[i]}: ${headers[i + 1]}`);
142-
}
143-
const concatenated = headerPairs.join('\r\n');
144-
if (concatenated) {
145-
request.headers = concatenated.concat('\r\n');
146-
}
147-
}
148-
}
149127

150128
function _deduplicateArrayHeaders(headers: string[]): void {
151129
_deduplicateArrayHeader(headers, SENTRY_TRACE_HEADER);

0 commit comments

Comments
 (0)