deploy: current vibn theia state
Made-with: Cursor
This commit is contained in:
127
dev-packages/request/src/common-request-service.ts
Normal file
127
dev-packages/request/src/common-request-service.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
/********************************************************************************
|
||||
* Copyright (C) 2022 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the Eclipse
|
||||
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
* with the GNU Classpath Exception which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
********************************************************************************/
|
||||
|
||||
const textDecoder = typeof TextDecoder !== 'undefined' ? new TextDecoder() : undefined;
|
||||
|
||||
export interface Headers {
|
||||
[header: string]: string;
|
||||
}
|
||||
|
||||
export interface RequestOptions {
|
||||
type?: string;
|
||||
url: string;
|
||||
user?: string;
|
||||
password?: string;
|
||||
headers?: Headers;
|
||||
timeout?: number;
|
||||
data?: string;
|
||||
followRedirects?: number;
|
||||
proxyAuthorization?: string;
|
||||
}
|
||||
|
||||
export interface RequestContext {
|
||||
url: string;
|
||||
res: {
|
||||
headers: Headers;
|
||||
statusCode?: number;
|
||||
};
|
||||
/**
|
||||
* Contains the data returned by the request.
|
||||
*
|
||||
* If the request was transferred from the backend to the frontend, the buffer has been compressed into a string. In every case the buffer is an {@link Uint8Array}.
|
||||
*/
|
||||
buffer: Uint8Array | string;
|
||||
}
|
||||
|
||||
export namespace RequestContext {
|
||||
export function isSuccess(context: RequestContext): boolean {
|
||||
return (context.res.statusCode && context.res.statusCode >= 200 && context.res.statusCode < 300) || context.res.statusCode === 1223;
|
||||
}
|
||||
|
||||
function hasNoContent(context: RequestContext): boolean {
|
||||
return context.res.statusCode === 204;
|
||||
}
|
||||
|
||||
export function asText(context: RequestContext): string {
|
||||
if (!isSuccess(context)) {
|
||||
throw new Error(`Server returned code ${context.res.statusCode}.`);
|
||||
}
|
||||
if (hasNoContent(context)) {
|
||||
return '';
|
||||
}
|
||||
// Ensures that the buffer is an Uint8Array
|
||||
context = decompress(context);
|
||||
if (textDecoder) {
|
||||
return textDecoder.decode(context.buffer as Uint8Array);
|
||||
} else {
|
||||
return context.buffer.toString();
|
||||
}
|
||||
}
|
||||
|
||||
export function asJson<T = {}>(context: RequestContext): T {
|
||||
const str = asText(context);
|
||||
try {
|
||||
return JSON.parse(str);
|
||||
} catch (err) {
|
||||
err.message += ':\n' + str;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the buffer to base64 before sending it to the frontend.
|
||||
* This reduces the amount of JSON data transferred massively.
|
||||
* Does nothing if the buffer is already compressed.
|
||||
*/
|
||||
export function compress(context: RequestContext): RequestContext {
|
||||
if (context.buffer instanceof Uint8Array && Buffer !== undefined) {
|
||||
context.buffer = Buffer.from(context.buffer).toString('base64');
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decompresses a base64 buffer into a normal array buffer
|
||||
* Does nothing if the buffer is not compressed.
|
||||
*/
|
||||
export function decompress(context: RequestContext): RequestContext {
|
||||
const buffer = context.buffer;
|
||||
if (typeof buffer === 'string' && typeof atob === 'function') {
|
||||
context.buffer = Uint8Array.from(atob(buffer), c => c.charCodeAt(0));
|
||||
}
|
||||
return context;
|
||||
}
|
||||
}
|
||||
|
||||
export interface RequestConfiguration {
|
||||
proxyUrl?: string;
|
||||
proxyAuthorization?: string;
|
||||
strictSSL?: boolean;
|
||||
}
|
||||
export interface RequestService {
|
||||
configure(config: RequestConfiguration): Promise<void>;
|
||||
request(options: RequestOptions, token?: CancellationToken): Promise<RequestContext>;
|
||||
resolveProxy(url: string): Promise<string | undefined>
|
||||
}
|
||||
|
||||
export const RequestService = Symbol('RequestService');
|
||||
export const BackendRequestService = Symbol('BackendRequestService');
|
||||
export const REQUEST_SERVICE_PATH = '/services/request-service';
|
||||
|
||||
export interface CancellationToken {
|
||||
readonly isCancellationRequested: boolean;
|
||||
readonly onCancellationRequested: (listener: () => void) => void;
|
||||
}
|
||||
17
dev-packages/request/src/index.ts
Normal file
17
dev-packages/request/src/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/********************************************************************************
|
||||
* Copyright (C) 2022 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the Eclipse
|
||||
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
* with the GNU Classpath Exception which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
********************************************************************************/
|
||||
|
||||
export * from './common-request-service';
|
||||
176
dev-packages/request/src/node-request-service.ts
Normal file
176
dev-packages/request/src/node-request-service.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
/********************************************************************************
|
||||
* Copyright (C) 2022 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the Eclipse
|
||||
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
* with the GNU Classpath Exception which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
********************************************************************************/
|
||||
|
||||
import * as http from 'http';
|
||||
import * as https from 'https';
|
||||
import { getProxyAgent, ProxyAgent } from './proxy';
|
||||
import { Headers, RequestConfiguration, RequestContext, RequestOptions, RequestService, CancellationToken } from './common-request-service';
|
||||
import { createGunzip } from 'zlib';
|
||||
|
||||
export interface RawRequestFunction {
|
||||
(options: http.RequestOptions, callback?: (res: http.IncomingMessage) => void): http.ClientRequest;
|
||||
}
|
||||
|
||||
export interface NodeRequestOptions extends RequestOptions {
|
||||
agent?: ProxyAgent | http.Agent | https.Agent | boolean;
|
||||
strictSSL?: boolean;
|
||||
getRawRequest?(options: NodeRequestOptions): RawRequestFunction;
|
||||
};
|
||||
|
||||
export class NodeRequestService implements RequestService {
|
||||
protected proxyUrl?: string;
|
||||
protected strictSSL?: boolean;
|
||||
protected authorization?: string;
|
||||
|
||||
protected getNodeRequest(options: RequestOptions): RawRequestFunction {
|
||||
const endpoint = new URL(options.url);
|
||||
const module = endpoint.protocol === 'https:' ? https : http;
|
||||
return module.request;
|
||||
}
|
||||
|
||||
protected async getProxyUrl(url: string): Promise<string | undefined> {
|
||||
return this.proxyUrl;
|
||||
}
|
||||
|
||||
async configure(config: RequestConfiguration): Promise<void> {
|
||||
if (config.proxyUrl !== undefined) {
|
||||
this.proxyUrl = config.proxyUrl;
|
||||
}
|
||||
if (config.strictSSL !== undefined) {
|
||||
this.strictSSL = config.strictSSL;
|
||||
}
|
||||
if (config.proxyAuthorization !== undefined) {
|
||||
this.authorization = config.proxyAuthorization;
|
||||
}
|
||||
}
|
||||
|
||||
protected async processOptions(options: NodeRequestOptions): Promise<NodeRequestOptions> {
|
||||
const { strictSSL } = this;
|
||||
options.strictSSL = options.strictSSL ?? strictSSL;
|
||||
const agent = options.agent ? options.agent : getProxyAgent(options.url || '', process.env, {
|
||||
proxyUrl: await this.getProxyUrl(options.url),
|
||||
strictSSL: options.strictSSL
|
||||
});
|
||||
options.agent = agent;
|
||||
|
||||
const authorization = options.proxyAuthorization || this.authorization;
|
||||
if (authorization) {
|
||||
options.headers = {
|
||||
...(options.headers || {}),
|
||||
'Proxy-Authorization': authorization
|
||||
};
|
||||
}
|
||||
|
||||
options.headers = {
|
||||
'Accept-Encoding': 'gzip',
|
||||
...(options.headers || {}),
|
||||
};
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
request(options: NodeRequestOptions, token?: CancellationToken): Promise<RequestContext> {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
options = await this.processOptions(options);
|
||||
|
||||
const endpoint = new URL(options.url);
|
||||
const rawRequest = options.getRawRequest
|
||||
? options.getRawRequest(options)
|
||||
: this.getNodeRequest(options);
|
||||
|
||||
const opts: https.RequestOptions = {
|
||||
hostname: endpoint.hostname,
|
||||
port: endpoint.port ? parseInt(endpoint.port) : (endpoint.protocol === 'https:' ? 443 : 80),
|
||||
protocol: endpoint.protocol,
|
||||
path: endpoint.pathname + endpoint.search,
|
||||
method: options.type || 'GET',
|
||||
headers: options.headers,
|
||||
agent: options.agent as https.Agent,
|
||||
rejectUnauthorized: !!options.strictSSL
|
||||
};
|
||||
|
||||
if (options.user && options.password) {
|
||||
opts.auth = options.user + ':' + options.password;
|
||||
}
|
||||
|
||||
const timeoutHandler = () => {
|
||||
reject('timeout');
|
||||
};
|
||||
|
||||
const req = rawRequest(opts, async res => {
|
||||
const followRedirects = options.followRedirects ?? 3;
|
||||
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && followRedirects > 0 && res.headers.location) {
|
||||
req.off('timeout', timeoutHandler);
|
||||
this.request({
|
||||
...options,
|
||||
url: res.headers.location,
|
||||
followRedirects: followRedirects - 1
|
||||
}, token).then(resolve, reject);
|
||||
} else {
|
||||
const chunks: Uint8Array[] = [];
|
||||
|
||||
const stream = res.headers['content-encoding'] === 'gzip' ? res.pipe(createGunzip()) : res;
|
||||
|
||||
stream.on('data', chunk => {
|
||||
chunks.push(chunk);
|
||||
});
|
||||
|
||||
stream.on('end', () => {
|
||||
req.off('timeout', timeoutHandler);
|
||||
const buffer = Buffer.concat(chunks);
|
||||
resolve({
|
||||
url: options.url,
|
||||
res: {
|
||||
headers: res.headers as Headers,
|
||||
statusCode: res.statusCode
|
||||
},
|
||||
buffer
|
||||
});
|
||||
});
|
||||
|
||||
stream.on('error', err => {
|
||||
reject(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
req.on('error', err => {
|
||||
reject(err);
|
||||
});
|
||||
|
||||
req.on('timeout', timeoutHandler);
|
||||
|
||||
if (options.timeout) {
|
||||
req.setTimeout(options.timeout);
|
||||
}
|
||||
|
||||
if (options.data) {
|
||||
req.write(options.data);
|
||||
}
|
||||
|
||||
req.end();
|
||||
|
||||
token?.onCancellationRequested(() => {
|
||||
req.destroy();
|
||||
reject();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async resolveProxy(url: string): Promise<string | undefined> {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
28
dev-packages/request/src/package.spec.ts
Normal file
28
dev-packages/request/src/package.spec.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/********************************************************************************
|
||||
* Copyright (C) 2022 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the Eclipse
|
||||
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
* with the GNU Classpath Exception which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
********************************************************************************/
|
||||
|
||||
/* note: this bogus test file is required so that
|
||||
we are able to run mocha unit tests on this
|
||||
package, without having any actual unit tests in it.
|
||||
This way a coverage report will be generated,
|
||||
showing 0% coverage, instead of no report.
|
||||
This file can be removed once we have real unit
|
||||
tests in place. */
|
||||
|
||||
describe('request package', () => {
|
||||
|
||||
it('should support code coverage statistics', () => true);
|
||||
});
|
||||
57
dev-packages/request/src/proxy.ts
Normal file
57
dev-packages/request/src/proxy.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
/********************************************************************************
|
||||
* Copyright (C) 2022 TypeFox and others.
|
||||
*
|
||||
* This program and the accompanying materials are made available under the
|
||||
* terms of the Eclipse Public License v. 2.0 which is available at
|
||||
* http://www.eclipse.org/legal/epl-2.0.
|
||||
*
|
||||
* This Source Code may also be made available under the following Secondary
|
||||
* Licenses when the conditions for such availability set forth in the Eclipse
|
||||
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
||||
* with the GNU Classpath Exception which is available at
|
||||
* https://www.gnu.org/software/classpath/license.html.
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
|
||||
********************************************************************************/
|
||||
|
||||
import { parse as parseUrl, Url } from 'url';
|
||||
import { HttpProxyAgent } from 'http-proxy-agent';
|
||||
import { HttpsProxyAgent } from 'https-proxy-agent';
|
||||
|
||||
export type ProxyAgent = HttpProxyAgent<string> | HttpsProxyAgent<string>;
|
||||
|
||||
function getSystemProxyURI(requestURL: Url, env: typeof process.env): string | undefined {
|
||||
if (requestURL.protocol === 'http:') {
|
||||
return env.HTTP_PROXY || env.http_proxy;
|
||||
} else if (requestURL.protocol === 'https:') {
|
||||
return env.HTTPS_PROXY || env.https_proxy || env.HTTP_PROXY || env.http_proxy;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export interface ProxySettings {
|
||||
proxyUrl?: string;
|
||||
strictSSL?: boolean;
|
||||
}
|
||||
|
||||
export function getProxyAgent(rawRequestURL: string, env: typeof process.env, options: ProxySettings = {}): ProxyAgent | undefined {
|
||||
const requestURL = parseUrl(rawRequestURL);
|
||||
const proxyURL = options.proxyUrl || getSystemProxyURI(requestURL, env);
|
||||
|
||||
if (!proxyURL) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const proxyEndpoint = parseUrl(proxyURL);
|
||||
|
||||
if (!/^https?:$/.test(proxyEndpoint.protocol || '')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (requestURL.protocol === 'http:') {
|
||||
return new HttpProxyAgent(proxyURL, { rejectUnauthorized: !!options.strictSSL });
|
||||
} else {
|
||||
return new HttpsProxyAgent(proxyURL, { rejectUnauthorized: !!options.strictSSL });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user