Skip to content

Commit d1e4add

Browse files
authored
feat: support HTTP2 (#516)
closes #474
1 parent b414140 commit d1e4add

File tree

5 files changed

+97
-3
lines changed

5 files changed

+97
-3
lines changed

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,22 @@ Response is normal object, it contains:
209209
NODE_DEBUG=urllib:* npm test
210210
```
211211

212+
## Request with HTTP2
213+
214+
Create a HttpClient with `options.allowH2 = true`
215+
216+
```ts
217+
import { HttpClient } from 'urllib';
218+
219+
const httpClient = new HttpClient({
220+
allowH2: true,
221+
});
222+
223+
const response = await httpClient.request('https://node.js.org');
224+
console.log(response.status);
225+
console.log(response.headers);
226+
```
227+
212228
## Mocking Request
213229

214230
export from [undici](https://undici.nodejs.org/#/docs/best-practices/mocking-request)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"test-keepalive": "cross-env TEST_KEEPALIVE_COUNT=50 vitest run --test-timeout 180000 keep-alive-header.test.ts",
4141
"cov": "vitest run --coverage",
4242
"preci": "node scripts/pre_test.js",
43-
"ci": "npm run lint && npm run cov && node scripts/build_test.js && attw --pack",
43+
"ci": "npm run lint && npm run cov && node scripts/build_test.js && attw --pack --ignore-rules no-resolution",
4444
"contributor": "git-contributor",
4545
"clean": "rm -rf dist",
4646
"prepublishOnly": "npm run build"

src/HttpAgent.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export type HttpAgentOptions = {
1212
lookup?: LookupFunction;
1313
checkAddress?: CheckAddressFunction;
1414
connect?: buildConnector.BuildOptions,
15+
allowH2?: boolean;
1516
};
1617

1718
class IllegalAddressError extends Error {
@@ -62,7 +63,7 @@ export class HttpAgent extends Agent {
6263
});
6364
};
6465
super({
65-
connect: { ...options.connect, lookup },
66+
connect: { ...options.connect, lookup, allowH2: options.allowH2 },
6667
});
6768
this.#checkAddress = options.checkAddress;
6869
}

src/HttpClient.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ const isNode14Or16 = /v1[46]\./.test(process.version);
7070

7171
export type ClientOptions = {
7272
defaultArgs?: RequestOptions;
73+
/** Allow to use HTTP2 first. Default is `false` */
74+
allowH2?: boolean;
7375
/**
7476
* Custom DNS lookup function, default is `dns.lookup`.
7577
*/
@@ -187,10 +189,17 @@ export class HttpClient extends EventEmitter {
187189
lookup: clientOptions.lookup,
188190
checkAddress: clientOptions.checkAddress,
189191
connect: clientOptions.connect,
192+
allowH2: clientOptions.allowH2,
190193
});
191194
} else if (clientOptions?.connect) {
192195
this.#dispatcher = new Agent({
193196
connect: clientOptions.connect,
197+
allowH2: clientOptions.allowH2,
198+
});
199+
} else if (clientOptions?.allowH2) {
200+
// Support HTTP2
201+
this.#dispatcher = new Agent({
202+
allowH2: clientOptions.allowH2,
194203
});
195204
}
196205
initDiagnosticsChannel();

test/HttpClient.test.ts

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { strict as assert } from 'node:assert';
22
import dns from 'node:dns';
3+
import { sensitiveHeaders } from 'node:http2';
34
import { describe, it, beforeAll, afterAll } from 'vitest';
4-
import { HttpClient } from '../src';
5+
import { HttpClient, getGlobalDispatcher } from '../src';
56
import { RawResponseWithMeta } from '../src/Response';
67
import { startServer } from './fixtures/server';
78

@@ -28,6 +29,73 @@ describe('HttpClient.test.ts', () => {
2829
});
2930
});
3031

32+
describe('clientOptions.allowH2', () => {
33+
it('should work with allowH2 = true', async () => {
34+
const httpClient = new HttpClient({
35+
allowH2: true,
36+
});
37+
assert(httpClient);
38+
let response = await httpClient.request('https://registry.npmmirror.com/urllib');
39+
assert.equal(response.status, 200);
40+
assert.equal(sensitiveHeaders in response.headers, true);
41+
assert.equal(response.headers['content-type'], 'application/json; charset=utf-8');
42+
assert.notEqual(httpClient.getDispatcher(), getGlobalDispatcher());
43+
response = await httpClient.request('https://registry.npmmirror.com/urllib');
44+
assert.equal(response.status, 200);
45+
assert.equal(sensitiveHeaders in response.headers, true);
46+
assert.equal(response.headers['content-type'], 'application/json; charset=utf-8');
47+
response = await httpClient.request('https://registry.npmmirror.com/urllib');
48+
assert.equal(response.status, 200);
49+
assert.equal(sensitiveHeaders in response.headers, true);
50+
assert.equal(response.headers['content-type'], 'application/json; charset=utf-8');
51+
response = await httpClient.request('https://registry.npmmirror.com/urllib');
52+
assert.equal(response.status, 200);
53+
assert.equal(sensitiveHeaders in response.headers, true);
54+
assert.equal(response.headers['content-type'], 'application/json; charset=utf-8');
55+
response = await httpClient.request('https://registry.npmmirror.com/urllib');
56+
assert.equal(response.status, 200);
57+
assert.equal(sensitiveHeaders in response.headers, true);
58+
assert.equal(response.headers['content-type'], 'application/json; charset=utf-8');
59+
await Promise.all([
60+
httpClient.request('https://registry.npmmirror.com/urllib'),
61+
httpClient.request('https://registry.npmmirror.com/urllib'),
62+
httpClient.request('https://registry.npmmirror.com/urllib'),
63+
httpClient.request('https://registry.npmmirror.com/urllib'),
64+
]);
65+
66+
// should request http 1.1 server work
67+
let response2 = await httpClient.request(_url);
68+
assert.equal(response2.status, 200);
69+
assert.equal(sensitiveHeaders in response2.headers, false);
70+
assert.equal(response2.headers['content-type'], 'application/json');
71+
response2 = await httpClient.request(_url);
72+
assert.equal(response2.status, 200);
73+
assert.equal(sensitiveHeaders in response2.headers, false);
74+
assert.equal(response2.headers['content-type'], 'application/json');
75+
response2 = await httpClient.request(_url);
76+
assert.equal(response2.status, 200);
77+
assert.equal(sensitiveHeaders in response2.headers, false);
78+
assert.equal(response2.headers['content-type'], 'application/json');
79+
response2 = await httpClient.request(_url);
80+
assert.equal(response2.status, 200);
81+
assert.equal(sensitiveHeaders in response2.headers, false);
82+
assert.equal(response2.headers['content-type'], 'application/json');
83+
response2 = await httpClient.request(_url);
84+
assert.equal(response2.status, 200);
85+
assert.equal(sensitiveHeaders in response2.headers, false);
86+
assert.equal(response2.headers['content-type'], 'application/json');
87+
await Promise.all([
88+
httpClient.request(_url),
89+
httpClient.request(_url),
90+
httpClient.request(_url),
91+
httpClient.request(_url),
92+
]);
93+
console.log(httpClient.getDispatcherPoolStats());
94+
assert.equal(httpClient.getDispatcherPoolStats()['https://registry.npmmirror.com'].connected, 1);
95+
assert(httpClient.getDispatcherPoolStats()[_url.substring(0, _url.length - 1)].connected > 1);
96+
});
97+
});
98+
3199
describe('clientOptions.defaultArgs', () => {
32100
it('should work with custom defaultArgs', async () => {
33101
const httpclient = new HttpClient({ defaultArgs: { timeout: 1000 } });

0 commit comments

Comments
 (0)