From 5200134749d8b61b99c1f7a63807f8952c5559d1 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 20 Sep 2021 13:50:26 -0700 Subject: [PATCH] chore: improve http request utilities (#9031) --- src/utils/registry.ts | 2 +- src/utils/utils.ts | 59 +++++++++++++++++++++++++------------------ 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/utils/registry.ts b/src/utils/registry.ts index e26074e7d2..262cbf0bbb 100644 --- a/src/utils/registry.ts +++ b/src/utils/registry.ts @@ -605,7 +605,7 @@ export class Registry { private async _installMSEdgeChannel(channel: 'msedge'|'msedge-beta'|'msedge-dev', scripts: Record<'linux' | 'darwin' | 'win32', string>) { const scriptArgs: string[] = []; if (process.platform !== 'linux') { - const products = JSON.parse(await fetchData('https://edgeupdates.microsoft.com/api/products')); + const products = JSON.parse(await fetchData({ url: 'https://edgeupdates.microsoft.com/api/products' })); const productName = { 'msedge': 'Stable', 'msedge-beta': 'Beta', diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 4ba1abb6ea..6e38bc1974 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -20,6 +20,8 @@ import stream from 'stream'; import removeFolder from 'rimraf'; import * as crypto from 'crypto'; import os from 'os'; +import http from 'http'; +import https from 'https'; import { spawn, SpawnOptions } from 'child_process'; import { getProxyForUrl } from 'proxy-from-env'; import * as URL from 'url'; @@ -37,53 +39,63 @@ const ProxyAgent = require('https-proxy-agent'); export const existsAsync = (path: string): Promise => new Promise(resolve => fs.stat(path, err => resolve(!err))); -function httpRequest(url: string, method: string, response: (r: any) => void) { - let options: any = URL.parse(url); - options.method = method; +type HTTPRequestParams = { + url: string, + method?: string, + headers?: http.OutgoingHttpHeaders, + data?: string | Buffer, +}; - const proxyURL = getProxyForUrl(url); +function httpRequest(params: HTTPRequestParams, onResponse: (r: http.IncomingMessage) => void, onError: (error: Error) => void) { + const parsedUrl = URL.parse(params.url); + let options: https.RequestOptions = { ...parsedUrl }; + options.method = params.method || 'GET'; + options.headers = params.headers; + + const proxyURL = getProxyForUrl(params.url); if (proxyURL) { - if (url.startsWith('http:')) { + if (params.url.startsWith('http:')) { const proxy = URL.parse(proxyURL); options = { - path: options.href, + path: parsedUrl.href, host: proxy.hostname, port: proxy.port, }; } else { - const parsedProxyURL: any = URL.parse(proxyURL); - parsedProxyURL.secureProxy = parsedProxyURL.protocol === 'https:'; + const parsedProxyURL = URL.parse(proxyURL); + (parsedProxyURL as any).secureProxy = parsedProxyURL.protocol === 'https:'; options.agent = new ProxyAgent(parsedProxyURL); options.rejectUnauthorized = false; } } - const requestCallback = (res: any) => { - if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) - httpRequest(res.headers.location, method, response); + const requestCallback = (res: http.IncomingMessage) => { + const statusCode = res.statusCode || 0; + if (statusCode >= 300 && statusCode < 400 && res.headers.location) + httpRequest({ ...params, url: res.headers.location }, onResponse, onError); else - response(res); + onResponse(res); }; const request = options.protocol === 'https:' ? - require('https').request(options, requestCallback) : - require('http').request(options, requestCallback); - request.end(); - return request; + https.request(options, requestCallback) : + http.request(options, requestCallback); + request.on('error', onError); + request.end(params.data); } -export function fetchData(url: string): Promise { +export function fetchData(params: HTTPRequestParams): Promise { return new Promise((resolve, reject) => { - httpRequest(url, 'GET', function(response){ + httpRequest(params, response => { if (response.statusCode !== 200) { - reject(new Error(`fetch failed: server returned code ${response.statusCode}. URL: ${url}`)); + reject(new Error(`fetch failed: server returned code ${response.statusCode}. URL: ${params.url}`)); return; } let body = ''; response.on('data', (chunk: string) => body += chunk); response.on('error', (error: any) => reject(error)); response.on('end', () => resolve(body)); - }).on('error', (error: any) => reject(error)); + }, reject); }); } @@ -104,7 +116,7 @@ export function downloadFile(url: string, destinationPath: string, options: {pro const promise: Promise<{error: any}> = new Promise(x => { fulfill = x; }); - const request = httpRequest(url, 'GET', response => { + httpRequest({ url }, response => { log(`-- response status code: ${response.statusCode}`); if (response.statusCode !== 200) { const error = new Error(`Download failed: server returned code ${response.statusCode}. URL: ${url}`); @@ -117,12 +129,11 @@ export function downloadFile(url: string, destinationPath: string, options: {pro file.on('finish', () => fulfill({error: null})); file.on('error', error => fulfill({error})); response.pipe(file); - totalBytes = parseInt(response.headers['content-length'], 10); + totalBytes = parseInt(response.headers['content-length'] || '0', 10); log(`-- total bytes: ${totalBytes}`); if (progressCallback) response.on('data', onData); - }); - request.on('error', (error: any) => fulfill({error})); + }, (error: any) => fulfill({error})); return promise; function onData(chunk: string) {