Skip to content
This repository was archived by the owner on Jan 21, 2026. It is now read-only.

Commit bc877a5

Browse files
authored
fix: inject context http headers early if expect header exists (#766)
PR-URL: #766
1 parent 2978728 commit bc877a5

2 files changed

Lines changed: 48 additions & 3 deletions

File tree

src/plugins/plugin-http.ts

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,16 @@ function getSpanName(options: ClientRequestArgs|url.URL) {
4242
return options.hostname || options.host || 'localhost';
4343
}
4444

45+
/**
46+
* Returns whether the Expect header is on the given options object.
47+
* @param options Options for http.request.
48+
*/
49+
function hasExpectHeader(options: ClientRequestArgs|url.URL): boolean {
50+
return !!(
51+
(options as ClientRequestArgs).headers &&
52+
(options as ClientRequestArgs).headers!.Expect);
53+
}
54+
4555
function extractUrl(
4656
options: ClientRequestArgs|url.URL, fallbackProtocol: string) {
4757
let path;
@@ -111,6 +121,24 @@ function makeRequestTrace(
111121
span.addLabel(api.labels.HTTP_METHOD_LABEL_KEY, method);
112122
span.addLabel(api.labels.HTTP_URL_LABEL_KEY, uri);
113123

124+
// If outgoing request headers contain the "Expect" header, the returned
125+
// ClientRequest will throw an error if any new headers are added. For this
126+
// reason, only in this scenario, we opt to clone the options object to
127+
// inject the trace context header instead of using ClientRequest#setHeader.
128+
// (We don't do this generally because cloning the options object is an
129+
// expensive operation.)
130+
let traceHeaderPreinjected = false;
131+
if (hasExpectHeader(options)) {
132+
traceHeaderPreinjected = true;
133+
// "Clone" the options object -- but don't deep-clone anything except for
134+
// headers.
135+
options = Object.assign({}, options) as ClientRequestArgs;
136+
options.headers = Object.assign({}, options.headers);
137+
// Inject the trace context header.
138+
options.headers[api.constants.TRACE_CONTEXT_HEADER_NAME] =
139+
span.getTraceContext();
140+
}
141+
114142
const req = request(options, (res) => {
115143
api.wrapEmitter(res);
116144
let numBytes = 0;
@@ -144,13 +172,17 @@ function makeRequestTrace(
144172
}
145173
});
146174
api.wrapEmitter(req);
147-
req.setHeader(
148-
api.constants.TRACE_CONTEXT_HEADER_NAME, span.getTraceContext());
149175
req.on('error', error => {
150176
span.addLabel(api.labels.ERROR_DETAILS_NAME, error.name);
151177
span.addLabel(api.labels.ERROR_DETAILS_MESSAGE, error.message);
152178
span.endSpan();
153179
});
180+
// Inject the trace context header, but only if it wasn't already injected
181+
// earlier.
182+
if (!traceHeaderPreinjected) {
183+
req.setHeader(
184+
api.constants.TRACE_CONTEXT_HEADER_NAME, span.getTraceContext());
185+
}
154186
return req;
155187
};
156188
}

test/plugins/test-trace-http.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,24 @@ for (const nodule of Object.keys(servers) as Array<keyof typeof servers>) {
183183
const req = http.request(
184184
{port, rejectUnauthorized: false},
185185
waitForResponse.handleResponse);
186-
await wait(DEFAULT_SPAN_DURATION / 2);
187186
req.end();
188187
await waitForResponse.done;
189188
}
190189
},
190+
{
191+
description: 'calling http.get with Expect header',
192+
fn: async () => {
193+
const waitForResponse = new WaitForResponse();
194+
const req = http.get(
195+
{
196+
port,
197+
rejectUnauthorized: false,
198+
headers: {Expect: '100-continue'}
199+
},
200+
waitForResponse.handleResponse);
201+
await waitForResponse.done;
202+
}
203+
},
191204
{
192205
description: 'calling http.get, but timing out and emitting an error',
193206
fn: async () => {

0 commit comments

Comments
 (0)