UNPKG

@whatwg-node/node-fetch

Version:

Fetch API implementation for Node

108 lines (107 loc) 4.73 kB
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); } }); }