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.
1 lines • 16 kB
Source Map (JSON)
{"version":3,"file":"index.cjs","names":["CurlCode","CurlError","Easy","Easy","Curl","returnedHeaderArray: string[]","getJSON: GetJSON","request"],"sources":["../src/utils.ts","../src/request.ts","../src/index.ts"],"sourcesContent":["import { IncomingHttpHeaders } from 'http';\nimport { CurlCode, Easy } from 'node-libcurl';\nimport { HttpVerb, Options } from './types';\nimport { CurlError } from './errors';\n\ninterface RequestInputs {\n method: HttpVerb,\n url: string,\n options: Options;\n}\n\n/**\n * Handles query string parameters in a URL by modifying or appending them\n * based on the provided object.\n *\n * Arrays of primitives, e.g. { quizIds: [1,2,3] }, will be of the form:\n * https://google.com.au/?quizIds%5B0%5D=0&quizIds%5B1%5D=1&quizIds%5B2%5D=2\n * i.e. https://www.google.com.au/?quizIds[0]=0&quizIds[1]=1&quizIds[2]=2\n *\n * @param {string} url - The URL to handle query string parameters for.\n * @param {Object.<string, any>} qs - query string parameters to modify or append.\n * @returns {string} The modified URL with the updated query string parameters.\n */\nexport const handleQs = (url: string, qs: { [key: string]: any }): string => {\n const urlObj = new URL(url);\n for (const [key, value] of Object.entries(qs)) {\n if (Array.isArray(value)) {\n urlObj.searchParams.delete(key);\n value.forEach((item, i) => urlObj.searchParams.append(`${key}[${i}]`, String(item)));\n } else if (value === null) {\n urlObj.searchParams.set(key, '');\n } else if (value !== undefined) {\n urlObj.searchParams.set(key, String(value));\n }\n }\n urlObj.search = urlObj.searchParams.toString();\n return urlObj.href;\n};\n\n/**\n * Parses incoming HTTP headers to an array of formatted strings.\n *\n * @param {IncomingHttpHeaders} headers - The header object to parse.\n * @returns {string[]} An array of formatted header strings.\n */\nexport const parseIncomingHeaders = (headers?: IncomingHttpHeaders): string[] => {\n return headers\n ? Object.entries(headers)\n .filter(([_, value]) => value !== undefined)\n .map(([key, value]) => value === '' ? `${key};` : `${key}: ${value}`)\n : [];\n};\n\n/**\n * Parses an array of header lines as IncomingHttpHeaders.\n *\n * @param {string[]} headerLines - An array of header lines to parse.\n * @returns {IncomingHttpHeaders} An object containing parsed headers.\n */\nexport const parseReturnedHeaders = (headerLines: string[]): IncomingHttpHeaders => {\n return headerLines.reduce((acc, header) => {\n const [name, ...values] = header.split(':');\n if (name && values.length > 0) {\n acc[name.trim().toLowerCase()] = values.join(':').trim();\n }\n return acc;\n }, {} as IncomingHttpHeaders);\n};\n\n/**\n * Checks CURL code and throws a `CurlError` if it indicates failure.\n *\n * @param {CurlCode} code - The CURL error code to check.\n * @param {RequestInputs} requestInputs - input parameters for the CURL request.\n * @throws {CurlError} Throws a `CurlError` if the CURL code indicates failure.\n */\nexport const checkValidCurlCode = (code: CurlCode, requestInputs: RequestInputs) => {\n if (code !== CurlCode.CURLE_OK) {\n throw new CurlError(code, `\n Curl request failed with code ${code}:\n - ${Easy.strError(code)}\n\n You can also look up the Libcurl Error (code ${code}) here:\n - https://curl.se/libcurl/c/libcurl-errors.html\n\n DEBUG: {\n method: \"${requestInputs.method}\",\n url: \"${requestInputs.url}\",\n options: ${JSON.stringify(requestInputs.options)}\n }\n `);\n }\n};\n\n/**\n * Checks the status code and body of an HTTP response\n *\n * @param {number} statusCode - The status code of the HTTP response.\n * @param {Buffer} body - The body of the HTTP response.\n * @throws {Error} if the status code is >= 300.\n */\nexport const checkValidStatusCode = (statusCode: number, body: Buffer) => {\n if (statusCode >= 300) {\n throw new Error(`\nServer responded with status code\n ${statusCode}\n\nBody:\n ${body.toString()}\n\nUse 'res.body' instead of 'res.getBody()' to not have any errors thrown.\n\nThe status code (in this case, ${statusCode}) can be checked manually\nwith res.statusCode.\n `);\n }\n};\n","import { Curl, Easy, HttpPostField } from 'node-libcurl';\nimport { HttpVerb, Options, Response, BufferEncoding, GetJSON } from './types';\nimport {\n checkValidStatusCode,\n checkValidCurlCode,\n handleQs,\n parseReturnedHeaders,\n parseIncomingHeaders,\n} from './utils';\n\n/**\n * Create a libcurl Easy object with default configurations\n *\n * @param {HttpVerb} method - The HTTP method (e.g., 'GET', 'POST', 'PUT')\n * @param {Options} options - configuration options for the request.\n * @returns {Easy} an initialized libcurl Easy object with default options\n */\nconst createCurlObjectWithDefaults = (\n method: HttpVerb,\n options: Options\n): Easy => {\n const curl = new Easy();\n curl.setOpt(Curl.option.CUSTOMREQUEST, method);\n curl.setOpt(Curl.option.TIMEOUT_MS, options.timeout ?? 0);\n curl.setOpt(\n Curl.option.FOLLOWLOCATION,\n options.followRedirects === undefined || options.followRedirects\n );\n curl.setOpt(Curl.option.MAXREDIRS, options.maxRedirects ?? -1);\n curl.setOpt(Curl.option.SSL_VERIFYPEER, !options.insecure);\n curl.setOpt(Curl.option.NOBODY, method === 'HEAD');\n return curl;\n};\n\n/**\n * Handles query string parameters in a URL, modifies the URL if necessary,\n * and sets it as the CURLOPT_URL option in the given cURL Easy object.\n *\n * @param {Easy} curl - The cURL easy handle\n * @param {string} url - The URL to handle query string parameters for\n * @param {Object.<string, any>} qs - query string parameters for the request\n */\nconst handleQueryString = (\n curl: Easy,\n url: string,\n qs?: { [key: string]: any }\n): void => {\n url = qs && Object.keys(qs).length ? handleQs(url, qs) : url;\n curl.setOpt(Curl.option.URL, url);\n};\n\n/**\n * Sets up a callback function for the cURL Easy object to handle returned\n * headers and populate the input array with header lines.\n *\n * @param {Easy} curl - The cURL easy handle\n * @param {string[]} returnedHeaderArray - array for returned header lines\n */\nconst handleOutgoingHeaders = (curl: Easy, returnedHeaderArray: string[]) => {\n curl.setOpt(Curl.option.HEADERFUNCTION, (headerLine) => {\n returnedHeaderArray.push(headerLine.toString('utf-8').trim());\n return headerLine.length;\n });\n};\n\n/**\n * Sets the JSON payload for the curl request.\n * @param {Easy} curl - The curl object.\n * @param {any} json - The JSON body to be sent\n * @param {string[]} httpHeaders - HTTP headers for the request\n */\nconst setJSONPayload = (curl: Easy, json: any, httpHeaders: string[]): void => {\n httpHeaders.push('Content-Type: application/json');\n const payload = JSON.stringify(json);\n httpHeaders.push(`Content-Length: ${Buffer.byteLength(payload, 'utf-8')}`);\n curl.setOpt(Curl.option.POSTFIELDS, payload);\n};\n\n/**\n * Sets the buffer payload for the curl request.\n * @param {Easy} curl - The curl object.\n * @param {string | Buffer} body - The body to be sent in the request.\n * @param {string[]} httpHeaders - HTTP headers for the request\n */\nconst setBodyPayload = (\n curl: Easy,\n body: string | Buffer,\n httpHeaders: string[]\n): void => {\n if (Buffer.isBuffer(body)) {\n let position = 0;\n curl.setOpt(Curl.option.POST, true);\n curl.setOpt(Curl.option.POSTFIELDSIZE, -1);\n curl.setOpt(\n Curl.option.READFUNCTION,\n (buffer: Buffer, size: number, nmemb: number): number => {\n const amountToRead = size * nmemb;\n if (position === body.length) {\n return 0;\n }\n const totalWritten = body.copy(\n buffer,\n 0,\n position,\n Math.min(amountToRead, body.length)\n );\n position += totalWritten;\n return totalWritten;\n }\n );\n } else {\n curl.setOpt(Curl.option.POSTFIELDS, body);\n httpHeaders.push(`Content-Length: ${Buffer.byteLength(body, 'utf-8')}`);\n }\n};\n\n/**\n * Sets the buffer payload for the curl request.\n * @param {Easy} curl - The curl object.\n * @param {Array<HttpPostField>} formData - list of files/contents to be sent\n */\nconst setFormPayload = (curl: Easy, formData: HttpPostField[]) => {\n curl.setOpt(Curl.option.HTTPPOST, formData);\n};\n\n/**\n * Prepares the request body and headers for a cURL request based on provided\n * options. Also sets up a callback function for the cURL Easy object to handle\n * returned body and populates the input buffet.\n *\n * @param {Easy} curl - The cURL easy handle\n * @param {Options} options - Options for configuring the request\n * @param {{ body: Buffer }} buffer - wrapped buffer for the returned body\n * @param {string[]} httpHeaders - HTTP headers for the request\n */\nconst handleBodyAndRequestHeaders = (\n curl: Easy,\n options: Options,\n buffer: { body: Buffer },\n httpHeaders: string[]\n): void => {\n if (options.json) {\n setJSONPayload(curl, options.json, httpHeaders);\n } else if (options.body) {\n setBodyPayload(curl, options.body, httpHeaders);\n } else if (options.formData) {\n setFormPayload(curl, options.formData);\n } else {\n httpHeaders.push('Content-Length: 0');\n }\n curl.setOpt(Curl.option.WRITEFUNCTION, (buff, nmemb, size) => {\n buffer.body = Buffer.concat([buffer.body, buff.subarray(0, nmemb * size)]);\n return nmemb * size;\n });\n\n curl.setOpt(Curl.option.HTTPHEADER, httpHeaders);\n};\n\n/**\n * Performs an HTTP request using cURL with the specified parameters.\n *\n * @param {HttpVerb} method - The HTTP method for the request (e.g., 'GET', 'POST')\n * @param {string} url - The URL to make the request to\n * @param {Options} [options={}] - An object to configure the request\n * @returns {Response} - HTTP response consisting of status code, headers, and body\n */\nconst request = (\n method: HttpVerb,\n url: string,\n options: Options = {}\n): Response => {\n const curl = createCurlObjectWithDefaults(method, options);\n handleQueryString(curl, url, options.qs);\n\n // Body/JSON and Headers (incoming)\n const bufferWrap = { body: Buffer.alloc(0) };\n handleBodyAndRequestHeaders(\n curl,\n options,\n bufferWrap,\n parseIncomingHeaders(options.headers)\n );\n\n // Headers (outgoing)\n const returnedHeaderArray: string[] = [];\n handleOutgoingHeaders(curl, returnedHeaderArray);\n\n if (options.setEasyOptions) {\n options.setEasyOptions(curl, Curl.option);\n }\n\n // Execute request\n const code = curl.perform();\n checkValidCurlCode(code, { method, url, options });\n\n // Creating return object\n const statusCode = curl.getInfo('RESPONSE_CODE').data as number;\n const headers = parseReturnedHeaders(returnedHeaderArray);\n const { body } = bufferWrap;\n\n /**\n * Get the body of a response with an optional encoding.\n *\n * @throws {Error} if the status code is >= 300\n * @returns {Buffer | string} buffer body by default, string body with encoding\n */\n\n function getBody<Encoding extends BufferEncoding>(encoding: Encoding): string;\n function getBody(encoding?: undefined): Buffer;\n function getBody(encoding?: BufferEncoding): string | Buffer {\n checkValidStatusCode(statusCode, body);\n return encoding ? body.toString(encoding) : body;\n }\n\n /**\n * Get the JSON-parsed body of a response.\n *\n * @throws {Error} if the body is into a valid JSON\n * @returns {any} parsed JSON body\n */\n const getJSON: GetJSON = (encoding?) => {\n try {\n return JSON.parse(body.toString(encoding));\n } catch (err) {\n throw new Error(`\nThe server body response for\n - ${method}\n - ${url}\ncannot be parsed as JSON.\n\nBody:\n ${body.toString(encoding)}\n\nJSON-Parsing Error Message:\n ${err instanceof Error ? err.message : String(err)}\n `);\n }\n };\n\n url = curl.getInfo('EFFECTIVE_URL').data as string;\n\n curl.close();\n return { statusCode, headers, url, body, getBody, getJSON };\n};\n\nexport default request;\n","import request from './request';\n\nexport type {\n HttpVerb,\n BufferEncoding,\n Options,\n Response,\n SetEasyOptionCallback,\n} from './types';\n\nexport type { HttpPostField } from 'node-libcurl';\n\nexport default request;\n"],"mappings":"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"}