Skip to content

Commit 03556fb

Browse files
authored
lib: fix ERR_HTTP_HEADERS_SENT on set traceparent (#7)
1 parent 9a491a9 commit 03556fb

2 files changed

Lines changed: 60 additions & 0 deletions

File tree

‎lib/instrumentations.js‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,11 @@ function onHttpClientRequestCreated({ request }) {
178178

179179
request[kSpan] = span;
180180

181+
// Node stores the headers inside the ClientRequest constructor when they
182+
// are given as an array or include Expect, so setHeader would throw
183+
// ERR_HTTP_HEADERS_SENT. The span is still recorded, without propagation.
184+
if (request.headersSent) return;
185+
181186
request.setHeader('traceparent', span.toTraceparent());
182187
// A stale tracestate from an unrelated trace must not travel with the
183188
// new trace context.

‎test/test-http-client.js‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,61 @@ describe('otel HTTP client spans', () => {
129129
);
130130
});
131131

132+
// Node has already stored the headers when the request is created with
133+
// array headers or an Expect header, so injection is skipped rather than
134+
// throwing ERR_HTTP_HEADERS_SENT.
135+
for (const [title, path, getHeaders] of [
136+
// Node adds no default Host header to array headers.
137+
[
138+
'array headers',
139+
'/array-headers',
140+
(port) => ['Host', `127.0.0.1:${port}`, 'x-dup', 'a', 'x-dup', 'b'],
141+
],
142+
['an Expect header', '/expect', () => ({ Expect: '100-continue' })],
143+
]) {
144+
it(`records a client span for a request with ${title}`, async () => {
145+
let receivedTraceparent = 'sentinel';
146+
147+
const target = await startHTTPServer((req, res) => {
148+
receivedTraceparent = req.headers.traceparent;
149+
req.resume();
150+
req.on('end', () => {
151+
res.writeHead(200);
152+
res.end('ok');
153+
});
154+
});
155+
156+
await new Promise((resolve, reject) => {
157+
const req = http.request(
158+
{
159+
host: '127.0.0.1',
160+
port: target.port,
161+
method: 'POST',
162+
path,
163+
headers: getHeaders(target.port),
164+
},
165+
(res) => {
166+
res.resume();
167+
res.on('end', resolve);
168+
},
169+
);
170+
req.on('error', reject);
171+
req.end('body');
172+
});
173+
174+
const spans = await flushAndCollect(collector);
175+
await target.close();
176+
177+
assert.strictEqual(receivedTraceparent, undefined);
178+
const clientSpan = getClientSpan(spans, path);
179+
assert.ok(clientSpan, 'Expected a client span');
180+
assert.strictEqual(
181+
spanAttrs(clientSpan)['http.response.status_code'],
182+
'200',
183+
);
184+
});
185+
}
186+
132187
it('omits server.port when the Host port is not decimal', async () => {
133188
const target = await startHTTPServer((req, res) => {
134189
res.writeHead(200);

0 commit comments

Comments
 (0)