Bandwidth Throttle Stream
A Node.js 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
- Distributes the desired bandwidth evenly over each second
- Distributes the desired bandwidth evenly between all active requests
- Abortable requests ensure bandwidth is redistributed if a client aborts a request
Contents
Installation
Firstly, install the package using your package manager of choice.
npm install bandwidth-throttle-stream
You may then import the createBandwidthThrottleGroup()
factory function into your project.
import {createBandwidthThrottleGroup} from 'bandwidth-throttle-stream';
Usage
Creating a Group
We must firstly create a "bandwidth throttle group" which will be configured with a specific throughput in bytes (B) per second.
import {createBandwidthThrottleGroup} from 'bandwidth-throttle-stream';
const bandwidthThrottleGroup = createBandwidthThrottleGroup({
bytesPerSecond: 500000
});
Typically we would create a single group only for a server running a simulation, which all incoming network requests to be throttled are routed through. However, we could also create multiple groups if we wanted to run multiple simulations with different configurations on a single server.
Attaching Throttles
Once we've created a group, we can then attach individual pipeable "throttles" to it, as requests come into our server.
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.
const throttle = bandwidthThrottleGroup.createBandwidthThrottle();
someReadableStream
.pipe(throttle)
.pipe(someWritableStream);
Handling Output
In most cases however, we require more granular control of data output than simply piping to a writable stream (for example when throttling an HTTP request).
In these cases, we can use any of the Node.js stream events available such as data
and end
:
someReadableStream
.pipe(throttle)
.on('data', chunk => response.write(chunk)
.on('end', () => {
response.status(200);
response.end();
throttle.destroy();
});
Configuration Options
Each bandwidth throttle group accepts an optional object of configuration options:
const bandwidthThrottleGroup = createBandwidthThrottleGroup({
bytesPerSecond: 500000
resolutionHz: 20
});
The following options are available.
interface IConfig {
bytesPerSecond?: number;
ticksPerSecond?: number;
}
Dynamic Configuration
A group can be reconfigured at any point after creation via its .configure()
method, which accepts the same configuration interface as the createBandwidthThrottleGroup()
factory.
const bandwidthThrottleGroup = createBandwidthThrottleGroup();
bandwidthThrottleGroup.configure({
bytesPerSecond: 6000000
})
Aborting Requests
When a client aborts a requests, its important that we also abort the throttle, ensuring the group can re-balance available bandwidth correctly.
const throttle = bandwidthThrottleGroup.createBandwidthThrottle();
request.on('aborted', () => {
throttle.abort();
});
someReadableStream
.pipe(throttle)
.on('data', chunk => response.write(chunk)
.on('end', () => {
response.status(200);
response.end();
throttle.destroy();
});
Destroying Requests
To prevent memory leaks, individual throttles should be destroyed once all data for a request has been processed, and the request as ended.
This ensures the throttle instance is fully released from its parent group.
Each throttle instance exposes a .destroy()
method for this purpose:
throttle.destroy();