bandwidth-throttle-stream
Advanced tools
Comparing version 0.1.1 to 0.2.0
{ | ||
"name": "bandwidth-throttle-stream", | ||
"version": "0.1.1", | ||
"description": "A Node.js transform stream for throttling bandwidth", | ||
"version": "0.2.0", | ||
"description": "A Node.js and Deno transform stream for throttling bandwidth", | ||
"author": "KunkaLabs Limited", | ||
"private": false, | ||
"license": "Apache-2.0", | ||
"main": "./dist/index.js", | ||
"types": "./dist/index.d.ts", | ||
"main": "./dist/node/index.js", | ||
"types": "./dist/node/index.d.ts", | ||
"module": "./dist/deno/mod.js", | ||
"files": [ | ||
"dist" | ||
"dist/node", | ||
"dist/deno" | ||
], | ||
@@ -18,6 +20,9 @@ "repository": { | ||
"scripts": { | ||
"test": "TS_NODE_PROJECT=./tsconfig.json mocha \"**/*.test.ts\" --config ./config/mocha/.mocharc.json", | ||
"test": "TS_NODE_PROJECT=./config/typescript/tsconfig.base.json mocha \"**/*.test.ts\" --config ./config/mocha/.mocharc.json", | ||
"test:watch": "npm run test -- --watch", | ||
"test:cover": "nyc npm run test", | ||
"build": "rm -rf dist && tsc -p ./config/typescript/tsconfig.build.json", | ||
"clean:dist": "rm -rf dist", | ||
"build:node": "ttsc -p ./config/typescript/tsconfig.build.node.json", | ||
"build:deno": "ttsc -p ./config/typescript/tsconfig.build.deno.json", | ||
"build": "npm run clean:dist npm run build:node && npm run build:deno", | ||
"lint": "tslint --project tsconfig.json -c ./config/tslint/tslint.json \"./src/**/*.ts\"" | ||
@@ -49,3 +54,4 @@ }, | ||
"@types/node": "12.6.8", | ||
"@types/sinon": "7.0.13", | ||
"@types/sinon": "9.0.4", | ||
"@zerollup/ts-transform-paths": "^1.7.17", | ||
"chai": "4.2.0", | ||
@@ -57,9 +63,11 @@ "husky": "3.0.2", | ||
"prettier": "1.19.1", | ||
"sinon": "7.4.2", | ||
"sinon": "9.0.2", | ||
"ts-node": "8.3.0", | ||
"tsc-watch": "2.4.0", | ||
"tsconfig-paths": "^3.9.0", | ||
"tslint": "5.20.1", | ||
"tslint-eslint-rules": "5.4.0", | ||
"ttypescript": "^1.5.10", | ||
"typescript": "3.7.4" | ||
} | ||
} |
129
README.md
@@ -5,6 +5,7 @@ ![CI](https://github.com/patrickkunka/bandwidth-throttle-stream/workflows/CI/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/patrickkunka/bandwidth-throttle-stream/badge.svg?branch=master)](https://coveralls.io/github/patrickkunka/bandwidth-throttle-stream?branch=master) | ||
A Node.js [transform stream](https://nodejs.org/api/stream.html) for throttling bandwidth which distributes available bandwidth evenly between all requests in a "group", accurately simulating the effect of network conditions on simultaneous overlapping requests. | ||
A [Node.js](https://nodejs.org/en/) and [Deno](https://deno.land/) transform stream for throttling bandwidth which distributes available bandwidth evenly between all requests in a "group", accurately simulating the effect of network conditions on simultaneous overlapping requests. | ||
#### Features | ||
- Idiomatic pipeable Node.js transform stream API | ||
- Idiomatic pipeable [Transform](https://nodejs.org/api/stream.html) API for use in Node.js | ||
- Idiomatic pipeable [TransformStream](https://developer.mozilla.org/en-US/docs/Web/API/TransformStream) API for use in Deno | ||
- Distributes the desired bandwidth evenly over each second | ||
@@ -15,3 +16,4 @@ - Distributes the desired bandwidth evenly between all active requests | ||
#### Contents | ||
- [Installation](#installation) | ||
- [Node.js Installation](#nodejs-installation) | ||
- [Deno Installation](#deno-installation) | ||
- [Usage](#usage) | ||
@@ -23,6 +25,5 @@ - [Creating a Group](#creating-a-group) | ||
- [Dynamic Configuration](#dynamic-configuration) | ||
- [Aborting Requests](#aborting-requests) | ||
- [Destroying Requests](#destroying-requests) | ||
- [Aborted Requests](#aborted-requests) | ||
## Installation | ||
## Node.js Installation | ||
@@ -41,2 +42,10 @@ Firstly, install the package using your package manager of choice. | ||
## Deno Installation | ||
In Deno, all libraries are imported from URLs as ES modules. Versioned releases of `bandwidth-throttle-stream` are available from TBC: | ||
```js | ||
import {createBandwidthThrottleGroup} from 'https://path/to/cdn/bandwidth-throttle-stream@0.2.0/mod.ts' | ||
``` | ||
## Usage | ||
@@ -46,7 +55,5 @@ | ||
We must firstly create a "bandwidth throttle group" which will be configured with a specific throughput in bytes (B) per second. | ||
Using the imported `createBandwidthThrottleGroup` factory function, we must firstly create a "bandwidth throttle group" which will be configured with a specific throughput in bytes (B) per second. | ||
```js | ||
import {createBandwidthThrottleGroup} from 'bandwidth-throttle-stream'; | ||
// Create a group with a configured available bandwidth in bytes (B) per second. | ||
@@ -65,11 +72,12 @@ | ||
The most simple integration would be to insert the throttle (via `.pipe`) between a readable stream (e.g file system readout, server-side HTTP response), and the response stream of the incoming client request to be throttled. | ||
The most simple integration would be to insert the throttle (via `.pipe`, or `.pipeThrough`) between a readable stream (e.g file system readout, server-side HTTP response), and the response stream of the incoming client request to be throttled. | ||
##### Node.js example: Piping between readable and writable streams | ||
```js | ||
// Attach a throttle to a group (e.g. in response to an incoming request) | ||
const throttle = bandwidthThrottleGroup.createBandwidthThrottle(); | ||
const throttle = bandwidthThrottleGroup.createBandwidthThrottle(contentLength); | ||
// Throttle the response by piping a readable stream to a writable | ||
// stream via the throttle | ||
// Throttle the response by piping a `stream.Readable` to a `stream.Writable` | ||
// via the throttle | ||
@@ -82,2 +90,31 @@ someReadableStream | ||
#### Deno example: Piping between a readable stream and a reader: | ||
```ts | ||
// Attach a throttle to a group (e.g. in response to an incoming request) | ||
const throttle = bandwidthThrottleGroup.createBandwidthThrottle(contentLength); | ||
// Throttle the response by piping a `ReadableStream` to a `ReadableStreamDefaultReader`: | ||
someReadableStream | ||
.pipeThrough(throttle) | ||
.getReader() | ||
``` | ||
Note that a number value for `contentLength` (in "bytes") must be passed when creating an individual throttle. This should be the total size of data for the request being passed through the throttle, and is used to allocate memory upfront in a single `Uint8Array` typed array, thus preventing expensive GC calls as backpressure builds up. When throttling HTTP requests, `contentLength` can be obtained from the `'content-length'` header, once the headers of the request have arrived: | ||
#### Node.js (Express) example: Obtaining `content-length` from `req` headers: | ||
```js | ||
const contentLength = parseInt(req.get('content-length')) | ||
``` | ||
#### Deno example: Obtaining `content-length` from `fetch` headers: | ||
```ts | ||
const { body, headers } = await fetch(destination); | ||
const contentLength = parseInt(headers.get("content-length")); | ||
``` | ||
#### Handling Output | ||
@@ -89,4 +126,5 @@ | ||
##### Node.js example: Hooking into the `end` event of a writable stream | ||
```js | ||
someReadableStream | ||
request | ||
.pipe(throttle) | ||
@@ -99,7 +137,23 @@ .on('data', chunk => response.write(chunk) | ||
response.end(); | ||
// Destroy the throttle to release it from the group | ||
throttle.destroy(); | ||
}); | ||
``` | ||
##### Deno example: responding to a request with a reader and a status code | ||
```ts | ||
import {readerToDenoReader} from 'TBC'; | ||
... | ||
await request.respond({ | ||
status: 200 | ||
body: readerToDenoReader(reader, contentLength), | ||
}); | ||
// request sent successfully | ||
``` | ||
Note that in the Deno example, a reader may be passed directly to `request.respond()` allowing real-time streaming of the throttled output. However, the Deno [`std`](https://deno.land/std/http/server.ts) server expects a `Deno.Reader` as a `body` (rather than the standard `ReadableStreamDefaultReader`), meaning that conversion is needed between the two. | ||
The `readerToDenoReader` util is exposed for this purpose, and must be provided with both a reference to `ReadableStreamDefaultReader` (`reader`), and the `contentLength` of the request. | ||
## Configuration Options | ||
@@ -112,3 +166,3 @@ | ||
bytesPerSecond: 500000 // 500KB/s, | ||
resolutionHz: 20 // aim to write output 20x per second | ||
ticksPerSecond: 20 // aim to write output 20x per second | ||
}); | ||
@@ -163,8 +217,10 @@ ``` | ||
## Aborting Requests | ||
## Aborted Requests | ||
When a client aborts a requests, its important that we also abort the throttle, ensuring the group can re-balance available bandwidth correctly. | ||
When a client aborts a requests, its important that we also abort the throttle, ensuring the group can re-balance available bandwidth correctly, and backpressure buffer memory is released. | ||
##### Node.js example: Handling aborted requests | ||
```js | ||
const throttle = bandwidthThrottleGroup.createBandwidthThrottle(); | ||
const throttle = bandwidthThrottleGroup.createBandwidthThrottle(contentLength); | ||
@@ -177,25 +233,26 @@ request.on('aborted', () => { | ||
someReadableStream | ||
request | ||
.pipe(throttle) | ||
.on('data', chunk => response.write(chunk) | ||
.on('end', () => { | ||
// Set the response status of the HTTP request to 200 | ||
response.status(200); | ||
// End the request | ||
response.end(); | ||
// Destroy the throttle to release it from the group | ||
throttle.destroy(); | ||
}); | ||
.pipe(response); | ||
``` | ||
## Destroying Requests | ||
##### Deno example: Handling aborted requests | ||
To prevent memory leaks, individual throttles should be destroyed once all data for a request has been processed, and the request as ended. | ||
```ts | ||
const throttle = bandwidthThrottleGroup.createBandwidthThrottle(contentLength); | ||
This ensures the throttle instance is fully released from its parent group. | ||
request | ||
.pipeThrough(throttle) | ||
.getReader() | ||
Each throttle instance exposes a `.destroy()` method for this purpose: | ||
try { | ||
await request.respond({ | ||
status: 200 | ||
body: readerToDenoReader(reader, contentLength), | ||
}); | ||
} catch(err) { | ||
// request aborted or failed | ||
```js | ||
throttle.destroy(); | ||
throttle.abort(); | ||
} | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
59232
57
829
246
19
1