UNPKG

@whatwg-node/node-fetch

Version:

Fetch API implementation for Node

117 lines (116 loc) 4.6 kB
import { PassThrough, Readable } from 'stream'; import { PonyfillResponse } from './Response.js'; import { defaultHeadersSerializer, isNodeReadable } from './utils.js'; export function fetchCurl(fetchRequest) { const { Curl, CurlFeature, CurlPause, CurlProgressFunc } = globalThis['libcurl']; const curlHandle = new Curl(); curlHandle.enable(CurlFeature.NoDataParsing); curlHandle.setOpt('URL', fetchRequest.url); curlHandle.setOpt('SSL_VERIFYPEER', false); curlHandle.enable(CurlFeature.StreamResponse); curlHandle.setStreamProgressCallback(function () { return fetchRequest['_signal']?.aborted ? process.env.DEBUG ? CurlProgressFunc.Continue : 1 : 0; }); if (fetchRequest['bodyType'] === 'String') { curlHandle.setOpt('POSTFIELDS', fetchRequest['bodyInit']); } else { const nodeReadable = (fetchRequest.body != null ? isNodeReadable(fetchRequest.body) ? fetchRequest.body : Readable.from(fetchRequest.body) : null); if (nodeReadable) { curlHandle.setOpt('UPLOAD', true); curlHandle.setUploadStream(nodeReadable); } } if (process.env.DEBUG) { curlHandle.setOpt('VERBOSE', true); } curlHandle.setOpt('TRANSFER_ENCODING', false); curlHandle.setOpt('HTTP_TRANSFER_DECODING', true); curlHandle.setOpt('FOLLOWLOCATION', fetchRequest.redirect === 'follow'); curlHandle.setOpt('MAXREDIRS', 20); curlHandle.setOpt('ACCEPT_ENCODING', ''); curlHandle.setOpt('CUSTOMREQUEST', fetchRequest.method); const headersSerializer = fetchRequest.headersSerializer || defaultHeadersSerializer; let size; const curlHeaders = headersSerializer(fetchRequest.headers, value => { size = Number(value); }); if (size != null) { curlHandle.setOpt('INFILESIZE', size); } curlHandle.setOpt('HTTPHEADER', curlHeaders); curlHandle.enable(CurlFeature.NoHeaderParsing); return new Promise(function promiseResolver(resolve, reject) { let streamResolved; if (fetchRequest['_signal']) { fetchRequest['_signal'].onabort = () => { if (curlHandle.isOpen) { try { curlHandle.pause(CurlPause.Recv); } catch (e) { reject(e); } } }; } curlHandle.once('end', function endListener() { curlHandle.close(); }); curlHandle.once('error', function errorListener(error) { if (streamResolved && !streamResolved.closed && !streamResolved.destroyed) { streamResolved.destroy(error); } else { if (error.message === 'Operation was aborted by an application callback') { error.message = 'The operation was aborted.'; } reject(error); } curlHandle.close(); }); curlHandle.once('stream', function streamListener(stream, status, headersBuf) { const pipedStream = stream.pipe(new PassThrough()); const headersFlat = headersBuf .toString('utf8') .split(/\r?\n|\r/g) .filter(headerFilter => { if (headerFilter && !headerFilter.startsWith('HTTP/')) { if (fetchRequest.redirect === 'error' && (headerFilter.includes('location') || headerFilter.includes('Location'))) { pipedStream.destroy(); reject(new Error('redirect is not allowed')); } return true; } return false; }); const headersInit = headersFlat.map(headerFlat => headerFlat.split(/:\s(.+)/).slice(0, 2)); pipedStream.on('pause', () => { stream.pause(); }); pipedStream.on('resume', () => { stream.resume(); }); pipedStream.on('close', () => { stream.destroy(); }); const ponyfillResponse = new PonyfillResponse(pipedStream, { status, headers: headersInit, url: fetchRequest.url, }); resolve(ponyfillResponse); streamResolved = pipedStream; }); curlHandle.perform(); }); }