Skip to content

Commit 9a67045

Browse files
mzahordyladanvmarchaud
authored
fix: use socket from the request (#1939)
Co-authored-by: Daniel Dyla <[email protected]> Co-authored-by: Valentin Marchaud <[email protected]>
1 parent 9437d7e commit 9a67045

File tree

8 files changed

+121
-14
lines changed

8 files changed

+121
-14
lines changed

packages/opentelemetry-instrumentation-http/src/utils.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -465,7 +465,10 @@ export const getIncomingRequestAttributesOnResponse = (
465465
request: IncomingMessage & { __ot_middlewares?: string[] },
466466
response: ServerResponse & { socket: Socket }
467467
): SpanAttributes => {
468-
const { statusCode, statusMessage, socket } = response;
468+
// take socket from the request,
469+
// since it may be detached from the response object in keep-alive mode
470+
const { socket } = request;
471+
const { statusCode, statusMessage } = response;
469472
const { localAddress, localPort, remoteAddress, remotePort } = socket;
470473
const { __ot_middlewares } = (request as unknown) as {
471474
[key: string]: unknown;

packages/opentelemetry-instrumentation-http/test/functionals/utils.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,7 @@ describe('Utility', () => {
291291
it('should correctly parse the middleware stack if present', () => {
292292
const request = {
293293
__ot_middlewares: ['/test', '/toto', '/'],
294+
socket: {},
294295
} as IncomingMessage & { __ot_middlewares?: string[] };
295296

296297
const attributes = utils.getIncomingRequestAttributesOnResponse(request, {
@@ -300,7 +301,9 @@ describe('Utility', () => {
300301
});
301302

302303
it('should succesfully process without middleware stack', () => {
303-
const request = {} as IncomingMessage;
304+
const request = {
305+
socket: {},
306+
} as IncomingMessage;
304307
const attributes = utils.getIncomingRequestAttributesOnResponse(request, {
305308
socket: {},
306309
} as ServerResponse & { socket: Socket });

packages/opentelemetry-instrumentation-http/test/integrations/http-enable.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import * as http from 'http';
3939
import { httpRequest } from '../utils/httpRequest';
4040
import { DummyPropagation } from '../utils/DummyPropagation';
4141
import { Socket } from 'net';
42+
import { sendRequestTwice } from '../utils/rawRequest';
4243

4344
const protocol = 'http';
4445
const serverPort = 32345;
@@ -345,5 +346,14 @@ describe('HttpInstrumentation Integration tests', () => {
345346
});
346347
});
347348
}
349+
350+
it('should work for multiple active requests in keep-alive mode', async () => {
351+
await sendRequestTwice(hostname, mockServerPort);
352+
const spans = memoryExporter.getFinishedSpans();
353+
const span = spans.find((s: any) => s.kind === SpanKind.SERVER);
354+
assert.ok(span);
355+
assert.strictEqual(spans.length, 2);
356+
assert.strictEqual(span.name, 'HTTP GET');
357+
});
348358
});
349359
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import * as net from 'net';
17+
18+
// used to reproduce this issue:
19+
// https://github.com/open-telemetry/opentelemetry-js/pull/1939
20+
export async function sendRequestTwice(
21+
host: string,
22+
port: number
23+
): Promise<Buffer> {
24+
return new Promise((resolve, reject) => {
25+
const request = 'GET /raw HTTP/1.1\n\n';
26+
const socket = net.createConnection({ host, port }, () => {
27+
socket.write(`${request}${request}`, err => {
28+
if (err) reject(err);
29+
});
30+
});
31+
socket.on('data', data => {
32+
// since it's <1kb size, we expect both responses to come in a single chunk
33+
socket.destroy();
34+
resolve(data);
35+
});
36+
socket.on('error', err => reject(err));
37+
});
38+
}

packages/opentelemetry-plugin-http/src/utils.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import {
3131
RequestOptions,
3232
ServerResponse,
3333
} from 'http';
34-
import { Socket } from 'net';
3534
import * as url from 'url';
3635
import { Err, IgnoreMatcher, ParsedRequestOptions } from './types';
3736

@@ -463,9 +462,12 @@ export const getIncomingRequestAttributes = (
463462
*/
464463
export const getIncomingRequestAttributesOnResponse = (
465464
request: IncomingMessage & { __ot_middlewares?: string[] },
466-
response: ServerResponse & { socket: Socket }
465+
response: ServerResponse
467466
): SpanAttributes => {
468-
const { statusCode, statusMessage, socket } = response;
467+
// use socket from the request,
468+
// since it may be detached from the response object in keep-alive mode
469+
const { socket } = request;
470+
const { statusCode, statusMessage } = response;
469471
const { localAddress, localPort, remoteAddress, remotePort } = socket;
470472
const { __ot_middlewares } = (request as unknown) as {
471473
[key: string]: unknown;

packages/opentelemetry-plugin-http/test/functionals/utils.test.ts

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ import { HttpAttribute } from '@opentelemetry/semantic-conventions';
2525
import * as assert from 'assert';
2626
import * as http from 'http';
2727
import { IncomingMessage, ServerResponse } from 'http';
28-
import { Socket } from 'net';
2928
import * as sinon from 'sinon';
3029
import * as url from 'url';
3130
import { IgnoreMatcher } from '../../src/types';
@@ -291,19 +290,23 @@ describe('Utility', () => {
291290
it('should correctly parse the middleware stack if present', () => {
292291
const request = {
293292
__ot_middlewares: ['/test', '/toto', '/'],
294-
} as IncomingMessage & { __ot_middlewares?: string[] };
295-
296-
const attributes = utils.getIncomingRequestAttributesOnResponse(request, {
297293
socket: {},
298-
} as ServerResponse & { socket: Socket });
294+
} as IncomingMessage & { __ot_middlewares?: string[] };
295+
const response = {} as ServerResponse;
296+
const attributes = utils.getIncomingRequestAttributesOnResponse(
297+
request,
298+
response
299+
);
299300
assert.deepEqual(attributes[HttpAttribute.HTTP_ROUTE], '/test/toto');
300301
});
301302

302303
it('should succesfully process without middleware stack', () => {
303-
const request = {} as IncomingMessage;
304-
const attributes = utils.getIncomingRequestAttributesOnResponse(request, {
305-
socket: {},
306-
} as ServerResponse & { socket: Socket });
304+
const request = { socket: {} } as IncomingMessage;
305+
const response = {} as ServerResponse;
306+
const attributes = utils.getIncomingRequestAttributesOnResponse(
307+
request,
308+
response
309+
);
307310
assert.deepEqual(attributes[HttpAttribute.HTTP_ROUTE], undefined);
308311
});
309312
});

packages/opentelemetry-plugin-http/test/integrations/http-enable.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
import { HttpPluginConfig } from '../../src/types';
3636
import { AsyncHooksContextManager } from '@opentelemetry/context-async-hooks';
3737
import { Socket } from 'net';
38+
import { sendRequestTwice } from '../utils/rawRequest';
3839
const protocol = 'http';
3940
const serverPort = 32345;
4041
const hostname = 'localhost';
@@ -337,5 +338,14 @@ describe('HttpPlugin Integration tests', () => {
337338
});
338339
});
339340
}
341+
342+
it('should work for multiple active requests in keep-alive mode', async () => {
343+
await sendRequestTwice(hostname, mockServerPort);
344+
const spans = memoryExporter.getFinishedSpans();
345+
const span = spans.find((s: any) => s.kind === SpanKind.SERVER);
346+
assert.ok(span);
347+
assert.strictEqual(spans.length, 2);
348+
assert.strictEqual(span.name, 'HTTP GET');
349+
});
340350
});
341351
});
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
import * as net from 'net';
17+
18+
// used to reproduce this issue:
19+
// https://github.com/open-telemetry/opentelemetry-js/pull/1939
20+
export async function sendRequestTwice(
21+
host: string,
22+
port: number
23+
): Promise<Buffer> {
24+
return new Promise((resolve, reject) => {
25+
const request = 'GET /raw HTTP/1.1\n\n';
26+
const socket = net.createConnection({ host, port }, () => {
27+
socket.write(`${request}${request}`, err => {
28+
if (err) reject(err);
29+
});
30+
});
31+
socket.on('data', data => {
32+
// since it's <1kb size, we expect both responses to come in a single chunk
33+
socket.destroy();
34+
resolve(data);
35+
});
36+
socket.on('error', err => reject(err));
37+
});
38+
}

0 commit comments

Comments
 (0)