sync-request-curl
Version:
Fast way to send synchronous web requests in NodeJS. API is a subset of sync-request. Leverages node-libcurl for high performance. Cannot be used in a browser.
191 lines (189 loc) • 7.66 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
const node_libcurl_1 = require("node-libcurl");
const utils_1 = require("./utils");
/**
* Create a libcurl Easy object with default configurations
*
* @param {HttpVerb} method - The HTTP method (e.g., 'GET', 'POST', 'PUT')
* @param {Options} options - configuration options for the request.
* @returns {Easy} an initialized libcurl Easy object with default options
*/
const createCurlObjectWithDefaults = (method, options) => {
var _a, _b;
const curl = new node_libcurl_1.Easy();
curl.setOpt(node_libcurl_1.Curl.option.CUSTOMREQUEST, method);
curl.setOpt(node_libcurl_1.Curl.option.TIMEOUT_MS, (_a = options.timeout) !== null && _a !== void 0 ? _a : 0);
curl.setOpt(node_libcurl_1.Curl.option.FOLLOWLOCATION, options.followRedirects === undefined ||
options.followRedirects);
curl.setOpt(node_libcurl_1.Curl.option.MAXREDIRS, (_b = options.maxRedirects) !== null && _b !== void 0 ? _b : -1);
curl.setOpt(node_libcurl_1.Curl.option.SSL_VERIFYPEER, !options.insecure);
curl.setOpt(node_libcurl_1.Curl.option.NOBODY, method === 'HEAD');
return curl;
};
/**
* Handles query string parameters in a URL, modifies the URL if necessary,
* and sets it as the CURLOPT_URL option in the given cURL Easy object.
*
* @param {Easy} curl - The cURL easy handle
* @param {string} url - The URL to handle query string parameters for
* @param {Object.<string, any>} qs - query string parameters for the request
*/
const handleQueryString = (curl, url, qs) => {
url = qs && Object.keys(qs).length ? (0, utils_1.handleQs)(url, qs) : url;
curl.setOpt(node_libcurl_1.Curl.option.URL, url);
};
/**
* Sets up a callback function for the cURL Easy object to handle returned
* headers and populate the input array with header lines.
*
* @param {Easy} curl - The cURL easy handle
* @param {string[]} returnedHeaderArray - array for returned header lines
*/
const handleOutgoingHeaders = (curl, returnedHeaderArray) => {
curl.setOpt(node_libcurl_1.Curl.option.HEADERFUNCTION, (headerLine) => {
returnedHeaderArray.push(headerLine.toString('utf-8').trim());
return headerLine.length;
});
};
/**
* Sets the JSON payload for the curl request.
* @param {Easy} curl - The curl object.
* @param {any} json - The JSON body to be sent
* @param {string[]} httpHeaders - HTTP headers for the request
*/
const setJSONPayload = (curl, json, httpHeaders) => {
httpHeaders.push('Content-Type: application/json');
const payload = JSON.stringify(json);
httpHeaders.push(`Content-Length: ${Buffer.byteLength(payload, 'utf-8')}`);
curl.setOpt(node_libcurl_1.Curl.option.POSTFIELDS, payload);
};
/**
* Sets the buffer payload for the curl request.
* @param {Easy} curl - The curl object.
* @param {string | Buffer} body - The body to be sent in the request.
* @param {string[]} httpHeaders - HTTP headers for the request
*/
const setBodyPayload = (curl, body, httpHeaders) => {
if (Buffer.isBuffer(body)) {
let position = 0;
curl.setOpt(node_libcurl_1.Curl.option.POST, true);
curl.setOpt(node_libcurl_1.Curl.option.POSTFIELDSIZE, -1);
curl.setOpt(node_libcurl_1.Curl.option.READFUNCTION, (buffer, size, nmemb) => {
const amountToRead = size * nmemb;
if (position === body.length) {
return 0;
}
const totalWritten = body.copy(buffer, 0, position, Math.min(amountToRead, body.length));
position += totalWritten;
return totalWritten;
});
}
else {
curl.setOpt(node_libcurl_1.Curl.option.POSTFIELDS, body);
httpHeaders.push(`Content-Length: ${Buffer.byteLength(body, 'utf-8')}`);
}
};
/**
* Sets the buffer payload for the curl request.
* @param {Easy} curl - The curl object.
* @param {Array<HttpPostField>} formData - list of files/contents to be sent
*/
const setFormPayload = (curl, formData) => {
curl.setOpt(node_libcurl_1.Curl.option.HTTPPOST, formData);
};
/**
* Prepares the request body and headers for a cURL request based on provided
* options. Also sets up a callback function for the cURL Easy object to handle
* returned body and populates the input buffet.
*
* @param {Easy} curl - The cURL easy handle
* @param {Options} options - Options for configuring the request
* @param {{ body: Buffer }} buffer - wrapped buffer for the returned body
* @param {string[]} httpHeaders - HTTP headers for the request
*/
const handleBodyAndRequestHeaders = (curl, options, buffer, httpHeaders) => {
if (options.json) {
setJSONPayload(curl, options.json, httpHeaders);
}
else if (options.body) {
setBodyPayload(curl, options.body, httpHeaders);
}
else if (options.formData) {
setFormPayload(curl, options.formData);
}
else {
httpHeaders.push('Content-Length: 0');
}
curl.setOpt(node_libcurl_1.Curl.option.WRITEFUNCTION, (buff, nmemb, size) => {
buffer.body = Buffer.concat([buffer.body, buff.subarray(0, nmemb * size)]);
return nmemb * size;
});
curl.setOpt(node_libcurl_1.Curl.option.HTTPHEADER, httpHeaders);
};
/**
* Performs an HTTP request using cURL with the specified parameters.
*
* @param {HttpVerb} method - The HTTP method for the request (e.g., 'GET', 'POST')
* @param {string} url - The URL to make the request to
* @param {Options} [options={}] - An object to configure the request
* @returns {Response} - HTTP response consisting of status code, headers, and body
*/
const request = (method, url, options = {}) => {
const curl = createCurlObjectWithDefaults(method, options);
handleQueryString(curl, url, options.qs);
// Body/JSON and Headers (incoming)
const bufferWrap = { body: Buffer.alloc(0) };
handleBodyAndRequestHeaders(curl, options, bufferWrap, (0, utils_1.parseIncomingHeaders)(options.headers));
// Headers (outgoing)
const returnedHeaderArray = [];
handleOutgoingHeaders(curl, returnedHeaderArray);
if (options.setEasyOptions) {
options.setEasyOptions(curl, node_libcurl_1.Curl.option);
}
// Execute request
const code = curl.perform();
(0, utils_1.checkValidCurlCode)(code, { method, url, options });
// Creating return object
const statusCode = curl.getInfo('RESPONSE_CODE').data;
const headers = (0, utils_1.parseReturnedHeaders)(returnedHeaderArray);
const { body } = bufferWrap;
/**
* Get the body of a response with an optional encoding.
*
* @throws {Error} if the status code is >= 300
* @returns {Buffer | string} buffer body by default, string body with encoding
*/
const getBody = (encoding) => {
(0, utils_1.checkValidStatusCode)(statusCode, body);
return typeof encoding === 'string' ? body.toString(encoding) : body;
};
/**
* Get the JSON-parsed body of a response.
*
* @throws {Error} if the body is nto a valid JSON
* @returns {any} parsed JSON body
*/
const getJSON = (encoding) => {
try {
return JSON.parse(body.toString(encoding));
}
catch (err) {
throw new Error(`
The server body response for
- ${method}
- ${url}
cannot be parsed as JSON.
Body:
${body.toString(encoding)}
JSON-Parsing Error Message:
${err instanceof Error ? err.message : String(err)}
`);
}
};
url = curl.getInfo('EFFECTIVE_URL').data;
curl.close();
return { statusCode, headers, url, body, getBody, getJSON };
};
exports.default = request;
//# sourceMappingURL=request.js.map