UNPKG

@xan105/request

Version:

Simple HTTP request client with file download progress

346 lines (259 loc) 12.9 kB
About ===== 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, ...<br /> 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](https://www.npmjs.com/package/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 ``` ## Optional packages - [webtorrent](https://www.npmjs.com/package/webtorrent)<br /> Downloading torrent<br /> ``` npm i webtorrent ``` - [xml2js](https://www.npmjs.com/package/xml2js)<br /> XML parser<br /> ``` npm i xml2js ``` API === ⚠️ This module is only available as an ECMAScript module (ESM) starting with version 1.0.0.<br /> Previous version(s) are CommonJS (CJS) with an ESM wrapper. 💡 The underlying API used is determined by which namespace you import.<br /> By default this is the http/https (h1) API.<br /> 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) ## Named export ### `request(href: string, payload?: any, option?: object): Promise<object>` 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**.<br/>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.<br/>Use 0 to not follow redirects | | maxRetry | number | 0 | How many retries on error before aborting.<br/>Use 0 to not retry at all | | retryDelay | number | 200 (ms) | How long to wait before a retry.<br/>Use 0 to instantly retry | | headers | object | -> Chrome UA and UA Hint if https | Headers of your request | | signal | AbortSignal | none | Abort signal | ### `get(url: string, option?: object): Promise<object>` Force the `GET` method. Since `request()` default to 'GET' you could just use `request()` directly. This is here for completeness. ### `head(url: string, option?: object): Promise<object>` Force the `HEAD` method. ### `getJSON(url: string, option?: object): Promise<object>` Parse the response body as a JSON string and return the result.<br/> Force method to `GET` and the header `Accept` to `"application/json"`. - alias: `getJson()` ### `postJSON(url: string, obj: object, option?: object): Promise<object>` Send given object payload as a JSON encoded string.<br/> Parse the response body as a JSON string and return the result.<br/> Force method to `POST` and the headers `Accept` and `Content-Type` to `"application/json"`. ### `getXML(url: string, option?: object): Promise<object>` ⚠️ Requires the [xml2js](https://www.npmjs.com/package/xml2js) module. Parse the response body as a XML string and return the result.<br/> Force method to `GET` and the header `Accept` to `"application/xml"`. - alias: `getXml()` ### `post(url: string, payload: unknown, option?: object): Promise<object>` Force method to `POST` and write/push given payload.<br/> NB: On HTTP 301, 302, 303 redirection the method will be [changed to GET](https://developer.mozilla.org/en-US/docs/Web/HTTP/Redirections) ### `upload(url: string, payload: unknown, option?: object): Promise<object>` Force method to `POST` and write/push a multipart/form-data payload.<br/> You can use option `{fieldname: string, filename: string}` to specify the form field name and the file name.<br/> If you don't they will default respectively to 'file' and Date.now().<br/> ### `download(href: string, destDir: string, option?: object, callbackProgress?: fn): Promise<object>` 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.<br/> `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.<br/>Use 0 to not follow redirects | | maxRetry | number | 3 | How many retries on error before aborting.<br/>Use 0 to not retry at all | | retryDelay | number | 1000 (ms) | How long to wait before a retry.<br/>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. ### `downloadAll(href: string[], destDir: string|string[], option?: object, callbackProgress?: fn): Promise<object>` 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.<br/> In the same fashion you can force the filename of the files with option `{filename: [..,..,..]}`.<br/> And again same thing for checksum: `{hash: [{algo: ..., sum: ...},..,..]}`.<br/> Returns an array of `download()` response object. ## Torrent ### `download(torrent: string, dest: string, option?: object, callbackProgress?: fn): Promise<object>` ⚠️ Requires the [webtorrent](https://www.npmjs.com/package/webtorrent) module. Download files from a torrent url, torrent file, torrent magnet to `destDir`.<br/> 💡 Progress gives you the following stats: percent, speed.<br/> `callbackProgress(percent: number, speed: number)` 💡 Torrent can be resumed.<br/> Returns an objectect with torrent download location, torrent name, and for every files of the torrent its name, relative path and path.<br/> ```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 |