@theia/request 1.71.0-next.8 → 1.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/common-request-service.d.ts +12 -1
- package/lib/common-request-service.d.ts.map +1 -1
- package/lib/common-request-service.js.map +1 -1
- package/lib/node-request-service.d.ts +2 -9
- package/lib/node-request-service.d.ts.map +1 -1
- package/lib/node-request-service.js +59 -87
- package/lib/node-request-service.js.map +1 -1
- package/{src/package.spec.ts → lib/node-request-service.spec.d.ts} +3 -14
- package/lib/node-request-service.spec.d.ts.map +1 -0
- package/lib/node-request-service.spec.js +233 -0
- package/lib/node-request-service.spec.js.map +1 -0
- package/lib/proxy.d.ts +2 -4
- package/lib/proxy.d.ts.map +1 -1
- package/lib/proxy.js +30 -13
- package/lib/proxy.js.map +1 -1
- package/lib/{package.spec.d.ts → proxy.spec.d.ts} +3 -2
- package/lib/proxy.spec.d.ts.map +1 -0
- package/lib/proxy.spec.js +85 -0
- package/lib/proxy.spec.js.map +1 -0
- package/package.json +4 -5
- package/src/common-request-service.ts +10 -1
- package/src/node-request-service.spec.ts +243 -0
- package/src/node-request-service.ts +65 -102
- package/src/proxy.spec.ts +94 -0
- package/src/proxy.ts +32 -17
- package/lib/package.spec.d.ts.map +0 -1
- package/lib/package.spec.js +0 -26
- package/lib/package.spec.js.map +0 -1
|
@@ -14,33 +14,20 @@
|
|
|
14
14
|
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
********************************************************************************/
|
|
16
16
|
|
|
17
|
-
import
|
|
18
|
-
import
|
|
19
|
-
import { getProxyAgent, ProxyAgent } from './proxy';
|
|
17
|
+
import type { Dispatcher } from 'undici';
|
|
18
|
+
import { getProxyAgent } from './proxy';
|
|
20
19
|
import { Headers, RequestConfiguration, RequestContext, RequestOptions, RequestService, CancellationToken } from './common-request-service';
|
|
21
|
-
import { createGunzip } from 'zlib';
|
|
22
|
-
|
|
23
|
-
export interface RawRequestFunction {
|
|
24
|
-
(options: http.RequestOptions, callback?: (res: http.IncomingMessage) => void): http.ClientRequest;
|
|
25
|
-
}
|
|
26
20
|
|
|
27
21
|
export interface NodeRequestOptions extends RequestOptions {
|
|
28
|
-
agent?: ProxyAgent | http.Agent | https.Agent | boolean;
|
|
29
22
|
strictSSL?: boolean;
|
|
30
|
-
|
|
31
|
-
}
|
|
23
|
+
dispatcher?: Dispatcher;
|
|
24
|
+
}
|
|
32
25
|
|
|
33
26
|
export class NodeRequestService implements RequestService {
|
|
34
27
|
protected proxyUrl?: string;
|
|
35
28
|
protected strictSSL?: boolean;
|
|
36
29
|
protected authorization?: string;
|
|
37
30
|
|
|
38
|
-
protected getNodeRequest(options: RequestOptions): RawRequestFunction {
|
|
39
|
-
const endpoint = new URL(options.url);
|
|
40
|
-
const module = endpoint.protocol === 'https:' ? https : http;
|
|
41
|
-
return module.request;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
31
|
protected async getProxyUrl(url: string): Promise<string | undefined> {
|
|
45
32
|
return this.proxyUrl;
|
|
46
33
|
}
|
|
@@ -60,11 +47,12 @@ export class NodeRequestService implements RequestService {
|
|
|
60
47
|
protected async processOptions(options: NodeRequestOptions): Promise<NodeRequestOptions> {
|
|
61
48
|
const { strictSSL } = this;
|
|
62
49
|
options.strictSSL = options.strictSSL ?? strictSSL;
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
50
|
+
if (!options.dispatcher) {
|
|
51
|
+
options.dispatcher = getProxyAgent(options.url || '', process.env, {
|
|
52
|
+
proxyUrl: await this.getProxyUrl(options.url),
|
|
53
|
+
strictSSL: options.strictSSL
|
|
54
|
+
});
|
|
55
|
+
}
|
|
68
56
|
|
|
69
57
|
const authorization = options.proxyAuthorization || this.authorization;
|
|
70
58
|
if (authorization) {
|
|
@@ -74,100 +62,75 @@ export class NodeRequestService implements RequestService {
|
|
|
74
62
|
};
|
|
75
63
|
}
|
|
76
64
|
|
|
77
|
-
options.headers = {
|
|
78
|
-
'Accept-Encoding': 'gzip',
|
|
79
|
-
...(options.headers || {}),
|
|
80
|
-
};
|
|
81
|
-
|
|
82
65
|
return options;
|
|
83
66
|
}
|
|
84
67
|
|
|
85
|
-
request(options: NodeRequestOptions, token?: CancellationToken): Promise<RequestContext> {
|
|
86
|
-
|
|
87
|
-
options = await this.processOptions(options);
|
|
88
|
-
|
|
89
|
-
const endpoint = new URL(options.url);
|
|
90
|
-
const rawRequest = options.getRawRequest
|
|
91
|
-
? options.getRawRequest(options)
|
|
92
|
-
: this.getNodeRequest(options);
|
|
93
|
-
|
|
94
|
-
const opts: https.RequestOptions = {
|
|
95
|
-
hostname: endpoint.hostname,
|
|
96
|
-
port: endpoint.port ? parseInt(endpoint.port) : (endpoint.protocol === 'https:' ? 443 : 80),
|
|
97
|
-
protocol: endpoint.protocol,
|
|
98
|
-
path: endpoint.pathname + endpoint.search,
|
|
99
|
-
method: options.type || 'GET',
|
|
100
|
-
headers: options.headers,
|
|
101
|
-
agent: options.agent as https.Agent,
|
|
102
|
-
rejectUnauthorized: !!options.strictSSL
|
|
103
|
-
};
|
|
68
|
+
async request(options: NodeRequestOptions, token?: CancellationToken): Promise<RequestContext> {
|
|
69
|
+
options = await this.processOptions(options);
|
|
104
70
|
|
|
105
|
-
|
|
106
|
-
opts.auth = options.user + ':' + options.password;
|
|
107
|
-
}
|
|
71
|
+
const headers: Record<string, string> = { ...(options.headers || {}) };
|
|
108
72
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
73
|
+
if (options.user && options.password) {
|
|
74
|
+
headers['Authorization'] = 'Basic ' + Buffer.from(options.user + ':' + options.password).toString('base64');
|
|
75
|
+
}
|
|
112
76
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
this.request({
|
|
118
|
-
...options,
|
|
119
|
-
url: res.headers.location,
|
|
120
|
-
followRedirects: followRedirects - 1
|
|
121
|
-
}, token).then(resolve, reject);
|
|
122
|
-
} else {
|
|
123
|
-
const chunks: Uint8Array[] = [];
|
|
124
|
-
|
|
125
|
-
const stream = res.headers['content-encoding'] === 'gzip' ? res.pipe(createGunzip()) : res;
|
|
126
|
-
|
|
127
|
-
stream.on('data', chunk => {
|
|
128
|
-
chunks.push(chunk);
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
stream.on('end', () => {
|
|
132
|
-
req.off('timeout', timeoutHandler);
|
|
133
|
-
const buffer = Buffer.concat(chunks);
|
|
134
|
-
resolve({
|
|
135
|
-
url: options.url,
|
|
136
|
-
res: {
|
|
137
|
-
headers: res.headers as Headers,
|
|
138
|
-
statusCode: res.statusCode
|
|
139
|
-
},
|
|
140
|
-
buffer
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
stream.on('error', err => {
|
|
145
|
-
reject(err);
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
});
|
|
77
|
+
const signals: AbortSignal[] = [];
|
|
78
|
+
if (options.timeout) {
|
|
79
|
+
signals.push(AbortSignal.timeout(options.timeout));
|
|
80
|
+
}
|
|
149
81
|
|
|
150
|
-
|
|
151
|
-
|
|
82
|
+
let tokenAbortController: AbortController | undefined;
|
|
83
|
+
let cancellationListener: void | { dispose(): void } | undefined;
|
|
84
|
+
if (token) {
|
|
85
|
+
tokenAbortController = new AbortController();
|
|
86
|
+
signals.push(tokenAbortController.signal);
|
|
87
|
+
cancellationListener = token.onCancellationRequested(() => {
|
|
88
|
+
tokenAbortController!.abort();
|
|
152
89
|
});
|
|
90
|
+
}
|
|
153
91
|
|
|
154
|
-
|
|
92
|
+
const signal = signals.length > 0
|
|
93
|
+
? (signals.length === 1 ? signals[0] : AbortSignal.any(signals))
|
|
94
|
+
: undefined;
|
|
155
95
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
96
|
+
const fetchOptions: RequestInit & { dispatcher?: Dispatcher } = {
|
|
97
|
+
method: options.type || 'GET',
|
|
98
|
+
headers,
|
|
99
|
+
redirect: options.followRedirects === 0 ? 'manual' : 'follow',
|
|
100
|
+
signal,
|
|
101
|
+
};
|
|
159
102
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
103
|
+
if (options.dispatcher) {
|
|
104
|
+
fetchOptions.dispatcher = options.dispatcher;
|
|
105
|
+
}
|
|
163
106
|
|
|
164
|
-
|
|
107
|
+
if (options.data) {
|
|
108
|
+
fetchOptions.body = options.data;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const response = await fetch(options.url, fetchOptions);
|
|
113
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
114
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
165
115
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
116
|
+
const responseHeaders: Headers = {};
|
|
117
|
+
response.headers.forEach((value, key) => {
|
|
118
|
+
responseHeaders[key] = value;
|
|
169
119
|
});
|
|
170
|
-
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
url: options.url,
|
|
123
|
+
res: {
|
|
124
|
+
headers: responseHeaders,
|
|
125
|
+
statusCode: response.status
|
|
126
|
+
},
|
|
127
|
+
buffer
|
|
128
|
+
};
|
|
129
|
+
} finally {
|
|
130
|
+
if (cancellationListener) {
|
|
131
|
+
cancellationListener.dispose();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
171
134
|
}
|
|
172
135
|
|
|
173
136
|
async resolveProxy(url: string): Promise<string | undefined> {
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (C) 2026 EclipseSource GmbH.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
* http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
*
|
|
8
|
+
* This Source Code may also be made available under the following Secondary
|
|
9
|
+
* Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
* with the GNU Classpath Exception which is available at
|
|
12
|
+
* https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
*
|
|
14
|
+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
+
********************************************************************************/
|
|
16
|
+
|
|
17
|
+
import { expect } from 'chai';
|
|
18
|
+
import { getProxyAgent } from './proxy';
|
|
19
|
+
|
|
20
|
+
describe('proxy', () => {
|
|
21
|
+
describe('getProxyAgent', () => {
|
|
22
|
+
it('should return undefined when no proxy is configured', () => {
|
|
23
|
+
const agent = getProxyAgent('https://example.com', {});
|
|
24
|
+
expect(agent).to.be.undefined;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('should return a dispatcher when proxy URL is provided via options', () => {
|
|
28
|
+
const agent = getProxyAgent('https://example.com', {}, {
|
|
29
|
+
proxyUrl: 'http://proxy.example.com:8080'
|
|
30
|
+
});
|
|
31
|
+
expect(agent).to.not.be.undefined;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('should return a dispatcher when HTTP_PROXY env variable is set for http URLs', () => {
|
|
35
|
+
const env = { HTTP_PROXY: 'http://proxy.example.com:8080' };
|
|
36
|
+
const agent = getProxyAgent('http://example.com', env);
|
|
37
|
+
expect(agent).to.not.be.undefined;
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it('should return a dispatcher when http_proxy env variable is set for http URLs', () => {
|
|
41
|
+
const env = { http_proxy: 'http://proxy.example.com:8080' };
|
|
42
|
+
const agent = getProxyAgent('http://example.com', env);
|
|
43
|
+
expect(agent).to.not.be.undefined;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('should return a dispatcher when HTTPS_PROXY env variable is set for https URLs', () => {
|
|
47
|
+
const env = { HTTPS_PROXY: 'http://proxy.example.com:8080' };
|
|
48
|
+
const agent = getProxyAgent('https://example.com', env);
|
|
49
|
+
expect(agent).to.not.be.undefined;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('should fall back to HTTP_PROXY for https URLs when HTTPS_PROXY is not set', () => {
|
|
53
|
+
const env = { HTTP_PROXY: 'http://proxy.example.com:8080' };
|
|
54
|
+
const agent = getProxyAgent('https://example.com', env);
|
|
55
|
+
expect(agent).to.not.be.undefined;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it('should return undefined for invalid request URLs', () => {
|
|
59
|
+
const agent = getProxyAgent('not-a-url', {}, {
|
|
60
|
+
proxyUrl: 'http://proxy.example.com:8080'
|
|
61
|
+
});
|
|
62
|
+
expect(agent).to.be.undefined;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should return undefined for non-http proxy protocols', () => {
|
|
66
|
+
const agent = getProxyAgent('https://example.com', {}, {
|
|
67
|
+
proxyUrl: 'socks5://proxy.example.com:1080'
|
|
68
|
+
});
|
|
69
|
+
expect(agent).to.be.undefined;
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('should return undefined for invalid proxy URLs', () => {
|
|
73
|
+
const agent = getProxyAgent('https://example.com', {}, {
|
|
74
|
+
proxyUrl: 'not-a-valid-url'
|
|
75
|
+
});
|
|
76
|
+
expect(agent).to.be.undefined;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('should return an Agent (not ProxyAgent) when strictSSL is false and no proxy is set', () => {
|
|
80
|
+
const agent = getProxyAgent('https://example.com', {}, {
|
|
81
|
+
strictSSL: false
|
|
82
|
+
});
|
|
83
|
+
expect(agent).to.not.be.undefined;
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should prefer proxyUrl option over environment variables', () => {
|
|
87
|
+
const env = { HTTP_PROXY: 'http://env-proxy.example.com:8080' };
|
|
88
|
+
const agent = getProxyAgent('http://example.com', env, {
|
|
89
|
+
proxyUrl: 'http://option-proxy.example.com:9090'
|
|
90
|
+
});
|
|
91
|
+
expect(agent).to.not.be.undefined;
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
});
|
package/src/proxy.ts
CHANGED
|
@@ -14,13 +14,9 @@
|
|
|
14
14
|
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
15
|
********************************************************************************/
|
|
16
16
|
|
|
17
|
-
import {
|
|
18
|
-
import * as httpAgent from 'http-proxy-agent';
|
|
19
|
-
import * as httpsAgent from 'https-proxy-agent';
|
|
17
|
+
import { ProxyAgent, Agent, Dispatcher } from 'undici';
|
|
20
18
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
function getSystemProxyURI(requestURL: Url, env: typeof process.env): string | undefined {
|
|
19
|
+
function getSystemProxyURI(requestURL: URL, env: typeof process.env): string | undefined {
|
|
24
20
|
if (requestURL.protocol === 'http:') {
|
|
25
21
|
return env.HTTP_PROXY || env.http_proxy;
|
|
26
22
|
} else if (requestURL.protocol === 'https:') {
|
|
@@ -35,27 +31,46 @@ export interface ProxySettings {
|
|
|
35
31
|
strictSSL?: boolean;
|
|
36
32
|
}
|
|
37
33
|
|
|
38
|
-
export function getProxyAgent(rawRequestURL: string, env: typeof process.env, options: ProxySettings = {}):
|
|
39
|
-
|
|
34
|
+
export function getProxyAgent(rawRequestURL: string, env: typeof process.env, options: ProxySettings = {}): Dispatcher | undefined {
|
|
35
|
+
let requestURL: URL;
|
|
36
|
+
try {
|
|
37
|
+
requestURL = new URL(rawRequestURL);
|
|
38
|
+
} catch {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
|
|
40
42
|
const proxyURL = options.proxyUrl || getSystemProxyURI(requestURL, env);
|
|
41
43
|
|
|
42
44
|
if (!proxyURL) {
|
|
45
|
+
if (options.strictSSL === false) {
|
|
46
|
+
return new Agent({
|
|
47
|
+
connect: { rejectUnauthorized: false }
|
|
48
|
+
});
|
|
49
|
+
}
|
|
43
50
|
return undefined;
|
|
44
51
|
}
|
|
45
52
|
|
|
46
|
-
|
|
53
|
+
let proxyEndpoint: URL;
|
|
54
|
+
try {
|
|
55
|
+
proxyEndpoint = new URL(proxyURL);
|
|
56
|
+
} catch {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
47
59
|
|
|
48
|
-
if (!/^https?:$/.test(proxyEndpoint.protocol
|
|
60
|
+
if (!/^https?:$/.test(proxyEndpoint.protocol)) {
|
|
49
61
|
return undefined;
|
|
50
62
|
}
|
|
51
63
|
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
const proxyAgentOptions: ProxyAgent.Options = {
|
|
65
|
+
uri: proxyURL,
|
|
66
|
+
token: proxyEndpoint.username
|
|
67
|
+
? `Basic ${Buffer.from(`${decodeURIComponent(proxyEndpoint.username)}:${decodeURIComponent(proxyEndpoint.password)}`).toString('base64')}`
|
|
68
|
+
: undefined,
|
|
57
69
|
};
|
|
58
70
|
|
|
59
|
-
|
|
60
|
-
|
|
71
|
+
if (options.strictSSL === false) {
|
|
72
|
+
proxyAgentOptions.requestTls = { rejectUnauthorized: false };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return new ProxyAgent(proxyAgentOptions);
|
|
61
76
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"package.spec.d.ts","sourceRoot":"","sources":["../src/package.spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;kFAckF"}
|
package/lib/package.spec.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/********************************************************************************
|
|
2
|
-
* Copyright (C) 2022 TypeFox and others.
|
|
3
|
-
*
|
|
4
|
-
* This program and the accompanying materials are made available under the
|
|
5
|
-
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
-
* http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
-
*
|
|
8
|
-
* This Source Code may also be made available under the following Secondary
|
|
9
|
-
* Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
-
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
-
* with the GNU Classpath Exception which is available at
|
|
12
|
-
* https://www.gnu.org/software/classpath/license.html.
|
|
13
|
-
*
|
|
14
|
-
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
|
15
|
-
********************************************************************************/
|
|
16
|
-
/* note: this bogus test file is required so that
|
|
17
|
-
we are able to run mocha unit tests on this
|
|
18
|
-
package, without having any actual unit tests in it.
|
|
19
|
-
This way a coverage report will be generated,
|
|
20
|
-
showing 0% coverage, instead of no report.
|
|
21
|
-
This file can be removed once we have real unit
|
|
22
|
-
tests in place. */
|
|
23
|
-
describe('request package', () => {
|
|
24
|
-
it('should support code coverage statistics', () => true);
|
|
25
|
-
});
|
|
26
|
-
//# sourceMappingURL=package.spec.js.map
|
package/lib/package.spec.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"package.spec.js","sourceRoot":"","sources":["../src/package.spec.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;kFAckF;AAElF;;;;;;qBAMqB;AAErB,QAAQ,CAAC,iBAAiB,EAAE,GAAG,EAAE;IAE7B,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC;AAC9D,CAAC,CAAC,CAAC"}
|