Skip to content

Commit 4358fb2

Browse files
authored
Merge branch 'master' into test-sendHeaders
2 parents fdf7f07 + 8cf5c3b commit 4358fb2

17 files changed

+288
-54
lines changed

CHANGELOG.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,40 @@
11
# Changelog
22

3+
## [4.6.4](https://github.com/node-modules/urllib/compare/v4.6.3...v4.6.4) (2024-12-06)
4+
5+
6+
### Bug Fixes
7+
8+
* export WebFormData ([#559](https://github.com/node-modules/urllib/issues/559)) ([dec6b12](https://github.com/node-modules/urllib/commit/dec6b1248ef68c1679f3d7f32c5544655e726045))
9+
10+
## [4.6.3](https://github.com/node-modules/urllib/compare/v4.6.2...v4.6.3) (2024-12-05)
11+
12+
13+
### Bug Fixes
14+
15+
* only set extend notation on non-ascii filename ([#558](https://github.com/node-modules/urllib/issues/558)) ([0cd9b06](https://github.com/node-modules/urllib/commit/0cd9b06031eb5bed08677b6503a8a0fba4ac7fd8))
16+
17+
## [4.6.2](https://github.com/node-modules/urllib/compare/v4.6.1...v4.6.2) (2024-12-04)
18+
19+
20+
### Bug Fixes
21+
22+
* fix socket info if fetch failed ([#556](https://github.com/node-modules/urllib/issues/556)) ([e9f4258](https://github.com/node-modules/urllib/commit/e9f425885aaa51258180048c5178d62af6da91d3))
23+
24+
## [4.6.1](https://github.com/node-modules/urllib/compare/v4.6.0...v4.6.1) (2024-12-04)
25+
26+
27+
### Bug Fixes
28+
29+
* fix socket info in response ([#555](https://github.com/node-modules/urllib/issues/555)) ([629c7a3](https://github.com/node-modules/urllib/commit/629c7a304998f921b5a9678808be3f28ac267f81))
30+
31+
## [4.6.0](https://github.com/node-modules/urllib/compare/v4.5.1...v4.6.0) (2024-12-04)
32+
33+
34+
### Features
35+
36+
* exports undici ([#554](https://github.com/node-modules/urllib/issues/554)) ([3c9fca7](https://github.com/node-modules/urllib/commit/3c9fca7c339753fa6d6430fb5659e6d11fc20997))
37+
338
## [4.5.1](https://github.com/node-modules/urllib/compare/v4.5.0...v4.5.1) (2024-12-02)
439

540

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "urllib",
3-
"version": "4.5.1",
3+
"version": "4.6.4",
44
"publishConfig": {
55
"tag": "latest"
66
},

src/BaseAgent.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import {
2+
Agent,
3+
Dispatcher,
4+
} from 'undici';
5+
import { AsyncLocalStorage } from 'node:async_hooks';
6+
import { FetchOpaque } from './FetchOpaqueInterceptor.js';
7+
8+
export interface BaseAgentOptions extends Agent.Options {
9+
opaqueLocalStorage?: AsyncLocalStorage<FetchOpaque>;
10+
}
11+
12+
export class BaseAgent extends Agent {
13+
#opaqueLocalStorage?: AsyncLocalStorage<FetchOpaque>;
14+
15+
constructor(options: BaseAgentOptions) {
16+
super(options);
17+
this.#opaqueLocalStorage = options.opaqueLocalStorage;
18+
}
19+
20+
dispatch(options: Agent.DispatchOptions, handler: Dispatcher.DispatchHandler): boolean {
21+
const opaque = this.#opaqueLocalStorage?.getStore();
22+
if (opaque) {
23+
(handler as any).opaque = opaque;
24+
}
25+
return super.dispatch(options, handler);
26+
}
27+
}

src/FetchOpaqueInterceptor.ts

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// const { AsyncLocalStorage } = require('node:async_hooks');
22
import { AsyncLocalStorage } from 'node:async_hooks';
33
import symbols from './symbols.js';
4-
import { Dispatcher } from 'undici';
54

65
// const RedirectHandler = require('../handler/redirect-handler')
76

@@ -28,14 +27,3 @@ export interface FetchOpaque {
2827
export interface OpaqueInterceptorOptions {
2928
opaqueLocalStorage: AsyncLocalStorage<FetchOpaque>;
3029
}
31-
32-
export function fetchOpaqueInterceptor(opts: OpaqueInterceptorOptions) {
33-
const opaqueLocalStorage = opts?.opaqueLocalStorage;
34-
return (dispatch: Dispatcher['dispatch']): Dispatcher['dispatch'] => {
35-
return function redirectInterceptor(opts: Dispatcher.DispatchOptions, handler: Dispatcher.DispatchHandler) {
36-
const opaque = opaqueLocalStorage?.getStore();
37-
(handler as any).opaque = opaque;
38-
return dispatch(opts, handler);
39-
};
40-
};
41-
}

src/FormData.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import path from 'node:path';
22
import _FormData from 'form-data';
33

4+
// eslint-disable-next-line
5+
const NON_ASCII_RE = /[^\x00-\x7F]/i;
6+
47
export class FormData extends _FormData {
58
_getContentDisposition(value: any, options: any) {
69
// support non-ascii filename
@@ -24,7 +27,10 @@ export class FormData extends _FormData {
2427
if (filename) {
2528
// https://datatracker.ietf.org/doc/html/rfc6266#section-4.1
2629
// support non-ascii filename
27-
contentDisposition = 'filename="' + filename + '"; filename*=UTF-8\'\'' + encodeURIComponent(filename);
30+
contentDisposition = 'filename="' + filename + '"';
31+
if (NON_ASCII_RE.test(filename)) {
32+
contentDisposition += '; filename*=UTF-8\'\'' + encodeURIComponent(filename);
33+
}
2834
}
2935

3036
return contentDisposition;

src/HttpAgent.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import {
55
Dispatcher,
66
buildConnector,
77
} from 'undici';
8+
import { BaseAgent, BaseAgentOptions } from './BaseAgent.js';
89

910
export type CheckAddressFunction = (ip: string, family: number | string, hostname: string) => boolean;
1011

11-
export interface HttpAgentOptions extends Agent.Options {
12+
export interface HttpAgentOptions extends BaseAgentOptions {
1213
lookup?: LookupFunction;
1314
checkAddress?: CheckAddressFunction;
1415
connect?: buildConnector.BuildOptions,
@@ -31,7 +32,7 @@ class IllegalAddressError extends Error {
3132
}
3233
}
3334

34-
export class HttpAgent extends Agent {
35+
export class HttpAgent extends BaseAgent {
3536
#checkAddress?: CheckAddressFunction;
3637

3738
constructor(options: HttpAgentOptions) {

src/HttpClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -300,7 +300,7 @@ export class HttpClient extends EventEmitter {
300300
// socket assigned
301301
queuing: 0,
302302
// dns lookup time
303-
// dnslookup: 0,
303+
dnslookup: 0,
304304
// socket connected
305305
connected: 0,
306306
// request headers sent

src/Response.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ export type Timing = {
2828
// socket assigned
2929
queuing: number;
3030
// dns lookup time
31-
// dnslookup: number;
31+
dnslookup: number;
3232
// socket connected
3333
connected: number;
3434
// request headers sent

src/diagnosticsChannel.ts

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,9 @@ export function initDiagnosticsChannel() {
8989
const opaque = getRequestOpaque(request, kHandler);
9090
// ignore non HttpClient Request
9191
if (!opaque || !opaque[symbols.kRequestId]) return;
92-
debug('[%s] Request#%d %s %s, path: %s, headers: %o',
92+
93+
Reflect.set(request, symbols.kRequestInternalOpaque, opaque);
94+
debug('[%s] Request#%d %s %s, path: %s, headers: %j',
9395
name, opaque[symbols.kRequestId], request.method, request.origin, request.path, request.headers);
9496
if (!opaque[symbols.kEnableRequestTiming]) return;
9597
opaque[symbols.kRequestTiming].queuing = performanceTime(opaque[symbols.kRequestStartTime]);
@@ -114,10 +116,10 @@ export function initDiagnosticsChannel() {
114116
sock[symbols.kSocketConnectProtocol] = connectParams.protocol;
115117
sock[symbols.kSocketConnectHost] = connectParams.host;
116118
sock[symbols.kSocketConnectPort] = connectParams.port;
117-
debug('[%s] Socket#%d connectError, connectParams: %o, error: %s, (sock: %o)',
119+
debug('[%s] Socket#%d connectError, connectParams: %j, error: %s, (sock: %j)',
118120
name, sock[symbols.kSocketId], connectParams, (error as Error).message, formatSocket(sock));
119121
} else {
120-
debug('[%s] connectError, connectParams: %o, error: %o',
122+
debug('[%s] connectError, connectParams: %j, error: %o',
121123
name, connectParams, error);
122124
}
123125
});
@@ -136,13 +138,13 @@ export function initDiagnosticsChannel() {
136138
socket[symbols.kSocketConnectProtocol] = connectParams.protocol;
137139
socket[symbols.kSocketConnectHost] = connectParams.host;
138140
socket[symbols.kSocketConnectPort] = connectParams.port;
139-
debug('[%s] Socket#%d connected (sock: %o)', name, socket[symbols.kSocketId], formatSocket(socket));
141+
debug('[%s] Socket#%d connected (sock: %j)', name, socket[symbols.kSocketId], formatSocket(socket));
140142
});
141143

142144
// This message is published right before the first byte of the request is written to the socket.
143145
subscribe('undici:client:sendHeaders', (message, name) => {
144146
const { request, socket } = message as DiagnosticsChannel.ClientSendHeadersMessage & { socket: SocketExtend };
145-
const opaque = getRequestOpaque(request, kHandler);
147+
const opaque = Reflect.get(request, symbols.kRequestInternalOpaque);
146148
if (!opaque || !opaque[symbols.kRequestId]) {
147149
debug('[%s] opaque not found', name);
148150
return;
@@ -151,7 +153,7 @@ export function initDiagnosticsChannel() {
151153
(socket[symbols.kHandledRequests] as number)++;
152154
// attach socket to opaque
153155
opaque[symbols.kRequestSocket] = socket;
154-
debug('[%s] Request#%d send headers on Socket#%d (handled %d requests, sock: %o)',
156+
debug('[%s] Request#%d send headers on Socket#%d (handled %d requests, sock: %j)',
155157
name, opaque[symbols.kRequestId], socket[symbols.kSocketId], socket[symbols.kHandledRequests],
156158
formatSocket(socket));
157159

@@ -167,7 +169,7 @@ export function initDiagnosticsChannel() {
167169

168170
subscribe('undici:request:bodySent', (message, name) => {
169171
const { request } = message as DiagnosticsChannel.RequestBodySentMessage;
170-
const opaque = getRequestOpaque(request, kHandler);
172+
const opaque = Reflect.get(request, symbols.kRequestInternalOpaque);
171173
if (!opaque || !opaque[symbols.kRequestId]) {
172174
debug('[%s] opaque not found', name);
173175
return;
@@ -181,7 +183,7 @@ export function initDiagnosticsChannel() {
181183
// This message is published after the response headers have been received, i.e. the response has been completed.
182184
subscribe('undici:request:headers', (message, name) => {
183185
const { request, response } = message as DiagnosticsChannel.RequestHeadersMessage;
184-
const opaque = getRequestOpaque(request, kHandler);
186+
const opaque = Reflect.get(request, symbols.kRequestInternalOpaque);
185187
if (!opaque || !opaque[symbols.kRequestId]) {
186188
debug('[%s] opaque not found', name);
187189
return;
@@ -191,7 +193,7 @@ export function initDiagnosticsChannel() {
191193
const socket = opaque[symbols.kRequestSocket];
192194
if (socket) {
193195
socket[symbols.kHandledResponses]++;
194-
debug('[%s] Request#%d get %s response headers on Socket#%d (handled %d responses, sock: %o)',
196+
debug('[%s] Request#%d get %s response headers on Socket#%d (handled %d responses, sock: %j)',
195197
name, opaque[symbols.kRequestId], response.statusCode, socket[symbols.kSocketId], socket[symbols.kHandledResponses],
196198
formatSocket(socket));
197199
} else {
@@ -206,7 +208,7 @@ export function initDiagnosticsChannel() {
206208
// This message is published after the response body and trailers have been received, i.e. the response has been completed.
207209
subscribe('undici:request:trailers', (message, name) => {
208210
const { request } = message as DiagnosticsChannel.RequestTrailersMessage;
209-
const opaque = getRequestOpaque(request, kHandler);
211+
const opaque = Reflect.get(request, symbols.kRequestInternalOpaque);
210212
if (!opaque || !opaque[symbols.kRequestId]) {
211213
debug('[%s] opaque not found', name);
212214
return;

src/fetch.ts

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { AsyncLocalStorage } from 'node:async_hooks';
2+
import { debuglog } from 'node:util';
23
import {
34
fetch as UndiciFetch,
45
RequestInfo,
@@ -8,6 +9,7 @@ import {
89
Agent,
910
getGlobalDispatcher,
1011
Pool,
12+
Dispatcher,
1113
} from 'undici';
1214
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
1315
// @ts-ignore
@@ -35,9 +37,12 @@ import {
3537
HttpMethod,
3638
RequestMeta,
3739
} from './Request.js';
38-
import { FetchOpaque, fetchOpaqueInterceptor } from './FetchOpaqueInterceptor.js';
40+
import { FetchOpaque } from './FetchOpaqueInterceptor.js';
3941
import { RawResponseWithMeta, SocketInfo } from './Response.js';
4042
import { IncomingHttpHeaders } from './IncomingHttpHeaders.js';
43+
import { BaseAgent, BaseAgentOptions } from './BaseAgent.js';
44+
45+
const debug = debuglog('urllib:fetch');
4146

4247
export interface UrllibRequestInit extends RequestInit {
4348
// default is true
@@ -56,7 +61,7 @@ export type FetchResponseDiagnosticsMessage = {
5661
};
5762

5863
export class FetchFactory {
59-
static #dispatcher: Agent;
64+
static #dispatcher: Dispatcher.ComposedDispatcher;
6065
static #opaqueLocalStorage = new AsyncLocalStorage<FetchOpaque>();
6166

6267
static getDispatcher() {
@@ -68,17 +73,10 @@ export class FetchFactory {
6873
}
6974

7075
static setClientOptions(clientOptions: ClientOptions) {
71-
let dispatcherOption: Agent.Options = {
72-
interceptors: {
73-
Agent: [
74-
fetchOpaqueInterceptor({
75-
opaqueLocalStorage: FetchFactory.#opaqueLocalStorage,
76-
}),
77-
],
78-
Client: [],
79-
},
76+
let dispatcherOption: BaseAgentOptions = {
77+
opaqueLocalStorage: FetchFactory.#opaqueLocalStorage,
8078
};
81-
let dispatcherClazz: new (options: Agent.Options) => Agent = Agent;
79+
let dispatcherClazz: new (options: BaseAgentOptions) => BaseAgent = BaseAgent;
8280
if (clientOptions?.lookup || clientOptions?.checkAddress) {
8381
dispatcherOption = {
8482
...dispatcherOption,
@@ -87,21 +85,21 @@ export class FetchFactory {
8785
connect: clientOptions.connect,
8886
allowH2: clientOptions.allowH2,
8987
} as HttpAgentOptions;
90-
dispatcherClazz = HttpAgent as unknown as new (options: Agent.Options) => Agent;
88+
dispatcherClazz = HttpAgent as unknown as new (options: BaseAgentOptions) => BaseAgent;
9189
} else if (clientOptions?.connect) {
9290
dispatcherOption = {
9391
...dispatcherOption,
9492
connect: clientOptions.connect,
9593
allowH2: clientOptions.allowH2,
9694
} as HttpAgentOptions;
97-
dispatcherClazz = Agent;
95+
dispatcherClazz = BaseAgent;
9896
} else if (clientOptions?.allowH2) {
9997
// Support HTTP2
10098
dispatcherOption = {
10199
...dispatcherOption,
102100
allowH2: clientOptions.allowH2,
103101
} as HttpAgentOptions;
104-
dispatcherClazz = Agent;
102+
dispatcherClazz = BaseAgent;
105103
}
106104
FetchFactory.#dispatcher = new dispatcherClazz(dispatcherOption);
107105
initDiagnosticsChannel();
@@ -142,7 +140,7 @@ export class FetchFactory {
142140
// socket assigned
143141
queuing: 0,
144142
// dns lookup time
145-
// dnslookup: 0,
143+
dnslookup: 0,
146144
// socket connected
147145
connected: 0,
148146
// request headers sent
@@ -223,11 +221,14 @@ export class FetchFactory {
223221
res = await UndiciFetch(input, init);
224222
});
225223
} catch (e: any) {
226-
channels.response.publish({
224+
updateSocketInfo(socketInfo, internalOpaque, e);
225+
urllibResponse.rt = performanceTime(requestStartTime);
226+
debug('Request#%d throw error: %s', requestId, e);
227+
channels.fetchResponse.publish({
227228
fetch: fetchMeta,
228229
error: e,
229230
} as FetchResponseDiagnosticsMessage);
230-
channels.fetchResponse.publish({
231+
channels.response.publish({
231232
request: reqMeta,
232233
response: urllibResponse,
233234
error: e,
@@ -237,7 +238,7 @@ export class FetchFactory {
237238

238239
// get undici internal response
239240
const state = getResponseState(res!);
240-
updateSocketInfo(socketInfo, internalOpaque /* , rawError */);
241+
updateSocketInfo(socketInfo, internalOpaque);
241242

242243
urllibResponse.headers = convertHeader(res!.headers);
243244
urllibResponse.status = urllibResponse.statusCode = res!.status;
@@ -246,7 +247,8 @@ export class FetchFactory {
246247
urllibResponse.size = parseInt(urllibResponse.headers['content-length']);
247248
}
248249
urllibResponse.rt = performanceTime(requestStartTime);
249-
250+
debug('Request#%d got response, status: %s, headers: %j, timing: %j, socket: %j',
251+
requestId, urllibResponse.status, urllibResponse.headers, timing, urllibResponse.socket);
250252
channels.fetchResponse.publish({
251253
fetch: fetchMeta,
252254
timingInfo: state.timingInfo,

0 commit comments

Comments
 (0)