Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/nodejs-3.x.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ jobs:
uses: node-modules/github-actions/.github/workflows/node-test.yml@master
with:
os: 'ubuntu-latest, macos-latest, windows-latest'
version: '14.19.3, 14, 16, 18, 20, 22'
# skip 16: TypeError: crypto$2.getRandomValues is not a function
version: '14.19.3, 14, 18, 20, 22'
secrets:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
"git-contributor": "^2.0.0",
"iconv-lite": "^0.6.3",
"proxy": "^1.0.2",
"selfsigned": "^2.0.1",
"selfsigned": "^2.4.1",
"tar-stream": "^2.2.0",
"tshy": "^1.0.0",
"tshy-after": "^1.0.0",
Expand Down
5 changes: 3 additions & 2 deletions src/HttpClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,9 @@ export type ClientOptions = {
*/
cert?: string | Buffer;
/**
* If true, the server certificate is verified against the list of supplied CAs.
* An 'error' event is emitted if verification fails.Default: true.
* If `true`, the server certificate is verified against the list of supplied CAs.
* An 'error' event is emitted if verification fails.
* Default: `true`
*/
rejectUnauthorized?: boolean;
/**
Expand Down
54 changes: 50 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,62 @@ import { HttpClient, HEADER_USER_AGENT } from './HttpClient.js';
import { RequestOptions, RequestURL } from './Request.js';

let httpClient: HttpClient;
let allowH2HttpClient: HttpClient;
let allowUnauthorizedHttpClient: HttpClient;
let allowH2AndUnauthorizedHttpClient: HttpClient;
const domainSocketHttpClients = new LRU(50);

export function getDefaultHttpClient(): HttpClient {
export function getDefaultHttpClient(rejectUnauthorized?: boolean, allowH2?: boolean): HttpClient {
if (rejectUnauthorized === false) {
if (allowH2) {
if (!allowH2AndUnauthorizedHttpClient) {
allowH2AndUnauthorizedHttpClient = new HttpClient({
allowH2,
connect: {
rejectUnauthorized,
},
});
}
return allowH2AndUnauthorizedHttpClient;
}

if (!allowUnauthorizedHttpClient) {
allowUnauthorizedHttpClient = new HttpClient({
connect: {
rejectUnauthorized,
},
});
}
return allowUnauthorizedHttpClient;
}

if (allowH2) {
if (!allowH2HttpClient) {
allowH2HttpClient = new HttpClient({
allowH2,
});
}
return allowH2HttpClient;
}

if (!httpClient) {
httpClient = new HttpClient();
}
return httpClient;
}

export async function request<T = any>(url: RequestURL, options?: RequestOptions) {
export interface UrllibRequestOptions extends RequestOptions {
/**
* If `true`, the server certificate is verified against the list of supplied CAs.
* An 'error' event is emitted if verification fails.
* Default: `true`
*/
rejectUnauthorized?: boolean;
/** Allow to use HTTP2 first. Default is `false` */
allowH2?: boolean;
}

export async function request<T = any>(url: RequestURL, options?: UrllibRequestOptions) {
if (options?.socketPath) {
let domainSocketHttpclient = domainSocketHttpClients.get<HttpClient>(options.socketPath);
if (!domainSocketHttpclient) {
Expand All @@ -24,15 +70,15 @@ export async function request<T = any>(url: RequestURL, options?: RequestOptions
return await domainSocketHttpclient.request<T>(url, options);
}

return await getDefaultHttpClient().request<T>(url, options);
return await getDefaultHttpClient(options?.rejectUnauthorized, options?.allowH2).request<T>(url, options);
}

// export curl method is keep compatible with urllib.curl()
// ```ts
// import * as urllib from 'urllib';
// urllib.curl(url);
// ```
export async function curl<T = any>(url: RequestURL, options?: RequestOptions) {
export async function curl<T = any>(url: RequestURL, options?: UrllibRequestOptions) {
return await request<T>(url, options);
}

Expand Down
2 changes: 1 addition & 1 deletion test/options.compressed.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ describe('options.compressed.test.ts', () => {
// console.error(err);
assert.equal(err.name, 'UnzipError');
assert.equal(err.message, 'Decompression failed');
if (nodeMajorVersion() >= 20) {
if (nodeMajorVersion() >= 18) {
assert.equal(err.code, 'ERR__ERROR_FORMAT_PADDING_1');
} else {
assert.equal(err.code, 'ERR_PADDING_1');
Expand Down
19 changes: 19 additions & 0 deletions test/urllib.options.allowH2.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { strict as assert } from 'node:assert';
import { describe, it } from 'vitest';
import urllib from '../src/index.js';

describe('urllib.options.allowH2.test.ts', () => {
it('should 200 on options.allowH2 = true', async () => {
let response = await urllib.request('https://registry.npmmirror.com', {
allowH2: true,
dataType: 'json',
});
assert.equal(response.status, 200);

response = await urllib.curl('https://registry.npmmirror.com', {
allowH2: true,
dataType: 'json',
});
assert.equal(response.status, 200);
});
});
71 changes: 71 additions & 0 deletions test/urllib.options.rejectUnauthorized-false.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { strict as assert } from 'node:assert';
import { once } from 'node:events';
import { createSecureServer } from 'node:http2';
import { describe, it, beforeAll, afterAll } from 'vitest';
import selfsigned from 'selfsigned';
import urllib, { HttpClient } from '../src/index.js';
import { startServer } from './fixtures/server.js';

describe('urllib.options.rejectUnauthorized-false.test.ts', () => {
let close: any;
let _url: string;
beforeAll(async () => {
const { closeServer, url } = await startServer({ https: true });
close = closeServer;
_url = url;
});

afterAll(async () => {
await close();
});

it('should 200 on options.rejectUnauthorized = false', async () => {
const response = await urllib.request(_url, {
rejectUnauthorized: false,
dataType: 'json',
});
assert.equal(response.status, 200);
assert.equal(response.data.method, 'GET');
});

it('should 200 with H2 on options.rejectUnauthorized = false', async () => {
const pem = selfsigned.generate();
const server = createSecureServer({
key: pem.private,
cert: pem.cert,
});

server.on('stream', (stream, headers) => {
assert.equal(headers[':method'], 'GET');
stream.respond({
'content-type': 'text/plain; charset=utf-8',
'x-custom-h2': 'hello',
':status': 200,
});
stream.end('hello h2!');
});

server.listen(0);
await once(server, 'listening');

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

const url = `https://localhost:${server.address()!.port}`;
const response1 = await httpClient.request(url, {});
assert.equal(response1.status, 200);
assert.equal(response1.data.toString(), 'hello h2!');

const response2 = await urllib.request(url, {
rejectUnauthorized: false,
allowH2: true,
dataType: 'text',
});
assert.equal(response2.status, 200);
assert.equal(response2.data, 'hello h2!');
});
});