Socket
Socket
Sign inDemoInstall

request-libcurl

Package Overview
Dependencies
Maintainers
1
Versions
27
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

request-libcurl - npm Package Compare versions

Comparing version 1.0.3 to 2.0.0

4

CONTRIBUTING.md
### I'm having an issue:
1. Search [issues](https://github.com/VeliovGroup/request-extra/issues), maybe your issue is already solved
2. Before submitting an issue make sure it's only related to `request-extra` package
2. Before submitting an issue make sure it's only related to `request-libcurl` package
3. If your issue is not solved:
- Give an expressive description of what is went wrong
- Version of `request-extra` you're experiencing this issue
- Version of `request-libcurl` you're experiencing this issue
- Version of `Node.js`, `NPM` and `MongoDB` you're experiencing this issue

@@ -10,0 +10,0 @@ - If you're getting an error (exception), please provide full error log (output in console) as file or screenshot

@@ -6,17 +6,75 @@ 'use strict';

const request = function LibCurlRequest (_opts, cb) {
const opts = Object.assign({}, request.defaultOptions, _opts);
opts.method = opts.method.toUpperCase();
let sent = false;
const badUrlError = {
code: 3,
status: 400,
message: '400: URL Malformed / Invalid URL',
errorCode: 3,
statusCode: 400
};
const badRequestError = {
code: 43,
status: 400,
message: '400: Bad request',
errorCode: 43,
statusCode: 400
};
const abortError = {
code: 42,
status: 408,
message: '408: Request aborted (timeout)',
errorCode: 42,
statusCode: 408
};
const noop = () => {};
const _debug = (...args) => {
console.info.call(console, '[DEBUG] [request-libcurl]', ...args);
};
const closeCurl = (curl) => {
try {
if (curl && curl.close) {
curl.close.call(curl);
}
} catch (err) {
// we are good here
}
};
const sendRequest = (libcurl, url, cb) => {
libcurl._debug('[sendRequest]', url.href);
closeCurl(libcurl.curl);
const opts = libcurl.opts;
const curl = new Curl();
let finished = false;
let retryTimer = null;
let timeoutTimer = null;
let reject = () => {};
const curl = new Curl();
const url = new URL(opts.uri);
let isJsonUpload = false;
let hasContentType = false;
let hasContentLength = false;
let hasAcceptEncoding = false;
if (opts.noStorage || opts.rawBody) {
curl.enable(CurlFeature.NO_STORAGE);
const stopRequestTimeout = () => {
if (timeoutTimer) {
clearTimeout(timeoutTimer);
timeoutTimer = null;
}
};
timeoutTimer = setTimeout(() => {
libcurl.abort();
}, opts.timeout + 1000);
if (opts.rawBody) {
curl.enable(CurlFeature.Raw);
}
if (opts.noStorage) {
curl.enable(CurlFeature.NoStorage);
}
curl.setOpt('URL', url.href);

@@ -32,3 +90,3 @@ curl.setOpt(Curl.option.VERBOSE, opts.debug);

curl.setOpt(Curl.option.NOPROGRESS, true);
curl.setOpt(Curl.option.TIMEOUT_MS, opts.timeout * 2);
curl.setOpt(Curl.option.TIMEOUT_MS, opts.timeout);
curl.setOpt(Curl.option.MAXREDIRS, opts.maxRedirects);

@@ -38,8 +96,12 @@ curl.setOpt(Curl.option.CUSTOMREQUEST, opts.method);

curl.setOpt(Curl.option.SSL_VERIFYPEER, opts.rejectUnauthorized ? 1 : 0);
curl.setOpt(Curl.option.SSL_VERIFYHOST, 0);
curl.setOpt(Curl.option.PROXY_SSL_VERIFYPEER, opts.rejectUnauthorizedProxy ? 1 : 0);
curl.setOpt(Curl.option.SSL_VERIFYHOST, opts.rejectUnauthorized ? 2 : 0);
curl.setOpt(Curl.option.PROXY_SSL_VERIFYHOST, opts.rejectUnauthorizedProxy ? 2 : 0);
curl.setOpt(Curl.option.CONNECTTIMEOUT_MS, opts.timeout);
if (opts.keepAlive === true) {
curl.setOpt(Curl.option.TCP_KEEPALIVE, 1);
} else {
curl.setOpt(Curl.option.TCP_KEEPALIVE, 0);
}
curl.setOpt(Curl.option.ACCEPT_ENCODING, '');

@@ -51,4 +113,13 @@ const customHeaders = [];

let lcHeader = '';
for (let header in opts.headers) {
if (opts.headers[header]) {
lcHeader = header.toLowerCase();
if (lcHeader === 'content-type') {
hasContentType = true;
} else if (lcHeader === 'content-length') {
hasContentLength = true;
} else if (lcHeader === 'accept-encoding') {
hasAcceptEncoding = opts.headers[header];
}
customHeaders.push(`${header}: ${opts.headers[header]}`);

@@ -58,2 +129,8 @@ }

if (!hasAcceptEncoding) {
curl.setOpt(Curl.option.ACCEPT_ENCODING, '');
} else {
curl.setOpt(Curl.option.ACCEPT_ENCODING, hasAcceptEncoding);
}
if (opts.auth) {

@@ -63,116 +140,121 @@ customHeaders.push(`Authorization: Basic ${Buffer.from(opts.auth).toString('base64')}`);

opts.debug && console.info('[request-libcurl] REQUEST:', url.href);
if (libcurl.onData) {
curl.on('data', libcurl.onData);
}
const stopRetryTimeout = () => {
if (retryTimer) {
clearTimeout(retryTimer);
retryTimer = null;
}
};
if (libcurl.onHeader) {
curl.on('header', libcurl.onHeader);
}
const stopRequestTimeout = () => {
if (timeoutTimer) {
clearTimeout(timeoutTimer);
timeoutTimer = null;
}
stopRetryTimeout();
};
curl.on('end', (statusCode, body, _headers) => {
libcurl._debug('[END EVENT]', opts.retries, url.href, finished, statusCode);
stopRequestTimeout();
libcurl.stopRequestTimeout();
if (finished) { return; }
const retry = () => {
opts.debug && console.info('[request-libcurl] REQUEST RETRY:', opts.retries, opts.retryDelay, url.href);
stopRetryTimeout();
retryTimer = setTimeout(() => {
--opts.retries;
request(opts, cb);
}, opts.retryDelay);
};
finished = true;
curl.close();
const promise = new Promise((resolve, _reject) => {
reject = _reject;
curl.on('end', (statusCode, body, _headers) => {
opts.debug && console.info('[request-libcurl] REQUEST END:', opts.retries, url.href, statusCode);
stopRequestTimeout();
if (finished) { return; }
curl.close();
if ((opts.isBadStatus(statusCode || 408, opts.badStatuses)) && opts.retry === true && opts.retries > 1) {
retry();
} else {
finished = true;
const headers = {};
if (_headers && _headers[0]) {
delete _headers[0].result;
for (let headerName in _headers[0]) {
if (_headers[0][headerName]) {
headers[headerName.toLowerCase()] = _headers[0][headerName];
}
}
const headers = {};
if (_headers && _headers[0]) {
delete _headers[0].result;
for (let headerName in _headers[0]) {
if (_headers[0][headerName]) {
headers[headerName.toLowerCase()] = _headers[0][headerName];
}
cb ? cb(void 0, {statusCode, body, headers}) : resolve({statusCode, body, headers});
}
});
}
curl.on('error', (error, errorCode) => {
opts.debug && console.error('[request-libcurl] REQUEST ERROR:', opts.retries, url.href, {error, errorCode});
stopRequestTimeout();
if (finished) { return; }
curl.close();
cb(void 0, {statusCode, status: statusCode, body, headers});
});
error.code = errorCode;
error.message = typeof error.toString === 'function' ? error.toString() : 'Error occurred during request';
error.errorCode = errorCode;
error.statusCode = 408;
curl.on('error', (error, errorCode) => {
libcurl._debug('REQUEST ERROR:', opts.retries, url.href, {error, errorCode});
stopRequestTimeout();
libcurl.stopRequestTimeout();
if (finished) { return; }
if (opts.retry === true && opts.retries > 1) {
retry();
} else {
finished = true;
curl.close();
let statusCode = 408;
if (errorCode === 52) {
statusCode = 503;
} else if (errorCode === 47) {
statusCode = 429;
} else if (errorCode === 60 || errorCode === 91) {
statusCode = 526;
}
error.code = errorCode;
error.status = statusCode;
error.message = typeof error.toString === 'function' ? error.toString() : 'Error occurred during request';
error.errorCode = errorCode;
error.statusCode = statusCode;
cb(error);
});
if (opts.form) {
if (typeof opts.form === 'object') {
isJsonUpload = true;
}
if (typeof opts.form !== 'string') {
try {
opts.form = JSON.stringify(opts.form);
} catch (e) {
libcurl._debug('Can\'t stringify opts.form in POST request :', url.href, e);
finished = true;
cb ? cb(error) : reject(error);
process.nextTick(() => {
libcurl.finished = true;
curl.close();
cb(badRequestError);
});
return curl;
}
});
}
if (opts.method === 'POST' && opts.form) {
if (typeof opts.form !== 'string') {
try {
opts.form = JSON.stringify(opts.form);
} catch (e) {
opts.debug && console.error('[request-libcurl] Can\'t stringify opts.form in POST request :', url.href, e);
const error = {
code: 43,
message: '408: Bad request',
errorCode: 43,
statusCode: 400
};
cb ? cb(error) : reject(error);
}
if (!hasContentType) {
if (isJsonUpload) {
customHeaders.push('Content-Type: application/json');
} else {
customHeaders.push('Content-Type: application/x-www-form-urlencoded');
}
}
customHeaders.push('Content-Type: application/x-www-form-urlencoded');
if (!hasContentLength) {
customHeaders.push(`Content-Length: ${Buffer.byteLength(opts.form)}`);
curl.setOpt(Curl.option.POSTFIELDS, opts.form);
}
curl.setOpt(Curl.option.HTTPHEADER, customHeaders);
curl.setOpt(Curl.option.POSTFIELDS, opts.form);
} else if (opts.upload) {
curl.setOpt(Curl.option.UPLOAD, true);
curl.setOpt(Curl.option.READDATA, opts.upload);
}
timeoutTimer = setTimeout(() => {
stopRequestTimeout();
if (finished) { return; }
curl.close();
if (opts.retries > 0) {
retry();
} else {
finished = true;
const error = {
code: 28,
message: '408: host unreachable, request timeout',
errorCode: 28,
statusCode: 408
};
cb ? cb(error) : reject(error);
if (opts.curlOptions && typeof opts.curlOptions === 'object') {
for (let option in opts.curlOptions) {
if (Curl.option[option] !== undefined) {
curl.setOpt(Curl.option[option], opts.curlOptions[option]);
}
}, opts.timeout + 2500);
}
}
if (!opts.wait) {
if (sent) { return; }
sent = true;
if (opts.curlFeatures && typeof opts.curlFeatures === 'object') {
for (let option in opts.curlFeatures) {
if (CurlFeature[option] !== undefined) {
if (opts.curlFeatures[option] === true) {
curl.enable(CurlFeature[option]);
} else if (opts.curlFeatures[option] === false) {
curl.disable(CurlFeature[option]);
}
}
}
}
curl.setOpt(Curl.option.HTTPHEADER, customHeaders);
process.nextTick(() => {
if (!libcurl.finished) {
curl.perform();

@@ -182,23 +264,142 @@ }

promise.request = curl;
promise.send = () => {
if (sent) { return; }
sent = true;
curl.perform();
};
promise.abort = () => {
if (finished) { return; }
finished = true;
curl.close();
const error = {
code: 42,
message: '499: Client Closed Request',
errorCode: 42,
statusCode: 499
};
cb ? cb(error) : reject(error);
};
return promise;
return curl;
};
class LibCurlRequest {
constructor (opts, cb) {
let isBadUrl = false;
this.cb = cb || noop;
this.sent = false;
this.finished = false;
this.retryTimer = false;
this.timeoutTimer = null;
this.opts = Object.assign({}, request.defaultOptions, opts);
this.opts.method = this.opts.method.toUpperCase();
if (this.opts.debug) {
this._debug = _debug;
} else {
this._debug = noop;
}
this._debug('[constructor]', this.opts.uri || this.opts.url);
this.stopRequestTimeout = () => {
if (this.timeoutTimer) {
clearTimeout(this.timeoutTimer);
this.timeoutTimer = null;
}
};
if (!this.opts.uri && !this.opts.url) {
this._debug('REQUEST: NO URL PROVIDED ERROR:', opts);
isBadUrl = true;
} else {
try {
this.url = new URL(this.opts.uri || this.opts.url);
} catch (urlError) {
this._debug('REQUEST: `new URL()` ERROR:', opts, urlError);
isBadUrl = true;
}
}
if (isBadUrl) {
this.sent = true;
this.finished = true;
process.nextTick(() => {
this.cb(badUrlError);
});
return;
}
if (!this.opts.wait) {
this.send();
}
}
onData(callback) {
this.onData = callback;
}
onHeader(callback) {
this.onHeader = callback;
}
_retry() {
this._debug('[_retry]', this.opts.retry, this.opts.retries, this.opts.uri || this.opts.url);
if (this.opts.retry === true && this.opts.retries > 0) {
--this.opts.retries;
this.retryTimer = setTimeout(() => {
this.currentCurl = sendRequest(this, this.url, this._sendRequestCallback.bind(this));
}, this.opts.retryDelay);
return true;
}
return false;
}
_sendRequestCallback(error, result) {
this._debug('[_sendRequestCallback]', this.opts.uri || this.opts.url);
let isRetry = false;
let statusCode = 408;
if (result && result.statusCode) {
statusCode = result.statusCode;
} else if (error && error.statusCode) {
statusCode = error.statusCode;
}
if (error && error.errorCode !== 47) {
isRetry = this._retry();
} else if ((this.opts.isBadStatus(statusCode, this.opts.badStatuses)) && this.opts.retry === true && this.opts.retries > 1) {
isRetry = this._retry();
}
if (!isRetry) {
this.finished = true;
if (error) {
this.cb(error);
} else {
this.cb(void 0, result);
}
}
}
send() {
this._debug('[send]', this.opts.uri || this.opts.url);
if (this.sent) {
return this;
}
this.sent = true;
this.timeoutTimer = setTimeout(() => {
this.abort();
}, ((this.opts.timeout + this.opts.retryDelay) * (this.opts.retries + 1)));
this.curl = sendRequest(this, this.url, this._sendRequestCallback.bind(this));
return this;
}
abort() {
this._debug('[abort]', this.opts.uri || this.opts.url);
if (this.retryTimer) {
clearTimeout(this.retryTimer);
this.retryTimer = false;
}
if (!this.finished) {
closeCurl(this.curl);
this.finished = true;
this.cb(abortError);
}
return this;
}
}
function request (opts, cb) {
return new LibCurlRequest(opts, cb);
}
request.defaultOptions = {

@@ -219,3 +420,4 @@ wait: false,

rejectUnauthorized: false,
badStatuses: [300, 303, 305, 400, 407, 408, 409, 410, 500, 510],
rejectUnauthorizedProxy: false,
badStatuses: [300, 303, 305, 400, 407, 408, 409, 410, 500, 502, 503, 504, 510],
isBadStatus(statusCode, badStatuses = request.defaultOptions.badStatuses) {

@@ -222,0 +424,0 @@ return badStatuses.includes(statusCode) || statusCode >= 500;

{
"name": "request-libcurl",
"version": "1.0.3",
"description": "Extremely stable HTTP request module built on top of libcurl with retries, timeouts, Promise and async/await API",
"version": "2.0.0",
"description": "Extremely stable HTTP request module built on top of libcurl",
"main": "index.js",
"scripts": {},
"scripts": {
"test": "mocha ./test/npm.js"
},
"repository": {

@@ -34,6 +36,9 @@ "type": "git",

"homepage": "https://github.com/VeliovGroup/request-extra#readme",
"devDependencies": {},
"devDependencies": {
"chai": "^4.2.0",
"mocha": "^6.2.1"
},
"dependencies": {
"node-libcurl": "^2.0.1"
"node-libcurl": "^2.0.2"
}
}

@@ -1,2 +0,2 @@

# Request-Extra
# Request-libcurl

@@ -13,14 +13,37 @@ <a href="https://www.patreon.com/bePatron?u=20396046">

## Main features:
## Main features
- 👷‍♂️ Follow `request` API;
- 👨‍💻 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 broken connection;
- 😎 HTTP/2 support;
- 😎 Repeat (*built-in retries*) request on failed or broken connection;
- 😎 Send GET/POST with custom `body` and headers;
- 😎 Follow or deny redirects;
- 💪 Bulletproof design, during development we plan to avoid complex solutions.
- 📤 Upload files with a single line;
- 🔐 Ignore or deny "broken" SSL/TLS certificates;
- 💪 Bulletproof design, during development we avoid complex solutions.
## Install:
## ToC:
- [Installation](https://github.com/VeliovGroup/request-extra#install)
- [API](https://github.com/VeliovGroup/request-extra#api)
- [Request options *detailed* description](https://github.com/VeliovGroup/request-extra#request-options):
- [response description (*success*)](https://github.com/VeliovGroup/request-extra#response)
- [error description (*fail*)](https://github.com/VeliovGroup/request-extra#error)
- [*LibCurlRequest* API](https://github.com/VeliovGroup/request-extra#returns-req-object)
- [List of default request options](https://github.com/VeliovGroup/request-extra#request-default-options)
- [Examples](https://github.com/VeliovGroup/request-extra#examples):
- [GET](https://github.com/VeliovGroup/request-extra#get-request)
- [POST](https://github.com/VeliovGroup/request-extra#post-request)
- [POST (*advanced*)](https://github.com/VeliovGroup/request-extra#post-request-with-extra-options)
- [File upload](https://github.com/VeliovGroup/request-extra#file-upload)
- [File upload (*multipart*)](https://github.com/VeliovGroup/request-extra#file-upload-multipartform-data)
- [Running tests](https://github.com/VeliovGroup/request-extra#running-tests)
- [Tests](https://github.com/VeliovGroup/request-extra/blob/master/test/npm.js)
- [Support](https://github.com/VeliovGroup/request-extra#support-our-open-source-contribution)
## Install
```shell

@@ -39,7 +62,7 @@ # ONLY for node@>=8.9.0

## Note:
## 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. Use on your own risk or wait for stable `v1.0.0`.
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:
## API

@@ -51,3 +74,3 @@ ```js

method: 'GET', // POST, GET
uri: 'https://example.com', // String
url: 'https://example.com', // String
auth: 'username:password', // String

@@ -70,23 +93,9 @@ form: '{"ops": "value"}', // String, can be JSON or any other type of payload

// Promise API v1
const promise = request(opts);
// Promise API v2
request(opts).then((resp) => {
const { statusCode, body, headers } = resp;
}).catch((error) => {
const { errorCode, code, statusCode, message } = error;
});
// Async/Await
async function get (opts) {
const { statusCode, body, headers } = await request(opts);
return body;
}
// Callback API
request(opts, (error, resp) => {
if (error) {
// Houston we got a problem! 😱
const { errorCode, code, statusCode, message } = error;
} else {
// We've got successful response! 🥳
const { statusCode, body, headers } = resp;

@@ -97,9 +106,56 @@ }

### Request options:
### Request default options
- `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`. If set to POST, `Content-Type: application/x-www-form-urlencoded` HTTP header would be set;
- `opts.auth` {*String*} - [Optional] value for HTTP Authorization header as plain string;
- `opts.form` {*String*|*Object*} - [Optional] Custom request body for POST request;
- `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' }`;
```js
const request = require('request-libcurl');
// Default "defaultOptions" Object:
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,
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: '*/*'
}
};
// Override default settings:
request.defaultOptions.timeout = 7000;
request.defaultOptions.retries = 12;
request.defaultOptions.retryDelay = 5000;
request.defaultOptions.followRedirect = false;
// Override bad statuses codes (used to trigger retries)
request.defaultOptions.badStatuses = [300, 303, 305, 400, 407, 408, 409, 410];
// Override function used to trigger retries based on status code
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](https://github.com/VeliovGroup/request-extra#file-upload) for reference;
- `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`;

@@ -113,20 +169,35 @@ - `opts.retry` {*Boolean*} - [Optional] Retry request if connection is broken? Default: `true`;

- `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, 510]`;
- `opts.isBadStatus` {*Function*} - [Optional] Function responsible for triggering request retries, default: *see at the bottom of examples section*;
- `opts.rawBody` and `opts.noStorage` {*Boolean*} - Disable all data processing, 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 listen events on `promise.request` object, default: `false`;
- `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*)](https://github.com/VeliovGroup/request-extra#request-default-options);
- `opts.rawBody` {*Boolean*} - Disable all data processing (`body` will be passed as *Buffer*, `headers` will be empty, use `.onHeaders()` hook 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 `.onHeaders()` and `.onBody()` hooks, 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 continue if SSL/TLS certificate can't be validated? Default: `false`.
- `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](https://curl.haxx.se/libcurl/c/curl_easy_setopt.html) and [here](https://github.com/JCMais/node-libcurl/blob/32647acc28b026bbd03aa1e3abea9cbbc8f7d43b/lib/generated/CurlOption.ts#L3286);
- `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](https://github.com/JCMais/node-libcurl/blob/249323f0f3883f8cbf6d0e91a89bfecb5862da53/lib/enum/CurlFeature.ts#L7).
__Note__: When using `opts.rawBody` or `opts.noStorage` callback/promise won't return `body` and `headers`, to get headers and body use next events:
__Notes__:
- When using `opts.rawBody` callback won't return `headers`, to get headers use `onHeaders` hook;
- When using `opts.noStorage` callback won't return `headers` and `body`, to get headers and body use `onData` and `onHeaders` hooks;
- `opts.upload` and `opts.form` __can not be used together__, there won't be exception thrown, if both presented — `opts.form` will be used;
- 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 `onHeaders` hook;
- This package is build on top of [`libcurl`](https://curl.haxx.se/libcurl/) and [`node-libcurl`](https://github.com/JCMais/node-libcurl) it's the way much 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`](https://github.com/JCMais/node-libcurl#node-libcurl) and [`libcurl`](https://curl.haxx.se/libcurl/) itself.
```js
let _body = Buffer.from('');
let _headers = Buffer.from('');
let _body = Buffer.from('');
let _headers = Buffer.from('');
const headersObj = {};
const promise = request({
uri: 'https://example.com',
const req = request({
url: 'https://example.com',
retry: false, // Do not retry with rawBody/noStorage, as it may mess up with headers and body inside `.onData()` and `.onHeader()` hooks
rawBody: true,
wait: true
}).then((status) => {
wait: true // Using 'wait' option to set `.onData()` and `.onHeader()` hooks
}, (error) => {
if (error) {
throw error;
}
const body = _body.toString('utf8');

@@ -136,14 +207,23 @@ const headers = _headers.toString('utf8');

promise.request.on('data', (chunkAsBuffer) => {
req.onData((chunkAsBuffer) => {
// Do something with a body
// .pipe() for example
_body = Buffer.concat([_body, chunkAsBuffer]);
});
promise.request.on('header', (chunkAsBuffer) => {
req.onHeader((chunkAsBuffer) => {
_headers = Buffer.concat([_headers, chunkAsBuffer]);
// or convert it to headers Object:
const header = chunkAsBuffer.toString('utf8');
if (header.includes(':')) {
const splitHeader = header.split(':');
headersObj[splitHeader[0].toLowerCase().trim()] = splitHeader[1].trim();
}
});
promise.send();
req.send();
```
### Response:
### Response

@@ -154,3 +234,3 @@ - `resp.statusCode` {*Number*} - HTTP response/status code;

### Error:
### Error

@@ -162,17 +242,19 @@ - `error.errorCode` {*Number*} - `libcurl` internal error code;

### Returns extended {*Promise*} Object:
### Returns `req` *Object*
```js
const request = require('request-libcurl');
const promise = request({uri: 'https://example.com'});
const req = request({url: 'https://example.com'});
````
- `promise.abort()` - Abort current request, request will return `499: Client Closed Request` HTTP error
- `promise.send()` - Send request, useful with `wait` and `rawBody`, when you need to delay sending request, for example to set event listeners
- `promise.request` {*ClientRequest*} - See [`node-libcurl` docs](https://github.com/JCMais/node-libcurl)
- `promise.then(resp)` - Callback triggered on successful response
- `resp.statusCode` {*Number*} - HTTP status code
- `req.abort()` - Abort current request, request will return `499: Client Closed Request` HTTP error
- `req.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 hooks
- `req.onData(callback)` - Hook, called right after data is received, called for each data-chunk. Useful with `.pipe()`, `rawBody`/`noStorage` and hooks/events
- `req.onHeader(callback)` - Hook, called right after header is received, called for each header. Useful with `.pipe()`, `rawBody`/`noStorage` and hooks/events
- `callback(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
- `promise.catch(error)` - Callback triggered on failed request
- `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;

@@ -183,17 +265,27 @@ - `error.code` {*Number*} - `libcurl` internal error code, same as `errorCode`;

## Examples:
## Examples
### GET request
```js
const request = require('request-libcurl');
// Simple GET:
request({ uri: 'https://example.com' }, (error, resp) => {
// GET request:
request({ url: 'https://example.com' }, (error, resp) => {
/* ... */
});
```
// Simple POST:
### POST request
```js
const request = require('request-libcurl');
const querystring = require('querystring');
// POST (Content-Type: application/x-www-form-urlencoded):
// by passing a String or formatted "Query String" to `form`
request({
method: 'POST',
uri: 'https://example.com',
form: JSON.stringify({ myForm: 'data' })
url: 'https://example.com',
form: querystring.stringify({ myForm: 'data' })
}, (error, resp) => {

@@ -203,8 +295,9 @@ /* ... */

// POST request with Authorization:
// POST with Authorization (Content-Type: application/x-www-form-urlencoded):
// by passing a String or formatted "Query String" to `form`
request({
method: 'POST',
uri: 'https://example.com',
url: 'https://example.com',
auth: 'username:passwd',
form: JSON.stringify({ myForm: 'data' })
form: querystring.stringify({ myForm: 'data' })
}, (error, resp) => {

@@ -214,17 +307,97 @@ /* ... */

// Override default settings:
request.defaultOptions.timeout = 7000;
request.defaultOptions.retries = 12;
request.defaultOptions.retryDelay = 5000;
request.defaultOptions.followRedirect = false;
// POST (Content-Type: application/json):
// by passing plain Object to `form`
request({
method: 'POST',
url: 'https://example.com',
form: { myForm: 'data' }
}, (error, resp) => {
/* ... */
});
```
// Override bad statuses codes (used to trigger retries)
request.defaultOptions.badStatuses = [300, 303, 305, 400, 407, 408, 409, 410];
### POST request with extra options
// Override function used to trigger retries based on status code
request.defaultOptions.isBadStatus = (statusCode, badStatuses = request.defaultOptions.badStatuses) => {
return badStatuses.includes(statusCode) || statusCode >= 500;
};
```js
const request = require('request-libcurl');
// POST with Authorization (Content-Type: application/json):
// by passing plain Object to `form`
request({
method: 'POST',
url: 'https://example.com',
auth: 'username:passwd',
form: { myForm: 'data' }
}, (error, resp) => {
/* ... */
});
// Custom POST (Content-Type: text/plain):
// by passing custom Headers
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 upload
```js
const fs = require('fs');
const request = require('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 successfully uploaded
}
});
});
```
### File upload (`multipart/form-data`)
In this example we are going to use [`HTTPPOST` libcurl option](https://curl.haxx.se/libcurl/c/CURLOPT_HTTPPOST.html) passing `[Object]` (*array of Objects* representing files, note: multiple files can be passed in a single request) via `curlOptions`
```js
const request = require('request-libcurl');
const fileLocation = '/full/absolute/path/to/a/file.ext';
request({
method: 'POST', // Can be used with PUT
url: 'https://example.com/upload.php',
retry: false,
curlOptions: {
HTTPPOST: [{
name: 'file.ext', // File's name
file: fileLocation, // Full absolute path to a file on FS
type: 'application/ext' // File's mime-type
} /*, {...} */]
}
}, (error) => {
if (error) {
throw error;
} else {
// File(s) successfully uploaded
}
});
```
## Running Tests

@@ -237,8 +410,17 @@

```shell
# TBD
# Install development NPM dependencies:
npm install --save-dev
# Install NPM dependencies:
npm install --save
# Run tests:
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 "timeouted responces"
# if a test looks stuck — give it another minute before interrupting it
```
## Support our open source contribution:
## Support our open source contribution
- [Become a patron](https://www.patreon.com/bePatron?u=20396046) — support my open source contributions with monthly donation
- Use [ostr.io](https://ostr.io) — [Monitoring](https://snmp-monitoring.com), [Analytics](https://ostr.io/info/web-analytics), [WebSec](https://domain-protection.info), [Web-CRON](https://web-cron.info) and [Pre-rendering](https://prerendering.com) for a website

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc