Skip to content

Commit

Permalink
feat: support HTTP2 (#516)
Browse files Browse the repository at this point in the history
closes #474
  • Loading branch information
fengmk2 authored Jun 26, 2024
1 parent b414140 commit d1e4add
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 3 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,22 @@ Response is normal object, it contains:
NODE_DEBUG=urllib:* npm test
```

## Request with HTTP2

Create a HttpClient with `options.allowH2 = true`

```ts
import { HttpClient } from 'urllib';

const httpClient = new HttpClient({
allowH2: true,
});

const response = await httpClient.request('https://node.js.org');
console.log(response.status);
console.log(response.headers);
```

## Mocking Request

export from [undici](https://undici.nodejs.org/#/docs/best-practices/mocking-request)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
"test-keepalive": "cross-env TEST_KEEPALIVE_COUNT=50 vitest run --test-timeout 180000 keep-alive-header.test.ts",
"cov": "vitest run --coverage",
"preci": "node scripts/pre_test.js",
"ci": "npm run lint && npm run cov && node scripts/build_test.js && attw --pack",
"ci": "npm run lint && npm run cov && node scripts/build_test.js && attw --pack --ignore-rules no-resolution",
"contributor": "git-contributor",
"clean": "rm -rf dist",
"prepublishOnly": "npm run build"
Expand Down
3 changes: 2 additions & 1 deletion src/HttpAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export type HttpAgentOptions = {
lookup?: LookupFunction;
checkAddress?: CheckAddressFunction;
connect?: buildConnector.BuildOptions,
allowH2?: boolean;
};

class IllegalAddressError extends Error {
Expand Down Expand Up @@ -62,7 +63,7 @@ export class HttpAgent extends Agent {
});
};
super({
connect: { ...options.connect, lookup },
connect: { ...options.connect, lookup, allowH2: options.allowH2 },
});
this.#checkAddress = options.checkAddress;
}
Expand Down
9 changes: 9 additions & 0 deletions src/HttpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ const isNode14Or16 = /v1[46]\./.test(process.version);

export type ClientOptions = {
defaultArgs?: RequestOptions;
/** Allow to use HTTP2 first. Default is `false` */
allowH2?: boolean;
/**
* Custom DNS lookup function, default is `dns.lookup`.
*/
Expand Down Expand Up @@ -187,10 +189,17 @@ export class HttpClient extends EventEmitter {
lookup: clientOptions.lookup,
checkAddress: clientOptions.checkAddress,
connect: clientOptions.connect,
allowH2: clientOptions.allowH2,
});
} else if (clientOptions?.connect) {
this.#dispatcher = new Agent({
connect: clientOptions.connect,
allowH2: clientOptions.allowH2,
});
} else if (clientOptions?.allowH2) {
// Support HTTP2
this.#dispatcher = new Agent({
allowH2: clientOptions.allowH2,
});
}
initDiagnosticsChannel();
Expand Down
70 changes: 69 additions & 1 deletion test/HttpClient.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { strict as assert } from 'node:assert';
import dns from 'node:dns';
import { sensitiveHeaders } from 'node:http2';
import { describe, it, beforeAll, afterAll } from 'vitest';
import { HttpClient } from '../src';
import { HttpClient, getGlobalDispatcher } from '../src';
import { RawResponseWithMeta } from '../src/Response';
import { startServer } from './fixtures/server';

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

describe('clientOptions.allowH2', () => {
it('should work with allowH2 = true', async () => {
const httpClient = new HttpClient({
allowH2: true,
});
assert(httpClient);
let response = await httpClient.request('https://registry.npmmirror.com/urllib');
assert.equal(response.status, 200);
assert.equal(sensitiveHeaders in response.headers, true);
assert.equal(response.headers['content-type'], 'application/json; charset=utf-8');
assert.notEqual(httpClient.getDispatcher(), getGlobalDispatcher());
response = await httpClient.request('https://registry.npmmirror.com/urllib');
assert.equal(response.status, 200);
assert.equal(sensitiveHeaders in response.headers, true);
assert.equal(response.headers['content-type'], 'application/json; charset=utf-8');
response = await httpClient.request('https://registry.npmmirror.com/urllib');
assert.equal(response.status, 200);
assert.equal(sensitiveHeaders in response.headers, true);
assert.equal(response.headers['content-type'], 'application/json; charset=utf-8');
response = await httpClient.request('https://registry.npmmirror.com/urllib');
assert.equal(response.status, 200);
assert.equal(sensitiveHeaders in response.headers, true);
assert.equal(response.headers['content-type'], 'application/json; charset=utf-8');
response = await httpClient.request('https://registry.npmmirror.com/urllib');
assert.equal(response.status, 200);
assert.equal(sensitiveHeaders in response.headers, true);
assert.equal(response.headers['content-type'], 'application/json; charset=utf-8');
await Promise.all([
httpClient.request('https://registry.npmmirror.com/urllib'),
httpClient.request('https://registry.npmmirror.com/urllib'),
httpClient.request('https://registry.npmmirror.com/urllib'),
httpClient.request('https://registry.npmmirror.com/urllib'),
]);

// should request http 1.1 server work
let response2 = await httpClient.request(_url);
assert.equal(response2.status, 200);
assert.equal(sensitiveHeaders in response2.headers, false);
assert.equal(response2.headers['content-type'], 'application/json');
response2 = await httpClient.request(_url);
assert.equal(response2.status, 200);
assert.equal(sensitiveHeaders in response2.headers, false);
assert.equal(response2.headers['content-type'], 'application/json');
response2 = await httpClient.request(_url);
assert.equal(response2.status, 200);
assert.equal(sensitiveHeaders in response2.headers, false);
assert.equal(response2.headers['content-type'], 'application/json');
response2 = await httpClient.request(_url);
assert.equal(response2.status, 200);
assert.equal(sensitiveHeaders in response2.headers, false);
assert.equal(response2.headers['content-type'], 'application/json');
response2 = await httpClient.request(_url);
assert.equal(response2.status, 200);
assert.equal(sensitiveHeaders in response2.headers, false);
assert.equal(response2.headers['content-type'], 'application/json');
await Promise.all([
httpClient.request(_url),
httpClient.request(_url),
httpClient.request(_url),
httpClient.request(_url),
]);
console.log(httpClient.getDispatcherPoolStats());
assert.equal(httpClient.getDispatcherPoolStats()['https://registry.npmmirror.com'].connected, 1);
assert(httpClient.getDispatcherPoolStats()[_url.substring(0, _url.length - 1)].connected > 1);
});
});

describe('clientOptions.defaultArgs', () => {
it('should work with custom defaultArgs', async () => {
const httpclient = new HttpClient({ defaultArgs: { timeout: 1000 } });
Expand Down

0 comments on commit d1e4add

Please sign in to comment.