toxy
Advanced tools
Comparing version 0.1.3 to 0.2.0
@@ -19,2 +19,3 @@ const Toxy = require('./lib/toxy') | ||
toxy.admin = require('./lib/admin') | ||
toxy.Rule = require('./lib/rule') | ||
@@ -21,0 +22,0 @@ toxy.Base = require('./lib/base') |
@@ -35,1 +35,9 @@ exports.isRegExp = function isRegExp(o) { | ||
} | ||
exports.randomId = function (head, tail) { | ||
var id = 0 | ||
var seed = head + '|' + tail | ||
var len = seed.length | ||
while (len--) { id += seed.charCodeAt(len) } | ||
return id.toString(16).slice(0, 10) | ||
} |
@@ -9,2 +9,3 @@ const Rule = require('./rule') | ||
this.directive = directive | ||
this.name = directive.$name || directive.name | ||
} | ||
@@ -51,5 +52,5 @@ | ||
handler.$of = this | ||
handler.$name = this.directive.$name || this.directive.name | ||
handler.$name = this.name | ||
return handler | ||
} |
module.exports = function inject(opts) { | ||
var code = +opts.code || 500 | ||
return function inject(req, res, next) { | ||
res.writeHead(+opts.code || 500, opts.headers) | ||
res.writeHead(code, opts.headers) | ||
res.end(opts.body, opts.encoding) | ||
} | ||
} |
@@ -7,6 +7,7 @@ module.exports = function slowClose(opts) { | ||
var ended = false | ||
// Cache native methods | ||
var _end = res.end | ||
var resproto = Object.getPrototypeOf(res) | ||
var _setHeader = resproto.setHeader | ||
var _writeHead = resproto.writeHead | ||
var _setHeader = res.setHeader | ||
var _writeHead = res.writeHead | ||
@@ -39,3 +40,3 @@ res.setHeader = function (header, value) { | ||
// End response | ||
// Ends the response | ||
res.end.apply(res, args) | ||
@@ -45,3 +46,3 @@ | ||
_res = _setHeader = null | ||
_writeHead = args = resproto = null | ||
_writeHead = args = null | ||
} | ||
@@ -48,0 +49,0 @@ } |
@@ -7,3 +7,3 @@ const common = require('../common') | ||
var threshold = +opts.threshold || 1000 | ||
var chunkSize = (+opts.chunk || +opts.bps) || 1024 | ||
var chunkSize = +opts.chunk || +opts.bps || 1024 | ||
@@ -16,3 +16,5 @@ return function slowRead(req, res, next) { | ||
if (isInvalidMethod(req)) return next() | ||
if (isInvalidMethod(req)) { | ||
return next() | ||
} | ||
@@ -42,7 +44,9 @@ // Handle client close connection properly | ||
function pushDefer(chunk, next) { | ||
setTimeout(function () { | ||
setTimeout(push, threshold) | ||
function push() { | ||
if (closed) return next('closed') | ||
_push.call(req, chunk.buffer, chunk.encoding) | ||
next() | ||
}, threshold) | ||
} | ||
} | ||
@@ -49,0 +53,0 @@ |
@@ -6,3 +6,3 @@ const common = require('../common') | ||
var threshold = +opts.threshold || 100 | ||
var chunkSize = (+opts.chunk || +opts.bps) || 1024 | ||
var chunkSize = +opts.bps || +opts.chunk || 1024 | ||
@@ -14,7 +14,6 @@ return function throttle(req, res, next) { | ||
var resproto = Object.getPrototypeOf(res) | ||
var _end = resproto.end | ||
var _write = resproto.write | ||
var _end = res.end | ||
var _write = res.write | ||
// Listen for client connection close | ||
// Listen for connection close in both ends | ||
req.on('close', cleanup) | ||
@@ -34,3 +33,3 @@ res.on('close', cleanup) | ||
// Party time: finally write each chunk with a delay in FIFO order | ||
// Party time: write each chunk with a delay in FIFO order | ||
common.eachSeries(buf, writeDefer, end) | ||
@@ -45,6 +44,8 @@ | ||
function writeDefer(chunk, next) { | ||
setTimeout(function () { | ||
setTimeout(write, threshold) | ||
function write() { | ||
if (closed) return next('closed') | ||
_write.call(res, chunk.buffer, chunk.encoding, next) | ||
}, threshold) | ||
} | ||
} | ||
@@ -56,8 +57,8 @@ | ||
// Restore and clean references | ||
// Restore methods | ||
res.end = _end | ||
res.write = _write | ||
_end = _write = resproto = buf = null | ||
// Clean listeners to prevent leaks | ||
// Clean references and listeners to prevent leaks | ||
_end = _write = buf = null | ||
req.removeListener('close', cleanup) | ||
@@ -64,0 +65,0 @@ req.removeListener('close', cleanup) |
const midware = require('midware') | ||
const Proxy = require('./proxy') | ||
const Admin = require('./admin') | ||
const randomId = require('./common').randomId | ||
const noop = function () {} | ||
const defaultPort = +process.env.PORT || 3000 | ||
module.exports = Toxy | ||
@@ -8,2 +13,4 @@ | ||
Proxy.call(this, opts) | ||
this.routes = [] | ||
this._rules = midware() | ||
@@ -18,12 +25,26 @@ this._poisons = midware() | ||
Toxy.prototype.listen = function (port, host) { | ||
this.host = host | ||
this.port = +port || defaultPort | ||
Proxy.prototype.listen.call(this, this.port, host) | ||
return this | ||
} | ||
function wrapRouteConstructor(self) { | ||
var _route = self.route | ||
self.route = function () { | ||
self.route = function (method, path) { | ||
var route = _route.apply(self, arguments) | ||
// Create toxy route-evel specific middleware | ||
// Expose useful data in the route | ||
route.id = randomId(method, path) | ||
route.method = method.toUpperCase() | ||
// Creates toxy route-level middleware stacks | ||
route._rules = midware() | ||
route._poisons = midware() | ||
// Setup middleware and final route handler | ||
// Register route in the toxy stack | ||
self.routes.push(route) | ||
// Setup route middleware and final handler | ||
setupMiddleware(route) | ||
@@ -39,3 +60,3 @@ reDispatchRoute(route) | ||
route.use(function (req, res, next) { | ||
route.dispatcher.doDispatch(req, res, function () {}) | ||
route.dispatcher.doDispatch(req, res, noop) | ||
}) | ||
@@ -42,0 +63,0 @@ } |
{ | ||
"name": "toxy", | ||
"version": "0.1.3", | ||
"description": "Hackable HTTP proxy to simulate server failure scenarios and unexpected conditions", | ||
"version": "0.2.0", | ||
"description": "Hackable HTTP proxy to simulate server failure scenarios and unexpected network conditions", | ||
"repository": "h2non/toxy", | ||
@@ -18,3 +18,3 @@ "author": "Tomas Aparicio", | ||
"testing", | ||
"resilency", | ||
"resiliency", | ||
"fuzz", | ||
@@ -27,3 +27,6 @@ "evil", | ||
"unexpected", | ||
"backoff" | ||
"backoff", | ||
"network", | ||
"latency", | ||
"jitter" | ||
], | ||
@@ -38,6 +41,9 @@ "engines": { | ||
"midware": "^0.1.3", | ||
"object-assign": "^3.0.0", | ||
"raw-body": "^2.1.2", | ||
"rocky": "^0.3.3" | ||
"rocky": "^0.3.3", | ||
"router": "^1.1.3" | ||
}, | ||
"devDependencies": { | ||
"async": "^1.4.2", | ||
"chai": "^3.0.0", | ||
@@ -44,0 +50,0 @@ "clone": "^1.0.2", |
343
README.md
@@ -1,12 +0,13 @@ | ||
# 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 [![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) | ||
<img align="right" height="180" src="http://s8.postimg.org/ikc9jxllh/toxic.jpg" /> | ||
**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). | ||
**toxy** is a fully programmatic and **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 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. | ||
It was mainly designed for fuzzing/evil testing purposes, toxy becomes 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 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. | ||
toxy allows you to plug in [poisons](#poisons), optionally filtered by [rules](#rules), which essentially 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. | ||
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. | ||
toxy can be fluently used [programmatically](#programmatic-api) or via [HTTP API](#http-api). | ||
It's 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. | ||
@@ -47,2 +48,7 @@ Requires node.js +0.12 or io.js +1.6 | ||
- [Programmatic API](#programmatic-api) | ||
- [HTTP API](#http-api) | ||
- [Usage](#usage) | ||
- [Authorization](#authorization) | ||
- [API](#api) | ||
- [Programmatic API](#programmatic-api-1) | ||
- [License](#license) | ||
@@ -54,5 +60,6 @@ | ||
- Hackable and elegant programmatic API (inspired on connect/express) | ||
- Admin HTTP API for external management and dynamic configuration | ||
- Featured built-in router with nested configuration | ||
- Hierarchical poisioning and rules based filtering | ||
- Hierarchical middleware layer (global and route-specific) | ||
- Hierarchical and composable poisioning with rule based filtering | ||
- Hierarchical middleware layer (both global and route scopes) | ||
- Easily augmentable via middleware (based on connect/express middleware) | ||
@@ -65,3 +72,3 @@ - Built-in poisons (bandwidth, error, abort, latency, slow read...) | ||
- Compatible with connect/express (and most of their middleware) | ||
- Runs as standalone HTTP proxy | ||
- Able to run as standalone HTTP proxy | ||
@@ -72,5 +79,5 @@ ## Introduction | ||
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. | ||
There're some other similar solutions like `toxy` in the market, but most of them do not provide a proper programmatic control and usually are not easy to hack, configure and/or extend. Additionally, most of the those solutions only operate at TCP level stack instead of providing high-level abstraction to cover common requirements of the specific domain and nature of the HTTP protocol, like toxy does. | ||
`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. | ||
toxy provides a powerful hackable and extensible solution with a convenient abstraction, but also a low-level interface and programmatic capabilities exposed as a simple, concise and fluent API, with the implicit power, simplicity and fun of node.js. | ||
@@ -118,3 +125,3 @@ ### Concepts | ||
See the [examples](https://github.com/h2non/toxy/tree/master/examples) directory for more use cases | ||
See [examples](https://github.com/h2non/toxy/tree/master/examples) directory for more use cases. | ||
@@ -126,4 +133,6 @@ ```js | ||
// Create a new toxy proxy | ||
var proxy = toxy() | ||
// Default server to forward incoming traffic | ||
proxy | ||
@@ -140,2 +149,3 @@ .forward('http://httpbin.org') | ||
.get('/download/*') | ||
.forward('http://files.myserver.net') | ||
.poison(poisons.bandwidth({ bps: 1024 })) | ||
@@ -239,3 +249,4 @@ .withRule(rules.headers({'Authorization': /^Bearer (.*)$/i })) | ||
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. | ||
Note that this is very simple rate limit implementation, indeed limits are stored in-memory, therefore are completely volalite. | ||
There're a bunch of featured and consistent rate limiter implementations in [npm](https://www.npmjs.com/search?q=rate+limit) that you can plug in as poison. You might also interested in [token bucket algorithm](http://en.wikipedia.org/wiki/Token_bucket). | ||
@@ -315,7 +326,8 @@ **Arguments**: | ||
Aborts the TCP connection, optionally with a custom error. From the low-level perspective, this will destroy the socket on the server, operating only at TCP level without sending any specific HTTP application level data. | ||
Aborts the TCP connection. From the low-level perspective, this will destroy the socket on the server, operating only at TCP level without sending any specific HTTP application level data. | ||
**Arguments**: | ||
- **miliseconds** `number` - Optional socket destroy delay in miliseconds | ||
- **options** `object` | ||
- **delay** `number` - Socket destroy delay in miliseconds. Default to `0` | ||
@@ -524,14 +536,2 @@ ```js | ||
### 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... ]) | ||
@@ -714,2 +714,14 @@ Return: `ToxyRoute` | ||
### 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. | ||
### ToxyRoute | ||
@@ -766,4 +778,283 @@ | ||
## HTTP API | ||
The `toxy` HTTP API follows the [JSON API](http://jsonapi.org) conventions, including resouce based hypermedia linking. | ||
### Usage | ||
```js | ||
const toxy = require('toxy') | ||
// Create the toxy admin server | ||
var admin = toxy.admin() | ||
admin.listen(9000) | ||
// Create the toxy proxy | ||
var proxy = toxy() | ||
proxy.listen(3000) | ||
// Add the toxy instance to be managed by the admin server | ||
admin.manage(proxy) | ||
console.log('toxy proxy listening on port:', 3000) | ||
console.log('toxy admin server listening on port:', 9000) | ||
``` | ||
For more details about the admin programmatic API, see [below](#programmatic-api-1). | ||
### Authorization | ||
The HTTP API can be protected to unauthorized clients. | ||
Authorized clients must define the API key token via `API-Key` or `Authorization` HTTP headers. | ||
To enable it, you should simple pass the following options to `toxy` admin server: | ||
```js | ||
const toxy = require('toxy') | ||
const opts = { apiKey: 's3cr3t' } | ||
var admin = toxy.admin(opts) | ||
admin.listen(9000) | ||
console.log('protected toxy admin server listening on port:', 9000) | ||
``` | ||
### API | ||
**Hierarchy**: | ||
- Servers - Managed `toxy` instances | ||
- Rules - Globally applied rules | ||
- Poisons - Globally applied poisons | ||
- Rules - Poison-specific rules | ||
- Routes - List of configured routes | ||
- Route - Object for each specific route | ||
- Rules - Route-level registered rules | ||
- Poisons - Route-level registered poisons | ||
- Rules - Route-level and poison-specific rules | ||
#### GET / | ||
### Servers | ||
#### GET /servers | ||
#### GET /servers/:id | ||
### Rules | ||
#### GET /servers/:id/rules | ||
#### POST /servers/:id/rules | ||
Accepts: `application/json` | ||
Example payload: | ||
```js | ||
{ | ||
"name": "method", | ||
"options": "GET" | ||
} | ||
``` | ||
#### DELETE /servers/:id/rules | ||
#### GET /servers/:id/rules/:id | ||
#### DELETE /servers/:id/rules/:id | ||
### Poisons | ||
#### GET /servers/:id/poison | ||
#### POST /servers/:id/poisons | ||
Accepts: `application/json` | ||
Example payload: | ||
```js | ||
{ | ||
"name": "latency", | ||
"options": { "jitter": 1000 } | ||
} | ||
``` | ||
#### DELETE /servers/:id/poisons | ||
#### GET /servers/:id/poisons/:id | ||
#### DELETE /servers/:id/poisons/:id | ||
#### GET /servers/:id/poisons/:id/rules | ||
#### POST /servers/:id/poisons/:id/rules | ||
Accepts: `application/json` | ||
Example payload: | ||
```js | ||
{ | ||
"name": "method", | ||
"options": "GET" | ||
} | ||
``` | ||
#### DELETE /servers/:id/poisons/:id/rules | ||
#### GET /servers/:id/poisons/:id/rules/:id | ||
#### DELETE /servers/:id/poisons/:id/rules/:id | ||
### Routes | ||
#### GET /servers/:id/routes | ||
#### POST /servers/:id/routes | ||
Accepts: `application/json` | ||
Example payload: | ||
```js | ||
{ | ||
"path": "/foo", // Required | ||
"method": "GET", // use ALL for all the methods | ||
"forward": "http://my.server", // Optional custom forward server URL | ||
} | ||
``` | ||
#### DELETE /servers/:id/routes | ||
#### GET /servers/:id/routes/:id | ||
#### DELETE /servers/:id/routes/:id | ||
### Route rules | ||
#### GET /servers/:id/routes/:id/rules | ||
#### POST /servers/:id/routes/:id/rules | ||
Accepts: `application/json` | ||
Example payload: | ||
```js | ||
{ | ||
"name": "method", | ||
"options": "GET" | ||
} | ||
``` | ||
#### DELETE /servers/:id/routes/:id/rules | ||
#### GET /servers/:id/routes/:id/rules/:id | ||
#### DELETE /servers/:id/routes/:id/rules/:id | ||
### Route poisons | ||
#### GET /servers/:id/routes/:id/poisons | ||
#### POST /servers/:id/routes/:id/poisons | ||
Accepts: `application/json` | ||
Example payload: | ||
```js | ||
{ | ||
"name": "latency", | ||
"options": { "jitter": 1000 } | ||
} | ||
``` | ||
#### DELETE /servers/:id/routes/:id/poisons | ||
#### GET /servers/:id/routes/:id/poisons/:id | ||
#### DELETE /servers/:id/routes/:id/poisons/:id | ||
#### GET /servers/:id/routes/:id/poisons/:id/rules | ||
#### POST /servers/:id/routes/:id/poisons/:id/rules | ||
Accepts: `application/json` | ||
Example payload: | ||
```js | ||
{ | ||
"name": "method", | ||
"options": "GET" | ||
} | ||
``` | ||
#### DELETE /servers/:id/routes/:id/poisons/:id/rules | ||
#### GET /servers/:id/routes/:id/poisons/:id/rules/:id | ||
#### DELETE /servers/:id/routes/:id/poisons/:id/rules/:id | ||
### Programmatic API | ||
The built-in HTTP admin server also provides a simple interface open to extensibility and hacking purposes. | ||
For instance, you can plug in additional middleware to the admin server, or register new routes. | ||
#### toxy.admin([ opts ]) | ||
Returns: `Admin` | ||
**Supported options**: | ||
- **apiKey** - Optional API key to protect the server | ||
- **port** - Optional. TCP port to listen | ||
##### Admin#listen([ port, host ]) | ||
Start listening on the network. | ||
##### Admin#manage(toxy) | ||
Manage a `toxy` server instance. | ||
##### Admin#find(toxy) | ||
Find a toxy instance. Accepts toxy server ID or toxy instance. | ||
##### Admin#remove(toxy) | ||
Stop managing a toxy instance. | ||
##### Admin#use(...middleware) | ||
Register a middleware. | ||
##### Admin#param(...middleware) | ||
Register a param middleware. | ||
##### Admin#get(path, [ ...middleware ]) | ||
Register a GET route. | ||
##### Admin#post(path, [ ...middleware ]) | ||
Register a POST route. | ||
##### Admin#put(path, [ ...middleware ]) | ||
Register a PUT route. | ||
##### Admin#delete(path, [ ...middleware ]) | ||
Register a DELETE route. | ||
##### Admin#patch(path, [ ...middleware ]) | ||
Register a PATCH route. | ||
##### Admin#all(path, [ ...middleware ]) | ||
Register a route accepting any HTTP method. | ||
##### Admin#middleware(req, res, next) | ||
Middleware to plug in with connect/express. | ||
##### Admin#close(cb) | ||
Stop the server. | ||
## License | ||
MIT - Tomas Aparicio |
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
115338
84
2914
1049
5
7
5
11
+ Addedobject-assign@^3.0.0
+ Addedrouter@^1.1.3
+ Addedobject-assign@3.0.0(transitive)