toxy
Advanced tools
Comparing version 0.1.2 to 0.1.3
@@ -16,7 +16,17 @@ const toxy = require('..') | ||
})) | ||
.withRule(rules.method('GET')) | ||
.withRule(rules.probability(50)) | ||
.rule(rules.method('GET')) | ||
// Get poison | ||
var poison = proxy.getPoison('inject') | ||
poison.isEnabled() // -> true | ||
// Get rule in the poison scope (nested) | ||
var rule = poison.getRule('probability') // -> Directive | ||
rule.isEnabled() // -> true | ||
rule.disable() | ||
poison.isRuleEnabled('probability') // -> false | ||
// Disable poison | ||
proxy.disable('inject') | ||
poison.disable() // Or: proxy.disable('inject') | ||
proxy.isEnabled('inject') // -> false | ||
@@ -29,7 +39,7 @@ | ||
// Get registered poisons | ||
proxy.poisons() // -> [ inject ] | ||
proxy.getPoisons() // -> [ inject ] | ||
// Remove the poison | ||
proxy.remove('inject') | ||
proxy.poisons() // -> [] | ||
proxy.getPoisons() // -> [] | ||
@@ -39,4 +49,8 @@ // Flush all poisons (not necessary, though) | ||
// Get rule | ||
var rule = proxy.getRule('method') | ||
rule.isEnabled() // -> true | ||
// Disable rule | ||
proxy.disableRule('method') | ||
poison.disable() // Or: proxy.disableRule('method') | ||
proxy.isRuleEnabled('method') // -> false | ||
@@ -49,10 +63,10 @@ | ||
// Get registered rules | ||
proxy.rules() // -> [ method, probability ] | ||
proxy.getRules() // -> [ method, probability ] | ||
// Remove the rule | ||
proxy.removeRule('method') | ||
proxy.rules() // -> [ probability ] | ||
proxy.getRules() // -> [ probability ] | ||
// Flush all rules (aka remove all) | ||
proxy.flushFlush() | ||
proxy.flush() | ||
@@ -59,0 +73,0 @@ proxy.all('/*') |
@@ -1,39 +0,44 @@ | ||
const toxy = require('..') | ||
var toxy = require('..') | ||
var poisons = toxy.poisons | ||
var rules = toxy.rules | ||
const proxy = toxy() | ||
const rules = proxy.rules | ||
const poisons = proxy.poisons | ||
var proxy = toxy() | ||
proxy | ||
.forward('http://httpbin.org') | ||
.rule(rules.probability(50)) | ||
.poison(poisons.slowOpen({ delay: 500 })) | ||
var route = proxy.get('/*') | ||
// Register global poisons and rules | ||
proxy | ||
.poison(poisons.latency({ jitter: 100 })) | ||
.rule(rules.probability(25)) | ||
route | ||
.poison(poisons.latency({ jitter: 1000 })) | ||
// Register multiple routes | ||
proxy | ||
.get('/download/*') | ||
.poison(poisons.bandwidth({ bps: 1024 })) | ||
.withRule(rules.headers({'Authorization': /^Bearer (.*)$/i })) | ||
route | ||
.poison(poisons.inject({ code: 502, body: 'Error!', headers: { 'X-Toxy-Poison': 'error' } })) | ||
.withRule(rules.probability(20)) | ||
proxy | ||
.get('/image/*') | ||
.poison(poisons.bandwidth({ bps: 1024 })) | ||
route | ||
proxy | ||
.all('/ip') | ||
.poison(poisons.rateLimit({ limit: 1, threshold: 1000 })) | ||
.withRule(rules.method(['POST', 'PUT', 'DELETE'])) | ||
// And use a different more permissive poison for GET requests | ||
.poison(poisons.rateLimit({ limit: 10, threshold: 1000 })) | ||
.withRule(rules.method('GET')) | ||
// Handle the rest of the traffic | ||
proxy | ||
.all('/*') | ||
.poison(poisons.slowClose({ delay: 1000 })) | ||
.withRule(rules.probability(20)) | ||
.poison(poisons.slowRead({ bps: 128 })) | ||
.withRule(rules.probability(50)) | ||
route | ||
.poison(poisons.rateLimit({ limit: 2, threshold: 5000 })) | ||
.withRule(rules.probability(20)) | ||
route | ||
.poison(poisons.slowRead({ bps: 100 })) | ||
.withRule(rules.probability(35)) | ||
route | ||
.poison(poisons.abort()) | ||
.poisonRule(rules.probability(5)) // does the same as withRule() | ||
.poisonRule(rules.method('GET')) | ||
proxy.listen(3000) | ||
console.log('Server listening on port:', 3000) | ||
console.log('Test it opening:') | ||
console.log('http://localhost:3000/ip') | ||
console.log('http://localhost:3000/image/jpeg') |
@@ -28,3 +28,3 @@ const Toxy = require('./lib/toxy') | ||
toxy.VERSION = require('./package.json').version | ||
toxy.VERSION = require('./package.json').version | ||
@@ -38,3 +38,3 @@ /** | ||
poisons.forEach(function (poison) { | ||
Toxy.prototype.poisons[poison.name] = function () { | ||
toxy.poisons[poison.name] = function () { | ||
return poison.apply(null, arguments) | ||
@@ -51,5 +51,5 @@ } | ||
rules.forEach(function (rule) { | ||
Toxy.prototype.rules[rule.name] = function () { | ||
toxy.rules[rule.name] = function () { | ||
return rule.apply(null, arguments) | ||
} | ||
}) |
@@ -35,7 +35,16 @@ module.exports = Base | ||
Base.prototype._getDirective = function (mw, name) { | ||
var item = this._searchInStack(mw, name) | ||
if (item) { | ||
return item.$of | ||
} | ||
return null | ||
} | ||
Base.prototype._searchInStack = function (mw, name) { | ||
var stack = mw.stack | ||
for (var i = 0, l = stack.length; i < l; i += 1) { | ||
if (stack[i].$name === name || stack[i].$of === name) { | ||
return stack[i] | ||
var node = stack[i] | ||
if (node.$name === name || node.$of === name) { | ||
return node | ||
} | ||
@@ -42,0 +51,0 @@ } |
@@ -22,3 +22,3 @@ exports.isRegExp = function isRegExp(o) { | ||
exports.sliceBuffer = function sliceBuffer(size, buffer, encoding, cache) { | ||
exports.sliceBuffer = function sliceBuffer(size, buffer, encoding, target) { | ||
if (!buffer) return | ||
@@ -30,3 +30,3 @@ | ||
for (var i = 0; i < length; i += size) { | ||
cache.push({ | ||
target.push({ | ||
buffer: buffer.slice(i, i + size), | ||
@@ -33,0 +33,0 @@ encoding: encoding |
@@ -37,3 +37,2 @@ const Rule = require('./rule') | ||
var self = this | ||
var directive = this.directive | ||
@@ -47,3 +46,3 @@ function handler(req, res, next) { | ||
if (filter === true) return next() | ||
directive(req, res, next) | ||
self.directive(req, res, next) | ||
} | ||
@@ -50,0 +49,0 @@ } |
module.exports = function abort(opts) { | ||
opts = opts || {} | ||
var delay = +opts.delay || 1 | ||
return function abort(req, res, next) { | ||
setTimeout(function () { | ||
try { destroy() } catch (e) {} | ||
}, opts.delay) | ||
setTimeout(destroy, delay) | ||
function destroy() { | ||
req.destroy(opts.error) | ||
try { | ||
req.destroy(opts.error) | ||
} catch (e) { | ||
next(e) | ||
} | ||
} | ||
} | ||
} |
const throttler = require('./throttle') | ||
module.exports = function bandwidth(opts) { | ||
if (typeof opts === 'number') { | ||
opts = { bps: opts } | ||
} | ||
opts = opts || {} | ||
opts.bps = (+opts.bps || 1024) | ||
opts.bps = +opts.bps || 1024 | ||
opts.threshold = +opts.threshold || 1000 | ||
@@ -7,0 +11,0 @@ |
module.exports = [ | ||
require('./inject'), | ||
require('./abort'), | ||
require('./latency'), | ||
require('./throttle'), | ||
require('./timeout'), | ||
require('./rate-limit'), | ||
require('./slow-close'), | ||
require('./slow-open'), | ||
require('./slow-read'), | ||
require('./bandwidth') | ||
] | ||
'inject', | ||
'abort', | ||
'latency', | ||
'throttle', | ||
'timeout', | ||
'rate-limit', | ||
'slow-close', | ||
'slow-open', | ||
'slow-read', | ||
'bandwidth' | ||
].map(function (module) { | ||
return require('./' + module) | ||
}) |
module.exports = function rateLimit(opts) { | ||
opts = opts || {} | ||
var limit = +opts.limit || 10 | ||
var code = +opts.code || 429 | ||
var threshold = +opts.threshold || 1000 | ||
var message = opts.message || 'Too many requests' | ||
var code = +opts.code || 429 | ||
var current = Date.now() | ||
var remaining = opts.limit | ||
var remaining = limit | ||
@@ -33,3 +33,2 @@ return function rateLimit(req, res, next) { | ||
} | ||
return remaining -= 1 | ||
@@ -36,0 +35,0 @@ } |
const rocky = require('rocky') | ||
const RockyBase = rocky.Base | ||
const Rule = require('./rule') | ||
@@ -10,2 +9,4 @@ const rules = require('./rules') | ||
var RockyBase = rocky.Base | ||
RockyBase.prototype.rule = | ||
@@ -32,3 +33,3 @@ RockyBase.prototype.filter = Directive.prototype.rule | ||
RockyBase.prototype.remove = function (poison) { | ||
return this._remove(this_poisons, poison) | ||
return this._remove(this._poisons, poison) | ||
} | ||
@@ -45,3 +46,6 @@ | ||
RockyBase.prototype.poisons = | ||
RockyBase.prototype.getPoison = function (poison) { | ||
return this._getDirective(this._poisons, poison) | ||
} | ||
RockyBase.prototype.getPoisons = function () { | ||
@@ -48,0 +52,0 @@ return this._getAll(this._poisons) |
@@ -26,7 +26,10 @@ const midware = require('midware') | ||
Rule.prototype.isRuleEnabled = function (poison) { | ||
return this._callMethod(this._rules, 'isEnabled', poison) | ||
Rule.prototype.isRuleEnabled = function (rule) { | ||
return this._callMethod(this._rules, 'isEnabled', rule) | ||
} | ||
Rule.prototype.rules = | ||
Rule.prototype.getRule = function (rule) { | ||
return this._getDirective(this._rules, rule) | ||
} | ||
Rule.prototype.getRules = function () { | ||
@@ -33,0 +36,0 @@ return this._getAll(this._rules) |
module.exports = [ | ||
require('./body'), | ||
require('./method'), | ||
require('./probability'), | ||
require('./headers'), | ||
require('./content-type'), | ||
] | ||
'body', | ||
'method', | ||
'headers', | ||
'probability', | ||
'content-type', | ||
].map(function (module) { | ||
return require('./' + module) | ||
}) |
module.exports = function probability(num) { | ||
var percent = +Math.min(+num, 100) || 50 | ||
var percent = +Math.min(num, 100) || 50 | ||
@@ -7,8 +7,7 @@ return function probability(req, res, next) { | ||
var rand = Math.round(Math.random() * 10) | ||
var perc = Math.round(percent * 0.1) | ||
var rand = Math.round(Math.random() * 100) | ||
var notMatches = rand > percent | ||
var notMatches = rand > perc | ||
next(null, notMatches) | ||
} | ||
} |
@@ -10,8 +10,3 @@ const midware = require('midware') | ||
this._poisons = midware() | ||
this._setup() | ||
} | ||
Toxy.prototype = Object.create(Proxy.prototype) | ||
Toxy.prototype._setup = function () { | ||
wrapRouteConstructor(this) | ||
@@ -21,10 +16,17 @@ setupMiddleware(this) | ||
Toxy.prototype = Object.create(Proxy.prototype) | ||
function wrapRouteConstructor(self) { | ||
var _route = self.route | ||
self.route = function (method, path) { | ||
self.route = function () { | ||
var route = _route.apply(self, arguments) | ||
// Create toxy route-evel specific middleware | ||
route._rules = midware() | ||
route._poisons = midware() | ||
// Setup middleware and final route handler | ||
setupMiddleware(route) | ||
reDispatchRoute(route) | ||
return route | ||
@@ -31,0 +33,0 @@ } |
{ | ||
"name": "toxy", | ||
"version": "0.1.2", | ||
"version": "0.1.3", | ||
"description": "Hackable HTTP proxy to simulate server failure scenarios and unexpected conditions", | ||
@@ -5,0 +5,0 @@ "repository": "h2non/toxy", |
229
README.md
@@ -5,10 +5,9 @@ # toxy [![Build Status](https://api.travis-ci.org/h2non/toxy.svg?branch=master&style=flat)](https://travis-ci.org/h2non/toxy) [![Code Climate](https://codeclimate.com/github/h2non/toxy/badges/gpa.svg)](https://codeclimate.com/github/h2non/toxy) [![NPM](https://img.shields.io/npm/v/toxy.svg)](https://www.npmjs.org/package/toxy) ![Stability](http://img.shields.io/badge/stability-beta-orange.svg?style=flat) | ||
**toxy** is a **hackable HTTP proxy** to **simulate** **failure scenarios** and **unexpected conditions**. | ||
**toxy** is a **hackable HTTP proxy** to **simulate** server **failure scenarios** and **unexpected network conditions**, built for [node.js](http://nodejs.org)/[io.js](https://iojs.org). | ||
It was mainly designed for fuzz/evil testing purposes, becoming particulary useful to cover fault tolerant and resiliency capabilities of a system, tipically in service-oriented distributed architectures, where `toxy` may act as intermediate proxy among services. | ||
It was mainly designed for fuzzing/evil testing purposes, becoming particulary useful to cover fault tolerance and resiliency capabilities of a system, especially in [service-oriented](http://microservices.io/) architectures, where toxy may act as intermediate proxy among services. | ||
toxy allows you to plug in [poisons](#poisons), optionally filtered by [rules](#rules), which basically can intercept and alter the HTTP flow as you want, performing multiple evil actions in the middle of that process, such as limiting the bandwidth, delaying TCP packets, injecting network jitter latency or replying with a custom error or status code. | ||
toxy allows you to plug in [poisons](#poisons), optionally filtered by [rules](#rules), which basically can intercept and alter the HTTP flow as you need, performing multiple evil actions in the middle of that process, such as limiting the bandwidth, delaying TCP packets, injecting network jitter latency or replying with a custom error or status code. | ||
Runs in [node.js](http://nodejs.org)/[io.js](https://iojs.org). Compatible with [connect](https://github.com/senchalabs/connect)/[express](http://expressjs.com). | ||
Built on top of [rocky](https://github.com/h2non/rocky), a full-featured, middleware-oriented HTTP/S proxy. | ||
toxy is compatible with [connect](https://github.com/senchalabs/connect)/[express](http://expressjs.com), and it was built on top of [rocky](https://github.com/h2non/rocky), a full-featured, middleware-oriented HTTP proxy. | ||
@@ -53,3 +52,3 @@ Requires node.js +0.12 or io.js +1.6 | ||
- Full-featured HTTP/S proxy (backed by [http-proxy](https://github.com/nodejistu/node-http-proxy)) | ||
- Full-featured HTTP/S proxy (backed by [rocky](https://github.com/h2non/rocky) and [http-proxy](https://github.com/nodejitsu/node-http-proxy)) | ||
- Hackable and elegant programmatic API (inspired on connect/express) | ||
@@ -64,3 +63,3 @@ - Featured built-in router with nested configuration | ||
- Built-in balancer and traffic intercept via middleware | ||
- Inherits the API and features from [rocky](https://github.com/h2non/rocky) | ||
- Inherits API and features from [rocky](https://github.com/h2non/rocky) | ||
- Compatible with connect/express (and most of their middleware) | ||
@@ -73,13 +72,13 @@ - Runs as standalone HTTP proxy | ||
There're some other similar solutions to `toxy` in the market, but most of them don't provide a proper programmatic control and are not easy to hack, configure and/or extend. Additionally, most of the those solutions are based only on the TCP stack only instead of providing more features to the scope of HTTP applicacion level protocol, like toxy does. | ||
There're some other similar solutions to `toxy` in the market, but most of them don't provide a proper programmatic control and are not easy to hack, configure and/or extend. Additionally, most of the those solutions are based only on the TCP stack, instead of providing features to the specific domain of the HTTP protocol, like toxy does. | ||
`toxy` provides a powerful hacking-driven and extensible solution with a convenient low-level interface and extensible programmatic control, serveds with a simple and fluent API and the power, simplicity and fun of node.js. | ||
`toxy` provides a powerful hackable and extensible solution with a convenient low-level interface and programmatic features based on simple, concise and fluent API, with the power, simplicity and fun of node.js. | ||
### Concepts | ||
`toxy` introduces two main core directives worth knowing before using it: | ||
`toxy` introduces two core directives that you can plug in the proxy and should knowing before using: poisons and rules. | ||
**Poisons** are the specific logic to infect an incoming or outgoing HTTP flow (e.g: injecting a latency, replying with an error). HTTP flow can be poisoned by one or multiple poisons, and you can plug in poisons at global or route level. | ||
**Poisons** are the specific logic to infect an incoming or outgoing HTTP flow (e.g: injecting a latency, replying with an error). HTTP flow can be poisoned by one or multiple poisons, and poisons can be plugged to infect both global or route level incoming traffic. | ||
**Rules** are a kind of validation filters that are applied to the whole global HTTP flow or to a concrete poison, in order to determine if one or multiple poisons should be enabled or not to infect the HTTP traffic (e.g: match headers, query params, method...). | ||
**Rules** are a kind of validation filters that can be reused and applied to global incoming HTTP traffic, route level traffic or into a specific poison. Their responsability is to determine, via inspecting each incoming HTTP request, if the registered poisons should be enabled or not, and therefore infecting or not the HTTP traffic (e.g: match headers, query params, method, body...). | ||
@@ -92,3 +91,3 @@ ### How it works | ||
↓ ---------------- ↓ | ||
↓ | Toxy Router | ↓ --> Match a route based on the incoming request | ||
↓ | Toxy Router | ↓ --> Match the incoming request | ||
↓ ---------------- ↓ | ||
@@ -99,5 +98,5 @@ ↓ ||| ↓ | ||
↓ ---------------- ↓ | ||
↓ ||| ↓ | ||
↓ ||| ↓ | ||
↓ ---------------- ↓ | ||
↓ | Exec Poisons | ↓ --> If all rules passed, poisoning the HTTP flow | ||
↓ | Exec Poisons | ↓ --> If all rules passed, then poison the HTTP flow | ||
↓ ---------------- ↓ | ||
@@ -107,3 +106,3 @@ ↓ / \ ↓ | ||
↓ ------------------- ↓ | ||
↓ | HTTP dispatcher | ↓ --> Proxy the HTTP traffic for both poisoned or not | ||
↓ | HTTP dispatcher | ↓ --> Proxy the HTTP traffic, either poisoned or not | ||
↓ ------------------- ↓ | ||
@@ -124,4 +123,2 @@ ``` | ||
#### Basic poisioning | ||
```js | ||
@@ -137,10 +134,35 @@ var toxy = require('toxy') | ||
// Register global poisons and rules | ||
proxy | ||
.poison(poisons.latency({ jitter: 500 })) | ||
.rule(rules.random(50)) | ||
.rule(rules.probability(25)) | ||
// Register multiple routes | ||
proxy | ||
.get('/download/*') | ||
.poison(poisons.bandwidth({ bps: 1024 })) | ||
.withRule(rules.headers({'Authorization': /^Bearer (.*)$/i })) | ||
proxy | ||
.get('/image/*') | ||
.poison(poisons.bandwidth({ bps: 512 })) | ||
proxy | ||
.all('/api/*') | ||
.poison(poisons.rateLimit({ limit: 10, threshold: 1000 })) | ||
.withRule(rules.method(['POST', 'PUT', 'DELETE'])) | ||
// And use a different more permissive poison for GET requests | ||
.poison(poisons.rateLimit({ limit: 50, threshold: 1000 })) | ||
.withRule(rules.method('GET')) | ||
proxy.get('/*') | ||
// Handle the rest of the traffic | ||
proxy | ||
.all('/*') | ||
.poison(poisons.slowClose({ delay: 1000 })) | ||
.poison(poisons.slowRead({ bps: 128 })) | ||
.withRule(rules.probability(50)) | ||
proxy.listen(3000) | ||
console.log('Server listening on port:', 3000) | ||
console.log('Test it:', 'http://localhost:3000/image/jpeg') | ||
``` | ||
@@ -166,3 +188,3 @@ | ||
- **options** `object` | ||
- **jitter*+ `number` - Jitter value in miliseconds | ||
- **jitter** `number` - Jitter value in miliseconds | ||
- **max** `number` - Random jitter maximum value | ||
@@ -185,3 +207,3 @@ - **min** `number` - Random jitter minimum value | ||
- **options** `object` | ||
- **code*+ `number` - Response HTTP status code | ||
- **code** `number` - Response HTTP status code | ||
- **headers** `object` - Optional headers to send | ||
@@ -204,10 +226,12 @@ - **body** `mixed` - Optional body data to send | ||
This poison is basically an alias to [throttle](#throttle). | ||
**Arguments**: | ||
- **options** `object` | ||
- **bps*+ `number` - Bytes per seconds | ||
- **threshold** `number` - Threshold time frame in miliseconds | ||
- **bps** `number` - Bytes per second. Default to `1024` | ||
- **threshold** `number` - Limit time frame in miliseconds. Default `1000` | ||
```js | ||
toxy.poison(toxy.poisons.bandwidth({ bps: 1024 })) | ||
toxy.poison(toxy.poisons.bandwidth({ bps: 512 })) | ||
``` | ||
@@ -218,5 +242,5 @@ | ||
Limits the amount of requests received by the proxy in a specific threshold time frame. Designed to test API limits. Exposes the `X-RateLimit-*` headers. | ||
Limits the amount of requests received by the proxy in a specific threshold time frame. Designed to test API limits. Exposes typical `X-RateLimit-*` headers. | ||
Limits are stored in-memory, meaning they are volalite and therfore flushed on every server stop. | ||
Note that this is very simple rate limit implementation, indeed limits are stored in-memory, therefore are completely volalite. There're a bunch of more featured and consistent rate limiter implementations in [npm](https://www.npmjs.com/search?q=rate+limit) that you can plug in as poison. | ||
@@ -226,9 +250,9 @@ **Arguments**: | ||
- **options** `object` | ||
- **limit*+ `number` - Total amount of request | ||
- **threshold** `number` - Limit threshold time frame in miliseconds. | ||
- **limit** `number` - Total amount of request. Default to `10` | ||
- **threshold** `number` - Limit threshold time frame in miliseconds. Default to `1000` | ||
- **message** `string` - Optional error message when limit reached. | ||
- **code** `number` - HTTP status code when limit reached. Default to 429. | ||
- **code** `number` - HTTP status code when limit reached. Default to `429`. | ||
```js | ||
toxy.poison(toxy.poisons.rateLimit({ limit: 10, threshold: 1000 })) | ||
toxy.poison(toxy.poisons.rateLimit({ limit: 5, threshold: 10 * 1000 })) | ||
``` | ||
@@ -268,3 +292,3 @@ | ||
Delays the HTTP connection close signal. | ||
Delays the HTTP connection close signal (EOF). | ||
@@ -283,7 +307,9 @@ **Arguments**: | ||
Restricts the amount of packets sent over the network in a specific threshold time frame.**Arguments**: | ||
Restricts the amount of packets sent over the network in a specific threshold time frame. | ||
**Arguments**: | ||
- **options** `object` | ||
- **chunk*+ `number` - Packet chunk size in bytes. Default to `1024` | ||
- **threshold** `object` - Limit threshold time frame in miliseconds. Default to `1000` | ||
- **chunk** `number` - Packet chunk size in bytes. Default to `1024` | ||
- **threshold** `object` - Limit threshold time frame in miliseconds. Default to `100` | ||
@@ -358,3 +384,4 @@ ```js | ||
Rules are useful to compose, decouple and reuse logic among different scenarios of poisoning. You can also define globally applied rules or nested poison-scope rules only. | ||
Rules are useful to compose, decouple and reuse logic among different scenarios of poisoning. | ||
Rules can be applied to the global, route or even poison scope. | ||
@@ -505,11 +532,29 @@ Rules are executed in FIFO order. Their evaluation logic is equivalent to `Array#every()` in JavaScript: all the rules must pass in order to proceed with the poisoning. | ||
### toxy.poisons `=>` Object | ||
Exposes a map with the built-in poisons. | ||
### toxy.rules `=>` Object | ||
Exposes a map with the built-in rules. | ||
### toxy.VERSION `=>` String | ||
Current toxy semantic version. | ||
#### toxy#get(path, [ middleware... ]) | ||
Return: `ToxyRoute` | ||
Register a new route for `GET` method. | ||
#### toxy#post(path, [ middleware... ]) | ||
Return: `ToxyRoute` | ||
Register a new route for `POST` method. | ||
#### toxy#put(path, [ middleware... ]) | ||
Return: `ToxyRoute` | ||
Register a new route for `PUT` method. | ||
#### toxy#patch(path, [ middleware... ]) | ||
@@ -521,69 +566,164 @@ Return: `ToxyRoute` | ||
Register a new route for `DELETE` method. | ||
#### toxy#head(path, [ middleware... ]) | ||
Return: `ToxyRoute` | ||
Register a new route for `HEAD` method. | ||
#### toxy#all(path, [ middleware... ]) | ||
Return: `ToxyRoute` | ||
Register a new route for any method. | ||
#### toxy#poisons `=>` Object | ||
Exposes a map with the built-in poisons. Prototype alias to `toxy.poisons` | ||
#### toxy#rules `=>` Object | ||
Exposes a map with the built-in poisons. Prototype alias to `toxy.rules` | ||
#### toxy#forward(url) | ||
Define a URL to forward the incoming traffic received by the proxy. | ||
#### toxy#balance(urls) | ||
Forward to multiple servers balancing among them. | ||
For more information, see the [rocky docs](https://github.com/h2non/rocky#programmatic-api) | ||
#### toxy#replay(url) | ||
Define a new replay server. | ||
You can call this method multiple times to define multiple replay servers. | ||
For more information, see the [rocky docs](https://github.com/h2non/rocky#programmatic-api) | ||
#### toxy#use(middleware) | ||
Plug in a custom middleware. | ||
For more information, see the [rocky docs](https://github.com/h2non/rocky#middleware-layer). | ||
#### toxy#useResponse(middleware) | ||
Plug in a response outgoing traffic middleware. | ||
For more information, see the [rocky docs](https://github.com/h2non/rocky#middleware-layer). | ||
#### toxy#useReplay(middleware) | ||
Plug in a replay traffic middleware. | ||
For more information, see the [rocky docs](https://github.com/h2non/rocky#middleware-layer) | ||
#### toxy#middleware() | ||
Return a standard middleware to use with connect/express. | ||
#### toxy#listen(port) | ||
Starts the built-in HTTP server, listening on a specific TCP port. | ||
#### toxy#close([ callback ]) | ||
Closes the HTTP server. | ||
#### toxy#poison(poison) | ||
Alias: `usePoison` | ||
Register a new poison. | ||
#### toxy#rule(rule) | ||
Alias: `useRule` | ||
Register a new rule. | ||
#### toxy#withRule(rule) | ||
Aliases: `poisonRule`, `poisonFilter` | ||
Apply a new rule for the latest registered poison. | ||
#### toxy#enable(poison) | ||
Enable a poison by name identifier | ||
#### toxy#disable(poison) | ||
Disable a poison by name identifier | ||
#### toxy#remove(poison) | ||
Return: `boolean` | ||
Remove poison by name identifier. | ||
#### toxy#isEnabled(poison) | ||
Return: `boolean` | ||
Checks if a poison is enabled by name identifier. | ||
#### toxy#disableAll() | ||
Alias: `disablePoisons` | ||
#### toxy#poisons() | ||
Return: `array<Directive>` Alias: `getPoisons` | ||
Disable all the registered poisons. | ||
#### toxy#getPoison(poison) | ||
Return: `Directive|null` | ||
Searchs and retrieves a registered poison in the stack by name identifier. | ||
#### toxy#getPoisons() | ||
Return: `array<Directive>` | ||
Return an array of registered poisons wrapped as `Directive`. | ||
#### toxy#flush() | ||
Alias: `flushPoisons` | ||
Remove all the registered poisons. | ||
#### toxy#enableRule(rule) | ||
Enable a rule by name identifier. | ||
#### toxy#disableRule(rule) | ||
Disable a rule by name identifier. | ||
#### toxy#removeRule(rule) | ||
Return: `boolean` | ||
Remove a rule by name identifier. | ||
#### toxy#disableRules() | ||
Disable all the registered rules. | ||
#### toxy#isRuleEnabled(rule) | ||
Return: `boolean` | ||
#### toxy#rules() | ||
Return: `array<Directive>` Alias: `getRules` | ||
Checks if the given rule is enabled by name identifier. | ||
#### toxy#getRule(rule) | ||
Return: `Directive|null` | ||
Searchs and retrieves a registered rule in the stack by name identifier. | ||
#### toxy#getRules() | ||
Return: `array<Directive>` | ||
Returns and array with the registered rules wrapped as `Directive`. | ||
#### toxy#flushRules() | ||
Remove all the rules. | ||
### ToxyRoute | ||
Toxy route has, indeed, the same interface as `Toxy` global interface, but further actions you perform againts the API will only be applicable at route-level. In other words: good news, you already know the API. | ||
Toxy route has, indeed, the same interface as `Toxy` global interface, it just adds some route level [additional methods](https://github.com/h2non/rocky#routepath). | ||
Further actions you perform againts the `ToxyRoute` API will only be applicable at route-level (nested). In other words: you already know the API. | ||
This example will probably clarify possible doubts: | ||
@@ -596,2 +736,3 @@ ```js | ||
proxy | ||
.forward('http://server.net') | ||
.poison(toxy.poisons.bandwidth({ bps: 1024 })) | ||
@@ -601,3 +742,7 @@ .rule(toxy.rules.method('GET')) | ||
// Now create a route | ||
var route = proxy.get('/foo') | ||
var route = proxy | ||
.get('/foo') | ||
.toPath('/bar') // Route-level API method | ||
.host('server.net') // Route-level API method | ||
.forward('http://new.server.net') | ||
@@ -604,0 +749,0 @@ // Now using the ToxyRoute interface |
@@ -16,3 +16,3 @@ const http = require('http') | ||
expect(err).to.be.undefined | ||
expect(Date.now() - init).to.be.at.least(delay) | ||
expect(Date.now() - init).to.be.at.least(delay - 1) | ||
done() | ||
@@ -19,0 +19,0 @@ } |
@@ -39,6 +39,6 @@ const http = require('http') | ||
test('close', function (done) { | ||
test('premature close', function (done) { | ||
var req = new http.IncomingMessage | ||
var res = new http.OutgoingMessage | ||
var threshold = 5 | ||
var threshold = 10 | ||
var spy = sinon.spy() | ||
@@ -63,3 +63,3 @@ var init = Date.now() | ||
function assert() { | ||
expect(Date.now() - init).to.be.within(0, 2) | ||
expect(Date.now() - init).to.be.within(0, 5) | ||
expect(spy.args).to.have.length(3) | ||
@@ -66,0 +66,0 @@ done() |
@@ -43,3 +43,3 @@ const http = require('http') | ||
var res = new http.OutgoingMessage | ||
var opts = { chunk: 1024, threshold: 5 } | ||
var opts = { chunk: 1024, threshold: 10 } | ||
@@ -53,3 +53,3 @@ var buf = [] | ||
expect(buffer).to.have.length(body.length) | ||
expect(Date.now() - lastWrite).to.be.at.least(opts.threshold) | ||
expect(Date.now() - lastWrite).to.be.at.least(opts.threshold - 1) | ||
buf.push(buffer) | ||
@@ -56,0 +56,0 @@ next() |
104
test/toxy.js
const http = require('http') | ||
const sinon = require('sinon') | ||
const expect = require('chai').expect | ||
@@ -56,19 +57,68 @@ const toxy = require('..') | ||
test('e2e', function (done) { | ||
test('get directives', function () { | ||
var proxy = toxy() | ||
var server = createServer(9001, 200) | ||
var called = false | ||
proxy.poison(function delay() {}) | ||
proxy.rule(function match() {}) | ||
expect(proxy.isEnabled('delay')).to.be.true | ||
expect(proxy.isRuleEnabled('match')).to.be.true | ||
var poison = proxy.getPoison('delay') | ||
expect(poison).to.be.an('object') | ||
expect(poison.isEnabled()).to.be.true | ||
poison.disable() | ||
expect(poison.isEnabled()).to.be.false | ||
var rule = proxy.getRule('match') | ||
expect(rule).to.be.an('object') | ||
expect(rule.isEnabled()).to.be.true | ||
rule.disable() | ||
expect(rule.isEnabled()).to.be.false | ||
}) | ||
test('flush directives', function () { | ||
var proxy = toxy() | ||
var called = false | ||
proxy.poison(function delay() {}) | ||
proxy.rule(function match() {}) | ||
expect(proxy.isEnabled('delay')).to.be.true | ||
expect(proxy.isRuleEnabled('match')).to.be.true | ||
proxy.flush() | ||
expect(proxy.isEnabled('delay')).to.be.false | ||
expect(proxy.getPoison('delay')).to.be.null | ||
expect(proxy.getPoisons()).to.have.length(0) | ||
proxy.flushRules() | ||
expect(proxy.isRuleEnabled('match')).to.be.false | ||
expect(proxy.getRule('match')).to.be.null | ||
expect(proxy.getRules()).to.have.length(0) | ||
}) | ||
test('basic proxy', function (done) { | ||
var proxy = toxy() | ||
var spy = sinon.spy() | ||
var server = createServer(9081, 200) | ||
var timeout = 100 | ||
proxy.poison(toxy.poisons.latency(timeout)) | ||
proxy.poison(function delay(req, res, next) { | ||
spy(req, res) | ||
setTimeout(next, timeout) | ||
}) | ||
proxy.rule(function method(req, res, next) { | ||
next(req.method === 'GET' ? null : true) | ||
spy(req, res) | ||
next(null, req.method !== 'GET') | ||
}) | ||
proxy.forward('http://localhost:9001') | ||
proxy.forward('http://localhost:9081') | ||
proxy.get('/foo') | ||
proxy.listen(9000) | ||
proxy.listen(9080) | ||
var init = Date.now() | ||
supertest('http://localhost:9000') | ||
supertest('http://localhost:9080') | ||
.get('/foo') | ||
@@ -82,2 +132,42 @@ .expect(200) | ||
expect(Date.now() - init).to.be.at.least(timeout - 1) | ||
expect(spy.calledTwice).to.be.true | ||
expect(spy.args[0][0].url).to.be.equal('/foo') | ||
expect(spy.args[0][0].method).to.be.equal('GET') | ||
server.close() | ||
proxy.close(done) | ||
} | ||
}) | ||
test('final route handler when no matches', function (done) { | ||
var proxy = toxy() | ||
var spy = sinon.spy() | ||
var server = createServer(9081, 200) | ||
var timeout = 100 | ||
proxy.poison(function delay(req, res, next) { | ||
throw 'Should not be called' | ||
}) | ||
proxy.rule(function method(req, res, next) { | ||
spy(req, res) | ||
next(null, true) | ||
}) | ||
proxy.forward('http://localhost:9081') | ||
proxy.get('/foo') | ||
proxy.listen(9080) | ||
var init = Date.now() | ||
supertest('http://localhost:9080') | ||
.get('/foo') | ||
.expect(200) | ||
.expect('Content-Type', 'application/json') | ||
.expect({ hello: 'world' }) | ||
.end(assert) | ||
function assert(err) { | ||
expect(spy.calledOnce).to.be.true | ||
expect(spy.args[0][0].url).to.be.equal('/foo') | ||
expect(spy.args[0][0].method).to.be.equal('GET') | ||
done(err) | ||
@@ -84,0 +174,0 @@ } |
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
78403
64
1869
758
3