Skip to content

Commit fdf7f07

Browse files
committed
fix: should got undici:client:sendHeaders message on H2
closes #510
1 parent 104664d commit fdf7f07

File tree

3 files changed

+143
-6
lines changed

3 files changed

+143
-6
lines changed

src/HttpClient.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -680,8 +680,8 @@ export class HttpClient extends EventEmitter {
680680
res,
681681
};
682682

683-
debug('Request#%d got response, status: %s, headers: %j, timing: %j',
684-
requestId, res.status, res.headers, res.timing);
683+
debug('Request#%d got response, status: %s, headers: %j, timing: %j, socket: %j',
684+
requestId, res.status, res.headers, res.timing, res.socket);
685685

686686
if (args.retry > 0 && requestContext.retries < args.retry) {
687687
const isRetry = args.isRetry ?? defaultIsRetry;

src/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,13 +172,13 @@ export function updateSocketInfo(socketInfo: SocketInfo, internalOpaque: any, er
172172
socketInfo.remotePort = socket.remotePort;
173173
socketInfo.remoteFamily = socket.remoteFamily;
174174
}
175+
if (Array.isArray(socket.autoSelectFamilyAttemptedAddresses)) {
176+
socketInfo.attemptedRemoteAddresses = socket.autoSelectFamilyAttemptedAddresses;
177+
}
175178
socketInfo.bytesRead = socket.bytesRead;
176179
socketInfo.bytesWritten = socket.bytesWritten;
177180
if (socket[symbols.kSocketConnectErrorTime]) {
178181
socketInfo.connectErrorTime = socket[symbols.kSocketConnectErrorTime];
179-
if (Array.isArray(socket.autoSelectFamilyAttemptedAddresses)) {
180-
socketInfo.attemptedRemoteAddresses = socket.autoSelectFamilyAttemptedAddresses;
181-
}
182182
socketInfo.connectProtocol = socket[symbols.kSocketConnectProtocol];
183183
socketInfo.connectHost = socket[symbols.kSocketConnectHost];
184184
socketInfo.connectPort = socket[symbols.kSocketConnectPort];

test/diagnostics_channel.test.ts

Lines changed: 138 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import { strict as assert } from 'node:assert';
22
import diagnosticsChannel from 'node:diagnostics_channel';
33
import { setTimeout as sleep } from 'node:timers/promises';
4+
import { createSecureServer } from 'node:http2';
5+
import { once } from 'node:events';
46
import { describe, it, beforeEach, afterEach } from 'vitest';
5-
import urllib from '../src/index.js';
7+
import pem from 'https-pem';
8+
import urllib, { HttpClient } from '../src/index.js';
69
import type {
710
RequestDiagnosticsMessage,
811
ResponseDiagnosticsMessage,
@@ -138,6 +141,140 @@ describe('diagnostics_channel.test.ts', () => {
138141
diagnosticsChannel.unsubscribe('undici:request:trailers', onMessage);
139142
});
140143

144+
it('should support trace socket info with H2 by undici:client:sendHeaders and undici:request:trailers', async () => {
145+
const server = createSecureServer(pem);
146+
server.on('stream', (stream, headers) => {
147+
stream.respond({
148+
'content-type': 'text/plain; charset=utf-8',
149+
'x-custom-h2': 'hello',
150+
':status': 200,
151+
});
152+
if (headers[':method'] !== 'HEAD') {
153+
stream.end('hello h2!');
154+
}
155+
});
156+
157+
server.listen(0);
158+
await once(server, 'listening');
159+
160+
const kRequests = Symbol('requests');
161+
let lastRequestOpaque: any;
162+
let kHandler: any;
163+
function onMessage(message: any, name: string | symbol) {
164+
if (name === 'undici:client:connected') {
165+
// console.log('%s %j', name, message.connectParams);
166+
message.socket[kRequests] = 0;
167+
return;
168+
}
169+
const { request, socket } = message;
170+
if (!kHandler) {
171+
const symbols = Object.getOwnPropertySymbols(request);
172+
for (const symbol of symbols) {
173+
if (symbol.description === 'handler') {
174+
kHandler = symbol;
175+
break;
176+
}
177+
}
178+
}
179+
const handler = request[kHandler];
180+
let opaque = handler.opaque || handler.opts?.opaque;
181+
assert(opaque);
182+
opaque = opaque[symbols.kRequestOriginalOpaque];
183+
if (opaque && name === 'undici:client:sendHeaders' && socket) {
184+
socket[kRequests]++;
185+
opaque.tracer.socket = {
186+
localAddress: socket.localAddress,
187+
localPort: socket.localPort,
188+
remoteAddress: socket.remoteAddress,
189+
remotePort: socket.remotePort,
190+
remoteFamily: socket.remoteFamily,
191+
timeout: socket.timeout,
192+
bytesWritten: socket.bytesWritten,
193+
bytesRead: socket.bytesRead,
194+
requests: socket[kRequests],
195+
};
196+
}
197+
// console.log('%s emit, %s %s, opaque: %j', name, request.method, request.origin, opaque);
198+
lastRequestOpaque = opaque;
199+
// console.log(request);
200+
}
201+
diagnosticsChannel.subscribe('undici:client:connected', onMessage);
202+
diagnosticsChannel.subscribe('undici:client:sendHeaders', onMessage);
203+
diagnosticsChannel.subscribe('undici:request:trailers', onMessage);
204+
205+
const httpClient = new HttpClient({
206+
allowH2: true,
207+
connect: {
208+
rejectUnauthorized: false,
209+
},
210+
});
211+
212+
let traceId = `mock-traceid-${Date.now()}`;
213+
_url = `https://localhost:${server.address().port}`;
214+
let response = await httpClient.request(`${_url}?head=true`, {
215+
method: 'HEAD',
216+
opaque: {
217+
tracer: { traceId },
218+
},
219+
});
220+
assert.equal(response.status, 200);
221+
assert(response.url.startsWith(_url));
222+
assert(!response.redirected);
223+
assert.equal(lastRequestOpaque.tracer.traceId, traceId);
224+
assert(lastRequestOpaque.tracer.socket);
225+
assert.equal(lastRequestOpaque.tracer.socket.requests, 1);
226+
227+
// HEAD, GET 请求都走同一个 http2 session socket
228+
await sleep(1);
229+
traceId = `mock-traceid-${Date.now()}`;
230+
response = await httpClient.request(_url, {
231+
method: 'GET',
232+
opaque: {
233+
tracer: { traceId },
234+
},
235+
});
236+
assert.equal(response.status, 200);
237+
assert.equal(lastRequestOpaque.tracer.traceId, traceId);
238+
assert(lastRequestOpaque.tracer.socket);
239+
assert.equal(lastRequestOpaque.tracer.socket.requests, 2);
240+
241+
await sleep(1);
242+
traceId = `mock-traceid-${Date.now()}`;
243+
response = await httpClient.request(_url, {
244+
method: 'GET',
245+
opaque: {
246+
tracer: { traceId },
247+
},
248+
});
249+
assert.equal(response.status, 200);
250+
assert.equal(lastRequestOpaque.tracer.traceId, traceId);
251+
assert(lastRequestOpaque.tracer.socket);
252+
assert.equal(lastRequestOpaque.tracer.socket.requests, 3);
253+
254+
// socket 复用 1000 次
255+
let count = 1000;
256+
while (count-- > 0) {
257+
await sleep(1);
258+
traceId = `mock-traceid-${Date.now()}`;
259+
response = await httpClient.request(`${_url}?count=${count}`, {
260+
method: 'GET',
261+
opaque: {
262+
tracer: { traceId },
263+
},
264+
});
265+
assert.equal(response.status, 200);
266+
assert.equal(lastRequestOpaque.tracer.traceId, traceId);
267+
assert(lastRequestOpaque.tracer.socket);
268+
assert.equal(lastRequestOpaque.tracer.socket.requests, 3 + 1000 - count);
269+
}
270+
assert.equal(lastRequestOpaque.tracer.socket.requests, 1003);
271+
272+
diagnosticsChannel.unsubscribe('undici:client:connected', onMessage);
273+
diagnosticsChannel.unsubscribe('undici:client:sendHeaders', onMessage);
274+
diagnosticsChannel.unsubscribe('undici:request:trailers', onMessage);
275+
server.close();
276+
});
277+
141278
it('should support trace request by urllib:request and urllib:response', async () => {
142279
let lastRequestOpaque: any;
143280
let socket: any;

0 commit comments

Comments
 (0)