Spaces:
Running
Running
File size: 4,584 Bytes
c2b7eb3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | /**
* @license
* Copyright 2023 Google Inc.
* SPDX-License-Identifier: Apache-2.0
*/
import {createWriteStream} from 'node:fs';
import * as http from 'node:http';
import * as https from 'node:https';
import {URL, urlToHttpOptions} from 'node:url';
import {ProxyAgent} from 'proxy-agent';
export function headHttpRequest(url: URL): Promise<boolean> {
return new Promise(resolve => {
const request = httpRequest(
url,
'HEAD',
response => {
// consume response data free node process
response.resume();
resolve(response.statusCode === 200);
},
false,
);
request.on('error', () => {
resolve(false);
});
});
}
export function httpRequest(
url: URL,
method: string,
response: (x: http.IncomingMessage) => void,
keepAlive = true,
): http.ClientRequest {
const options: http.RequestOptions = {
protocol: url.protocol,
hostname: url.hostname,
port: url.port,
path: url.pathname + url.search,
method,
headers: keepAlive ? {Connection: 'keep-alive'} : undefined,
auth: urlToHttpOptions(url).auth,
agent: new ProxyAgent(),
};
const requestCallback = (res: http.IncomingMessage): void => {
if (
res.statusCode &&
res.statusCode >= 300 &&
res.statusCode < 400 &&
res.headers.location
) {
httpRequest(new URL(res.headers.location), method, response);
// consume response data to free up memory
// And prevents the connection from being kept alive
res.resume();
} else {
response(res);
}
};
const request =
options.protocol === 'https:'
? https.request(options, requestCallback)
: http.request(options, requestCallback);
request.end();
return request;
}
/**
* @internal
*/
export function downloadFile(
url: URL,
destinationPath: string,
progressCallback?: (downloadedBytes: number, totalBytes: number) => void,
): Promise<void> {
return new Promise<void>((resolve, reject) => {
let downloadedBytes = 0;
let totalBytes = 0;
function onData(chunk: string): void {
downloadedBytes += chunk.length;
progressCallback!(downloadedBytes, totalBytes);
}
const request = httpRequest(url, 'GET', response => {
if (response.statusCode !== 200) {
const error = new Error(
`Download failed: server returned code ${response.statusCode}. URL: ${url}`,
);
// consume response data to free up memory
response.resume();
reject(error);
return;
}
const file = createWriteStream(destinationPath);
file.on('close', () => {
// The 'close' event is emitted when the stream and any of its
// underlying resources (a file descriptor, for example) have been
// closed. The event indicates that no more events will be emitted, and
// no further computation will occur.
return resolve();
});
file.on('error', error => {
// The 'error' event may be emitted by a Readable implementation at any
// time. Typically, this may occur if the underlying stream is unable to
// generate data due to an underlying internal failure, or when a stream
// implementation attempts to push an invalid chunk of data.
return reject(error);
});
response.pipe(file);
totalBytes = parseInt(response.headers['content-length']!, 10);
if (progressCallback) {
response.on('data', onData);
}
});
request.on('error', error => {
return reject(error);
});
});
}
export async function getJSON(url: URL): Promise<unknown> {
const text = await getText(url);
try {
return JSON.parse(text);
} catch {
throw new Error('Could not parse JSON from ' + url.toString());
}
}
export function getText(url: URL): Promise<string> {
return new Promise((resolve, reject) => {
const request = httpRequest(
url,
'GET',
response => {
let data = '';
if (response.statusCode && response.statusCode >= 400) {
return reject(new Error(`Got status code ${response.statusCode}`));
}
response.on('data', chunk => {
data += chunk;
});
response.on('end', () => {
try {
return resolve(String(data));
} catch {
return reject(
new Error(`Failed to read text response from ${url}`),
);
}
});
},
false,
);
request.on('error', err => {
reject(err);
});
});
}
|