Simple HTTP request client with file download progress
npm install @xan105/requestAbout
=====
HTTP request library based around Node.js' HTTP(S) API interfaces:
- http/https
- ~~http2~~¹
- ~~undici/fetch (_included in Node.js 18_)¹~~
¹ Work in progress
Provides common features such as retry on error, following redirects, progress when downloading file, ...
This library isn't intented to compete nor replace the well known libraries such as got, axios, node-fetch, ...
This is merely educational and for informational purposes in order to learn how HTTP requests work under the hood.
This was originally created as request-zero at a time were the module request was the main choice and I didn't quite like it.
It had a ton of dependencies, didn't use promises and I needed something very simple.
Example
=======
Simplest call
``js`
import { request } from "@xan105/request";
const res = await request("https://www.google.com");
console.log(res.body);
JSON
`js
import { getJSON } from "@xan105/request";
const json = await getJSON("https://jsonplaceholder.typicode.com/todos/1");
console.log(json);
/*Output:
{ userId: 1, id: 1, title: 'delectus aut autem', completed: false }
*/
//Github API
const json = await getJSON("https://api.github.com/repos/user/repo/releases/latest",{
headers: {"Accept" : "application/vnd.github.v3+json"}
});
console.log(json);
/*Output:
{ url: '...', tag_name: '0.0.0', target_commitish: 'master', ... }
*/
`
Download file(s)
`js
import { download, downloadAll } from "@xan105/request";
//Callback example to output progress in the console
function printProgress(percent, speed, file){
process.stdout.clearLine();
process.stdout.cursorTo(0);
process.stdout.write(${percent}% @ ${speed} kb/s [${file}]);
}
//Simple download to disk (pipe to stream)
await download(
"http://ipv4.download.thinkbroadband.com/1GB.zip",
"D:/Downloads",
printProgress
);
//Download from github ... aws redirection ... content disposition ... but custom filename
const res = await download(
"https://github.com/user/repo/releases/download/0.0.0/Setup.exe",
"D:/Downloads/",
{ filename: "supersetup.exe" },
printProgress
);
console.log(res);
/*Output:
{ status: 200, message: 'OK', headers: {...}, path: 'D:\\Downloads\\supersetup.exe' }
*/
//Download a list of files one by one
await request.download.all([
"http://ipv4.download.thinkbroadband.com/5MB.zip",
"http://ipv4.download.thinkbroadband.com/10MB.zip",
"http://ipv4.download.thinkbroadband.com/20MB.zip",
"http://ipv4.download.thinkbroadband.com/50MB.zip"],
"D:\\Downloads", printProgress);
`
Download a torrent
`js`
import { download } from "@xan105/request/torrent";
download("https://webtorrent.io/torrents/sintel.torrent", "D:\\Downloads");
Misc
`js
import * as h1 from "@xan105/request";
//Head request
const res = await h1.head(http://ipv4.download.thinkbroadband.com/1GB.zip);
console.log(res);
/*Output:
{ status: 200, message: 'OK', headers: {...} }
*/
//Manually specify retry on error and redirection to follow
await request("https://steamdb.info/app/220/", { maxRetry: 2, maxRedirect: 2 });
//Upload a single file multipart/form-data
const res = await h1.upload(
"http://127.0.0.1/upload/test/",
"Hello world",
{name: "file", filename: "hello world.txt"}
);
console.log(res);
/*Output:
{ status: 200, message: 'OK', headers: {...}, body: 'ok' }
*/
`
Installation
============
``
npm install @xan105/request
- webtorrent
Downloading torrent
``
npm i webtorrent
- xml2js
XML parser
``
npm i xml2js
API
===
⚠️ This module is only available as an ECMAScript module (ESM) starting with version 1.0.0.
Previous version(s) are CommonJS (CJS) with an ESM wrapper.
💡 The underlying API used is determined by which namespace you import.
By default this is the http/https (h1) API.
Torrent related are under the torrent namespace.
`js
//Default
import * as h1 from '@xan105/request';
//http/https (h1)
import * as h1 from '@xan105/request/h1';
//http2 (h2)¹
import * as h2 from '@xan105/request/h2';
//Fetch¹
import * as fetch from '@xan105/request/fetch';
//Torrent
import * as torrent from "@xan105/request/torrent";
`
¹ Work in progress (unavailable at the moment)
This is the core request function every other functions are helper based on this one (_except download, downloadAll and torrent_).
The response object tries to be similar whether the request failed or succeeded.
`ts`
{
code: string, //HTTP or Node error code
message: string, //HTTP or Node error message (if any)
trace: string[], //URL(s) of the request (redirection)
domain: string, //url domain
sent: object, //Header sent
address?: string, //IP address
family?: string, //IPv4 or IPv6
protocol?: string, //HTTP protocol (h1, h2, ...)
security?: string, //TLS (HTTPS)
port: number, //Network port
headers?: object, //Response header
body?: string //Response body
}
💡 In a dual stack network, IPv4 isn't prefered over IPv6 unlike Node's default behavior (_Node < 17_ ).
💡 When making a HEAD request:
- The promise always resolves no matter the HTTP response code.
- Doesn't follow redirection by design.
If you need to follow the redirection you can use the headers location from the response and make a new HEAD request.
#### ⚙️ Options
| option | type | default | description |
| ----------- | ----------- | ---------------------------------- | ---------------------------------------------------------------------------------- |
| method | string | GET | HTTP method: get, post, head, etc |
| encoding | string | utf8 | Response encoding |
| timeout | number | 3000 (ms) | Time before aborting request |
| maxRedirect | number | 3 | How many redirections to follow before aborting.
Use 0 to not follow redirects |
| maxRetry | number | 0 | How many retries on error before aborting.
Use 0 to not retry at all |
| retryDelay | number | 200 (ms) | How long to wait before a retry.
Use 0 to instantly retry |
| headers | object | -> Chrome UA and UA Hint if https | Headers of your request |
| signal | AbortSignal | none | Abort signal |
method. Since request() default to 'GET' you could just use request() directly. This is here for completeness.$3
Force the HEAD method.$3
Parse the response body as a JSON string and return the result.
Force method to GET and the header Accept to "application/json".- alias:
getJson()$3
Send given object payload as a JSON encoded string.
Parse the response body as a JSON string and return the result.
Force method to
POST and the headers Accept and Content-Type to "application/json".$3
⚠️ Requires the xml2js module.
Parse the response body as a XML string and return the result.
Force method to
GET and the header Accept to "application/xml".- alias:
getXml()$3
Force method to POST and write/push given payload.
NB: On HTTP 301, 302, 303 redirection the method will be changed to GET$3
Force method to POST and write/push a multipart/form-data payload.
You can use option {fieldname: string, filename: string} to specify the form field name and the file name.
If you don't they will default respectively to 'file' and Date.now().
$3
Download file to
destDir.The response object is like
request() minus body and with the addition of a file object:
`ts
{
name: string, //filename
path: string, //relative
fullPath: string //absolute
}
`
This is useful for promise chaining to example unzip an archive, etc.💡 Progress gives you the following stats: percent, speed, file.
callbackProgress(percent: number, speed: number, file: string)#### ⚙️ Options
| option | type | default | description |
| ----------- | ----------- | ---------------------------------- | ---------------------------------------------------------------------------------- |
| timeout | number | 3000 (ms) | Time before aborting request |
| maxRedirect | number | 3 | How many redirections to follow before aborting.
Use 0 to not follow redirects |
| maxRetry | number | 3 | How many retries on error before aborting.
Use 0 to not retry at all |
| retryDelay | number | 1000 (ms) | How long to wait before a retry.
Use 0 to instantly retry |
| headers | object | -> Chrome UA and UA Hint if https | Headers of your request |
| signal | AbortSignal | none | Abort signal |
| filename | string | null | Use this if you want to specify the filename (force rename) |
| hash | object | null | Verify checksum of downloaded file² |
²Checksum option
`ts
{
algo: string, //A Node.js supported crypto algo. eg: "sha1"
sum: string //Checksum
}
`
On error or mismatch it will trigger error/retry.$3
Download all the files in the list one-by-one to destDir.
If
destDir is an array, files[i] will be written to destDir[i] in a 1:1 relation.
In the same fashion you can force the filename of the files with option {filename: [..,..,..]}.
And again same thing for checksum: {hash: [{algo: ..., sum: ...},..,..]}.
Returns an array of
download() response object.Torrent
$3
⚠️ Requires the webtorrent module.
Download files from a torrent url, torrent file, torrent magnet to
destDir.
💡 Progress gives you the following stats: percent, speed.
callbackProgress(percent: number, speed: number)💡 Torrent can be resumed.
Returns an objectect with torrent download location, torrent name, and for every files of the torrent its name, relative path and path.
`ts
{
path: string, //absolute
name: string, //torrent name
file: [
{
name: string, //filename
path: string, //relative
fullPath: string //absolute
}
]
}
``#### ⚙️ Options
| option | type | default | description |
| ------------- | ----------| ------------ | ------------------------------------------ |
| timeout | number | 10 (sec) | Time to wait for peers before aborting |
| exclusion | string[] | none | Exclude files inside the torrent |
| downloadLimit | number | -1 (none) | Download speed limit |
| uploadLimit | number | 100 (kb/s) | Upload speed limit |