@whatwg-node/node-fetch
Version:
Fetch API implementation for Node
108 lines (107 loc) • 4.73 kB
JavaScript
import { request as httpRequest } from 'http';
import { request as httpsRequest } from 'https';
import { PassThrough, Readable } from 'stream';
import { createBrotliDecompress, createGunzip, createInflate } from 'zlib';
import { PonyfillAbortError } from './AbortError.js';
import { PonyfillRequest } from './Request.js';
import { PonyfillResponse } from './Response.js';
import { PonyfillURL } from './URL.js';
import { getHeadersObj, isNodeReadable } from './utils.js';
function getRequestFnForProtocol(url) {
if (url.startsWith('http:')) {
return httpRequest;
}
else if (url.startsWith('https:')) {
return httpsRequest;
}
throw new Error(`Unsupported protocol: ${url.split(':')[0] || url}`);
}
export function fetchNodeHttp(fetchRequest) {
return new Promise((resolve, reject) => {
try {
const requestFn = getRequestFnForProtocol(fetchRequest.url);
const nodeReadable = (fetchRequest.body != null
? isNodeReadable(fetchRequest.body)
? fetchRequest.body
: Readable.from(fetchRequest.body)
: null);
const headersSerializer = fetchRequest.headersSerializer || getHeadersObj;
const nodeHeaders = headersSerializer(fetchRequest.headers);
const nodeRequest = requestFn(fetchRequest.url, {
method: fetchRequest.method,
headers: nodeHeaders,
signal: fetchRequest['_signal'] ?? undefined,
agent: fetchRequest.agent,
});
nodeRequest.once('response', nodeResponse => {
let responseBody = nodeResponse;
const contentEncoding = nodeResponse.headers['content-encoding'];
switch (contentEncoding) {
case 'x-gzip':
case 'gzip':
responseBody = nodeResponse.pipe(createGunzip());
break;
case 'x-deflate':
case 'deflate':
responseBody = nodeResponse.pipe(createInflate());
break;
case 'br':
responseBody = nodeResponse.pipe(createBrotliDecompress());
break;
}
if (nodeResponse.headers.location) {
if (fetchRequest.redirect === 'error') {
const redirectError = new Error('Redirects are not allowed');
reject(redirectError);
nodeResponse.resume();
return;
}
if (fetchRequest.redirect === 'follow') {
const redirectedUrl = new PonyfillURL(nodeResponse.headers.location, fetchRequest.url);
const redirectResponse$ = fetchNodeHttp(new PonyfillRequest(redirectedUrl, fetchRequest));
resolve(redirectResponse$.then(redirectResponse => {
redirectResponse.redirected = true;
return redirectResponse;
}));
nodeResponse.resume();
return;
}
}
if (responseBody === nodeResponse) {
responseBody = nodeResponse.pipe(new PassThrough());
responseBody.on('pause', () => {
nodeResponse.pause();
});
responseBody.on('resume', () => {
nodeResponse.resume();
});
responseBody.on('close', () => {
nodeResponse.destroy();
});
fetchRequest['_signal']?.addEventListener('abort', () => {
if (!nodeResponse.destroyed) {
responseBody.emit('error', new PonyfillAbortError());
}
});
}
const ponyfillResponse = new PonyfillResponse(responseBody, {
status: nodeResponse.statusCode,
statusText: nodeResponse.statusMessage,
headers: nodeResponse.headers,
url: fetchRequest.url,
});
resolve(ponyfillResponse);
});
nodeRequest.once('error', reject);
if (nodeReadable) {
nodeReadable.pipe(nodeRequest);
}
else {
nodeRequest.end();
}
}
catch (e) {
reject(e);
}
});
}