Request-libcurl
npm install --save request-libcurl
This is a server-only package. This package was created due to a lack of stability in the Node's http
/https
ClientRequest and simplicity in fetch
modules. Since we've been looking for something tested by decades and generations, — our choice stopped on libcurl
, later core library might be changed, but we would keep same API and idea about fast, sustainable and simple HTTP requests.
Main features
- 👨💻 98% tests coverage + TDD (only for http(s));
- 👷♂️ Follow
request
API (simplified); - 📦 The single dependency on
node-libcurl
package; - ㊗️ IDNs support (internationalized domain names);
- 🛡 Repeat (built-in retries) request on failed or broken connection;
- 😎 HTTP/2 support;
- 🤘 HTTP/3 support!
- 🎒 Send GET/POST with custom
body
and headers; - 🗂 Pipe to the file;
- 🚦 Follow or deny redirects;
- 📤 Upload files with a single line;
- 🔐 Ignore or deny "broken" SSL/TLS certificates;
- 💪 Bulletproof design, during development we avoid complex solutions.
ToC:
Install
# ONLY for node@^16.14
npm install request-libcurl --save
# ONLY for node@^14.14 || >=16 || <16.14
npm install request-libcurl@3.0.0 --save
# for node@>=9.0.0
npm install request-libcurl@2.3.4 --save
import request from 'request-libcurl';
const request = require('request-libcurl');
Note
We build this package to serve our needs and solve our issues with Node's native API. It may have a lack of compatibility with request()
module API, or compatible only partially.
API
import request from 'request-libcurl';
const opts = {
method: 'GET',
url: 'https://example.com',
auth: 'username:password',
form: '{"ops": "value"}',
headers: {
Accept: '*/*',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36'
},
debug: false,
retry: true,
retries: 3,
timeout: 6144,
keepAlive: false,
retryDelay: 256,
followRedirect: true,
maxRedirects: 4,
rejectUnauthorized: false
};
request(opts, (error, resp) => {
if (error) {
const { errorCode, code, statusCode, message } = error;
} else {
const { statusCode, body, headers } = resp;
}
});
Request default options
import request from 'request-libcurl';
request.defaultOptions = {
wait: false,
proxy: false,
retry: true,
debug: false,
method: 'GET',
timeout: 6144,
retries: 3,
rawBody: false,
keepAlive: false,
noStorage: false,
retryDelay: 256,
maxRedirects: 4,
followRedirect: true,
rejectUnauthorized: false,
rejectUnauthorizedProxy: false,
badStatuses: [ 300, 303, 305, 400, 407, 408, 409, 410, 500, 502, 503, 504, 510 ],
isBadStatus(statusCode, badStatuses = request.defaultOptions.badStatuses) {
return badStatuses.includes(statusCode) || statusCode >= 500;
},
headers: {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36',
Accept: '*/*'
}
};
request.defaultOptions.timeout = 7000;
request.defaultOptions.retries = 12;
request.defaultOptions.retryDelay = 5000;
request.defaultOptions.followRedirect = false;
request.defaultOptions.badStatuses = [300, 303, 305, 400, 407, 408, 409, 410];
request.defaultOptions.isBadStatus = (statusCode, badStatuses = request.defaultOptions.badStatuses) => {
return badStatuses.includes(statusCode) || statusCode >= 500;
};
Request options
opts.url
or opts.uri
{String} - [Required] Fully qualified URI with protocol http
/https
;opts.method
{String} - [Optional] HTTP Method name, you can use any valid method name from HTTP specs, tested with GET/POST, default: GET
;opts.auth
{String} - [Optional] value for HTTP Authorization header as plain string in a form of username:password
;opts.form
{String|Object} - [Optional] Custom request body for POST request. If {String} is passed Content-Type
will be set to application/x-www-form-urlencoded
, by passing plain {Object} Content-Type
will be set to application/json
. To set custom Content-Type
— pass it to opts.headers
Object;opts.upload
{Integer} - [Optional] To upload a file pass an Integer representing the file descriptor. See this example for reference;opts.pipeTo
{stream.Writable} - [Optional] Pass response data to writableStream, for example download a file to FS via {pipeTo: fs.createWriteStream('/path/to/file.pdf')}
;opts.headers
{Object} - [Optional] Custom request headers, default: { Accept: '*/*', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.103 Safari/537.36' }
. Note: setting custom request headers will replace default ones;opts.debug
{Boolean} - [Optional] Enable debug and extra logging, default: false
;opts.retry
{Boolean} - [Optional] Retry request if connection is broken? Default: true
;opts.retries
{Number} - [Optional] How many times retry request if connection is broken, default: 3
;opts.retryDelay
{Number} - [Optional] How long to wait between request retries (ms), default: 256
;opts.timeout
{Number} - [Optional] How long to wait for response (ms), default: 6144
;opts.followRedirect
{Boolean} - [Optional] Shall request follow redirects? Default: true
;opts.keepAlive
{Boolean} - [Optional] Turn on TCP keepalive probes, default: false
;opts.maxRedirects
{Number} - [Optional] How many redirects are supported during single request, default: 4
;opts.badStatuses
{[Number]} - [Optional] Array of "bad" status codes responsible for triggering request retries, default: [300, 303, 305, 400, 407, 408, 409, 410, 500, 502, 503, 504, 510]
;opts.isBadStatus
{Function} - [Optional] Function responsible for triggering request retries, default (at the bottom of code-block);opts.rawBody
{Boolean} - Disable all data processing (body
will be passed as Buffer, headers
will be empty, use .onHeader()
callback to get headers with rawBody
option), great option for piping, default: false
;opts.noStorage
{Boolean} - Disable all data processing and data concatenation (headers
and body
won't be passed to response), great option for piping, default: false
;opts.wait
{Boolean} - Do not send request immediately and wait until .send()
method is called, set this option to true
to register .onHeader()
and .onBody()
callbacks, default: false
;opts.proxy
{String} - Fully qualified URL to HTTP proxy, when this feature is enabled connections are going to start with CONNECT
request, default: no proxy or system proxy is used;opts.rejectUnauthorized
{Boolean} - [Optional] Shall request be rejected if SSL/TLS certificate can't be validated? Default: false
;opts.rejectUnauthorizedProxy
{Boolean} - [Optional] Shall request be rejected if SSL/TLS certificate of a proxy host can't be validated? Default: false
;opts.curlOptions
{Object} - [Optional] Explicitly set libcurl
options, full list of options available here and here;opts.curlFeatures
{Object} - [Optional] Explicitly enable or disable libcurl
features. To enable a feature pass true
as a value, example: {NoDataParsing: true}
. To disable pass false
as a value, example: {NoDataParsing: false}
. Full list of available features is available here.
Notes:
- When using
opts.rawBody
callback won't return headers
, to get headers use onHeader
callback; - When using
opts.noStorage
callback won't return headers
and body
, to get headers and body use onData
and onHeader
callbacks; opts.upload
and opts.form
can not be used together, there won't be exception thrown, if both presented — opts.form
will be used instead;- When using
opts.upload
or any other request where server returns expect: '100-continue'
HTTP header — callback won't return headers
, to get headers use onHeader
callback; - This package is build on top of
libcurl
and node-libcurl
it's the way more powerful than just sending requests via http
and https
protocol. Libcurl can work with IMAP/SMTP protocols getting/sending emails. Libcurl can serve as fully-featured FTP-client. Here's full list of supported protocols: DICT
, FILE
, FTP
, FTPS
, Gopher
, HTTP
, HTTPS
, IMAP
, IMAPS
, LDAP
, LDAPS
, POP3
, POP3S
, RTMP
, RTSP
, SCP
, SFTP
, SMTP
, SMTPS
, Telnet
and TFTP
. To learn more on how to utilize all available power and features see docs of node-libcurl
and libcurl
itself.
Response
resp.statusCode
{Number} - HTTP response/status code;resp.body
{String} - Body of HTTP response, not modified or processed, as it is — plain text;resp.headers
{Object} - HTTP response headers as plain Object, all headers names are lower-cased.
Error
error.errorCode
{Number} - libcurl
internal error code;error.code
{Number} - libcurl
internal error code, same as errorCode
;error.statusCode
{Number} - HTTP error code, if any;error.message
{String} - Human-readable error.
Returns req
Object
import request from 'request-libcurl';
const req = request({ url: 'https://example.com' });
req.abort()
- Abort current request, request will return 499: Client Closed Request
HTTP errorreq.send()
- Send request, use it with wait
. For example with rawBody
/noStorage
, when you need to delay sending request, for example to set event listeners and/or callbacksreq.pipe(stream.Writable)
- Pipe response to a WritableStream, for example download a file to FS. Use with {wait: true, retry: false}
options, and .send()
methodreq.onData(callback)
- Hook, called right after data is received, called for each data-chunk. Useful with .pipe()
, rawBody
/noStorage
and callbacks/eventsreq.onHeader(callback)
- Hook, called right after header is received, called for each header. Useful with .pipe()
, rawBody
/noStorage
and callbacks/eventscallback(error, resp)
- Callback triggered on successful response
error
{undefined};resp.statusCode
{Number} - HTTP status code;resp.body
{String} - Body of HTTP response, not modified or processed, as it is — plain text;resp.headers
{Object} - Key-value plain Object with pairs of response headers;
callback(error)
- Callback triggered on failed request
error.errorCode
{Number} - libcurl
internal error code;error.code
{Number} - libcurl
internal error code, same as errorCode
;error.statusCode
{Number} - HTTP error code, if any;error.message
{String} - Human-readable error.
Examples
Send GET and POST requests, download and upload files — all just in few lines of code.
GET request
By default request-libcurl
will take care of chunked responses and encoding:
import request from 'request-libcurl';
request({ url: 'https://example.com' }, (error, resp) => {
console.log(resp.body)
});
For full control over request/response streams, chunks, and encoding use {rawBody: true, wait: true, retry: false}
options with .onData()
and .onHeader()
callbacks:
let responseBody = Buffer.from('');
let responseHeaders = Buffer.from('');
const headersObj = {};
const req = request({
url: 'https://example.com',
retry: false,
rawBody: true,
wait: true
}, (error) => {
if (error) {
throw error;
}
const body = responseBody.toString('utf8');
const headers = responseHeaders.toString('utf8');
console.log(headersObj);
});
req.onData((chunkAsBuffer) => {
responseBody = Buffer.concat([responseBody, chunkAsBuffer]);
});
req.onHeader((chunkAsBuffer) => {
responseHeaders = Buffer.concat([responseHeaders, chunkAsBuffer]);
const header = chunkAsBuffer.toString('utf8');
if (header.includes(':')) {
const splitHeader = header.split(':');
headersObj[splitHeader[0].toLowerCase().trim()] = splitHeader[1].trim();
}
});
req.send();
POST request
import request from 'request-libcurl';
import querystring from 'querystring';
request({
method: 'POST',
url: 'https://example.com',
form: querystring.stringify({ myForm: 'data' })
}, (error, resp) => {
});
request({
method: 'POST',
url: 'https://example.com',
auth: 'username:passwd',
form: querystring.stringify({ myForm: 'data' })
}, (error, resp) => {
});
request({
method: 'POST',
url: 'https://example.com',
form: { myForm: 'data' }
}, (error, resp) => {
});
import request from 'request-libcurl';
request({
method: 'POST',
url: 'https://example.com',
auth: 'username:passwd',
form: { myForm: 'data' }
}, (error, resp) => {
});
request({
method: 'POST',
url: 'https://example.com',
form: 'Plain String or Base64 String or any other String',
headers: {
'Content-Type': 'text/plain'
}
}, (error, resp) => {
});
File download
Download a file to the FileSystem by passing {stream.Writable}
File download using pipeTo
option
Download a file to the FileSystem by passing {stream.Writable} to pipeTo
option:
import fs from 'fs';
import request from 'request-libcurl';
const req = request({
url: 'https://example.com/file.pdf',
pipeTo: fs.createWriteStream('/path/to/file.pdf', {flags: 'w'}),
retry: false
}, (error, resp) => {
if (error) {
throw error;
} else {
fs.stat('/path/to/file.pdf', (error, stats) => {
});
}
});
File download via .pipe()
method
Download a file to the FileSystem using .pipe()
method:
import fs from 'fs';
import request from 'request-libcurl';
const req = request({
url: 'https://example.com/file.pdf',
wait: true,
retry: false
}, (error, resp) => {
if (error) {
throw error;
} else {
fs.stat('/path/to/file.pdf', (error, stats) => {
});
}
});
req.pipe(fs.createWriteStream('/path/to/file.pdf', {flags: 'w'}));
req.send();
File upload
import fs from 'fs';
import request from 'request-libcurl';
fs.open('/path/to/a/file', 'r', function(err, fd) {
if (err) {
throw new Error('can not read the file');
}
request({
method: 'POST',
url: 'https://example.com/upload',
upload: fd,
retry: false,
}, (error, resp) => {
if (error) {
throw error;
} else {
}
});
});
File upload (multipart/form-data
)
In this example we are going to use HTTPPOST
libcurl option passing [Object]
(array of Objects representing files, note: multiple files can be passed in a single request) via curlOptions
import request from 'request-libcurl';
const fileLocation = '/full/absolute/path/to/a/file.ext';
request({
method: 'POST',
url: 'https://example.com/upload.php',
retry: false,
curlOptions: {
HTTPPOST: [{
name: 'file.ext',
file: fileLocation,
type: 'application/ext'
} ]
}
}, (error) => {
if (error) {
throw error;
} else {
}
});
Known Issues
Got an issue? Start with checking "Known Issues" section below.
1. SSL connect error code: 35
To address most common issue with SSL certificates and speed up response time — SSL/TLS certificates validation is disabled in this package by default. But on edge cases this may return error-code 35
on SNI-enabled hosts. To solve this issue add { rejectUnauthorized: true }
to request object.
To change rejectUnauthorized
option globally use:
request.defaultOptions.rejectUnauthorized = true;
2. Compiled against Different Node.js version
Due to single dependency on node-libcurl
which shipped with statically built binaries, you may encounter This module was compiled against a different Node.js version using NODE_MODULE_VERSION
error. This may happen on edge cases, like running the very latest release of node.js (while bundled builds aren't shipped yet), then you may want to build this package locally, use one of next commands:
# Please see options below, in dependence from version of NPM and Node.js
# one of this options should solve this issue
# Option 1: Update and rebuild locally installed binaries
npm rebuild --update-binary --build-from-source
# Option 2: Build library
npm install --save request-libcurl --build-from-source
# Option 3: Build library and curl executables:
npm install --save request-libcurl --build-from-source --curl_static_build=true
# In case if you encounter errors during building package locally:
# 1. Execute same command as "sudo" (e.g. administrator), and try again
# 2. Install globally node-gyp and node-pre-gyp NPM packages, and try again
3. Missing libraries
Some of indirect dependencies of libcurl
might be missing. Errors related to missing dependencies include Library not loaded
, and image not found
in error's description/output.
zstd
One of curl
dependency libraries is zstd
. See installation instruction below or build from source
# Error: Library not loaded: /usr/local/opt/zstd/lib/libzstd.1.dylib
# Reason: image not found
# Solution 1: macOS Install via brew
brew install zstd
# Solution 2: Linux/Debian/Ubuntu Install via apt-get
apt-get update
apt-get install zstd
# Solution 3: Linux/CentOS/RHEL Install via yum
yum install zstd
# Solution 4: Unix build from source
# Up to date docs — https://github.com/facebook/zstd
# Download latest release from here — https://github.com/facebook/zstd/releases
# For example as of 2020-06-02 — zstd-1.4.8.tar.gz
curl https://github.com/facebook/zstd/releases/download/v1.4.8/zstd-1.4.8.tar.gz -O
make
make install # might require sudo/root permissions
# Optionally test compiled binary with:
make check
For more details and instructions for different platforms read node-libcurl
official docs. Note: It's highly recommended to run tests after building package locally.
Running Tests
- Clone this package
- In Terminal (Console) go to directory where package is cloned
- Then run:
# Install development NPM dependencies:
npm install --save-dev
# Install NPM dependencies:
npm install --save
# Run tests:
PORT=3003 npm test
# Run tests and output debugging details:
DEBUG=true PORT=3003 npm test
# PORT env.var is required! And can be changed to any open port!
# Note: The Internet connection is required to perform tests
# Note: Test-suite includes "no response" and "timing out responses"
# if a test looks stuck — give it another minute before interrupting it
Support our open source contribution