UNPKG

@sussudio/platform

Version:

Internal APIs for VS Code's service injection the base services.

191 lines (190 loc) 6.87 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? (desc = Object.getOwnPropertyDescriptor(target, key)) : desc, d; if (typeof Reflect === 'object' && typeof Reflect.decorate === 'function') r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if ((d = decorators[i])) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); }; }; import { parse as parseUrl } from 'url'; import { Promises } from '@sussudio/base/common/async.mjs'; import { streamToBufferReadableStream } from '@sussudio/base/common/buffer.mjs'; import { CancellationError } from '@sussudio/base/common/errors.mjs'; import { Disposable } from '@sussudio/base/common/lifecycle.mjs'; import { isBoolean, isNumber } from '@sussudio/base/common/types.mjs'; import { IConfigurationService } from '../../configuration/common/configuration.mjs'; import { INativeEnvironmentService } from '../../environment/common/environment.mjs'; import { getResolvedShellEnv } from '../../shell/node/shellEnv.mjs'; import { ILogService } from '../../log/common/log.mjs'; import { getProxyAgent } from './proxy.mjs'; import { createGunzip } from 'zlib'; /** * This service exposes the `request` API, while using the global * or configured proxy settings. */ let RequestService = class RequestService extends Disposable { configurationService; environmentService; logService; proxyUrl; strictSSL; authorization; shellEnvErrorLogged; constructor(configurationService, environmentService, logService) { super(); this.configurationService = configurationService; this.environmentService = environmentService; this.logService = logService; this.configure(); this._register( configurationService.onDidChangeConfiguration((e) => { if (e.affectsConfiguration('http')) { this.configure(); } }), ); } configure() { const config = this.configurationService.getValue('http'); this.proxyUrl = config?.proxy; this.strictSSL = !!config?.proxyStrictSSL; this.authorization = config?.proxyAuthorization; } async request(options, token) { this.logService.trace('RequestService#request (node) - begin', options.url); const { proxyUrl, strictSSL } = this; let shellEnv = undefined; try { shellEnv = await getResolvedShellEnv( this.configurationService, this.logService, this.environmentService.args, process.env, ); } catch (error) { if (!this.shellEnvErrorLogged) { this.shellEnvErrorLogged = true; this.logService.error('RequestService#request (node) resolving shell environment failed', error); } } const env = { ...process.env, ...shellEnv, }; const agent = options.agent ? options.agent : await getProxyAgent(options.url || '', env, { proxyUrl, strictSSL }); options.agent = agent; options.strictSSL = strictSSL; if (this.authorization) { options.headers = { ...(options.headers || {}), 'Proxy-Authorization': this.authorization, }; } try { const res = await this._request(options, token); this.logService.trace('RequestService#request (node) - success', options.url); return res; } catch (error) { this.logService.trace('RequestService#request (node) - error', options.url, error); throw error; } } async getNodeRequest(options) { const endpoint = parseUrl(options.url); const module = endpoint.protocol === 'https:' ? await import('https') : await import('http'); return module.request; } _request(options, token) { return Promises.withAsyncBody(async (c, e) => { const endpoint = parseUrl(options.url); const rawRequest = options.getRawRequest ? options.getRawRequest(options) : await this.getNodeRequest(options); const opts = { hostname: endpoint.hostname, port: endpoint.port ? parseInt(endpoint.port) : endpoint.protocol === 'https:' ? 443 : 80, protocol: endpoint.protocol, path: endpoint.path, method: options.type || 'GET', headers: options.headers, agent: options.agent, rejectUnauthorized: isBoolean(options.strictSSL) ? options.strictSSL : true, }; if (options.user && options.password) { opts.auth = options.user + ':' + options.password; } const req = rawRequest(opts, (res) => { const followRedirects = isNumber(options.followRedirects) ? options.followRedirects : 3; if ( res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && followRedirects > 0 && res.headers['location'] ) { this._request( { ...options, url: res.headers['location'], followRedirects: followRedirects - 1, }, token, ).then(c, e); } else { let stream = res; // Responses from Electron net module should be treated as response // from browser, which will apply gzip filter and decompress the response // using zlib before passing the result to us. Following step can be bypassed // in this case and proceed further. // Refs https://source.chromium.org/chromium/chromium/src/+/main:net/url_request/url_request_http_job.cc;l=1266-1318 if (!options.isChromiumNetwork && res.headers['content-encoding'] === 'gzip') { stream = res.pipe(createGunzip()); } c({ res, stream: streamToBufferReadableStream(stream) }); } }); req.on('error', e); if (options.timeout) { req.setTimeout(options.timeout); } // Chromium will abort the request if forbidden headers are set. // Ref https://source.chromium.org/chromium/chromium/src/+/main:services/network/public/cpp/header_util.cc;l=14-48; // for additional context. if (options.isChromiumNetwork) { req.removeHeader('Content-Length'); } if (options.data) { if (typeof options.data === 'string') { req.write(options.data); } } req.end(); token.onCancellationRequested(() => { req.abort(); e(new CancellationError()); }); }); } async resolveProxy(url) { return undefined; // currently not implemented in node } }; RequestService = __decorate( [__param(0, IConfigurationService), __param(1, INativeEnvironmentService), __param(2, ILogService)], RequestService, ); export { RequestService };