Comparing version 0.3.10 to 0.3.11
@@ -33,2 +33,5 @@ var http = require('http') | ||
req.body = newBody | ||
// Continue processing the request | ||
next() | ||
}) | ||
@@ -35,0 +38,0 @@ |
@@ -21,2 +21,9 @@ var http = require('http') | ||
}) | ||
.use(function (req, res, next) { | ||
if (req.params.id.length < 2) { | ||
console.log('Invalid ID param') | ||
} | ||
next() | ||
}) | ||
// Add middleware to transform the response | ||
@@ -56,2 +63,4 @@ .transformRequestBody(function transformer(req, res, next) { | ||
proxy.all('/*') | ||
proxy.listen(3000) | ||
@@ -88,3 +97,3 @@ | ||
res.writeHead(204) | ||
res.end() | ||
res.end('Hello from replay server') | ||
}).listen(3002) | ||
@@ -91,0 +100,0 @@ |
@@ -6,8 +6,10 @@ var http = require('http') | ||
var app = connect() | ||
var migrate = rocky() | ||
var proxy = rocky() | ||
migrate | ||
// Configure the proxy and routes | ||
proxy | ||
.forward('http://localhost:3001') | ||
migrate.get('/users/:id') | ||
proxy | ||
.get('/*') | ||
.replay('http://localhost:3002') | ||
@@ -22,3 +24,4 @@ .replay('http://localhost:3002') | ||
app.use(migrate.middleware()) | ||
// Plug in rocky middleware in connect | ||
app.use(proxy.middleware()) | ||
app.listen(3000) | ||
@@ -29,2 +32,3 @@ | ||
.use(function (req, res) { | ||
console.log('Target server reached') | ||
res.end('Hello World!') | ||
@@ -37,3 +41,3 @@ }) | ||
.use(function (req, res) { | ||
console.log('Not found') | ||
console.log('Replay server reached') | ||
res.statusCode = 404 | ||
@@ -45,4 +49,4 @@ res.end() | ||
// Test request | ||
http.get('http://localhost:3000/users/pepe', function (res) { | ||
console.log('Status:', res.statusCode) | ||
http.get('http://localhost:3000/', function (res) { | ||
console.log('Response status:', res.statusCode) | ||
@@ -49,0 +53,0 @@ res.on('data', function (chunk) { |
@@ -15,8 +15,17 @@ var fs = require('fs') | ||
// Forward to HTTPS server | ||
proxy | ||
.get('/image/*') | ||
.options({ secure: false }) | ||
.host('httpbin.org') | ||
.forward('https://httpbin.org') | ||
// Forward to plain HTTP server | ||
proxy | ||
.forward('http://localhost:3001') | ||
.all('/*') | ||
proxy.listen(3000) | ||
console.log('HTTPS server listening on port:', 3000) | ||
proxy.listen(3443) | ||
console.log('HTTPS server listening on port:', 3443) | ||
console.log('Open: https://localhost:3443/image/jpeg') | ||
@@ -23,0 +32,0 @@ // Target server |
const Dispatcher = require('./dispatcher') | ||
const toArray = require('./helpers').toArray | ||
@@ -6,4 +7,4 @@ exports = module.exports = routeHandler | ||
const middleware = exports.middleware = [ | ||
'forward', | ||
'replay', | ||
'forward', | ||
'replay', | ||
'response' | ||
@@ -39,3 +40,3 @@ ] | ||
route.on(event, function () { | ||
const args = Array.apply(null, arguments) | ||
const args = toArray(arguments) | ||
rocky.emit.apply(rocky, [ event ].concat(args)) | ||
@@ -42,0 +43,0 @@ }) |
@@ -8,3 +8,3 @@ const http = require('http') | ||
var server = opts.ssl | ||
const server = opts.ssl | ||
? https.createServer(opts.ssl, handler) | ||
@@ -25,12 +25,19 @@ : http.createServer(handler) | ||
if (err) { | ||
err.status = 500 | ||
rocky.emit('server:error', err, req, res) | ||
return errors.replyWithError(err, res) | ||
return serverError(rocky, err, req, res) | ||
} | ||
err = new Error('Route not configured') | ||
rocky.emit('route:missing', req, res) | ||
errors.replyWithError(err, res) | ||
notFound(rocky, req, res) | ||
} | ||
} | ||
} | ||
function notFound(rocky, req, res) { | ||
var err = new Error('Route not configured: ' + req.url) | ||
rocky.emit('route:missing', req, res) | ||
errors.replyWithError(err, res) | ||
} | ||
function serverError(rocky, err, req, res) { | ||
err.status = +err.status || 500 | ||
rocky.emit('server:error', err, req, res) | ||
errors.replyWithError(err, res) | ||
} |
const rawBody = require('raw-body') | ||
const typer = require('media-typer') | ||
const common = require('./common') | ||
const helpers = require('../helpers') | ||
@@ -11,4 +11,4 @@ module.exports = function transformRequestBody(middleware, filter) { | ||
// Apply the request filter is necessary | ||
if (filter && !common.filterRequest(filter, req)) { | ||
// Apply request filter, if defined | ||
if (filter && !helpers.filterRequest(filter, req)) { | ||
return next() | ||
@@ -15,0 +15,0 @@ } |
@@ -1,11 +0,11 @@ | ||
const common = require('./common') | ||
const helpers = require('../helpers') | ||
module.exports = function transformResponseBody(middleware, filter) { | ||
return function (req, res, next) { | ||
// Apply the request filter is necessary | ||
if (filter && !common.filterRequest(filter, res)) { | ||
// Apply request filter, if defined | ||
if (filter && !helpers.filterRequest(filter, res)) { | ||
return next() | ||
} | ||
// If body field is already present, just continue with it | ||
// If body is already present, just continue with it | ||
if (res.body) { | ||
@@ -24,5 +24,8 @@ return middleware(req, res, next) | ||
// Listen for client connection close | ||
req.on('close', onClose) | ||
// Wrap native http.OutgoingMessage methods | ||
res.writeHead = function (code, message, headers) { | ||
headArgs = Array.apply(null, arguments) | ||
headArgs = helpers.toArray(arguments) | ||
} | ||
@@ -52,3 +55,3 @@ | ||
// Expose the body | ||
// Expose the current body buffer | ||
res.body = res._originalBody = body | ||
@@ -61,11 +64,9 @@ | ||
// Restore and clean references | ||
cleanupAndRestore() | ||
// Restore native methods | ||
restore() | ||
// Trigger response middleware stack | ||
middleware(req, res, finisher(cb)) | ||
} | ||
// Listen for client connection close | ||
req.on('close', onClose) | ||
function onClose() { | ||
@@ -76,13 +77,2 @@ closed = true | ||
function cleanupAndRestore() { | ||
// Restore methods | ||
res.writeHead = _writeHead | ||
res.write = _write | ||
res.end = _end | ||
// Clean references to prevent leaks | ||
buf = body = _end = _write = headArgs = null | ||
req.removeListener('close', onClose) | ||
} | ||
function finisher(cb) { | ||
@@ -106,6 +96,9 @@ return function (err, body, encoding) { | ||
// Expose the new body int he response object, if exists | ||
// Expose the new body in the response object | ||
if (body) res.body = body | ||
// Send EOF | ||
// Clean references to prevent leaks | ||
cleanup() | ||
// Finally, send EOF | ||
res.end(cb) | ||
@@ -115,2 +108,19 @@ } | ||
function cleanupAndRestore() { | ||
restore() | ||
cleanup() | ||
} | ||
function restore() { | ||
res.writeHead = _writeHead | ||
res.write = _write | ||
res.end = _end | ||
_end = _write = null | ||
} | ||
function cleanup() { | ||
buf = body = length = headArgs = null | ||
req.removeListener('close', onClose) | ||
} | ||
next() | ||
@@ -117,0 +127,0 @@ } |
@@ -28,5 +28,3 @@ const midware = require('midware') | ||
var middleware = this.pool[name] | ||
if (!middleware) { | ||
return done() | ||
} | ||
if (!middleware) return done() | ||
@@ -33,0 +31,0 @@ middleware.run.apply(null, args) |
@@ -1,2 +0,2 @@ | ||
const common = require('../common') | ||
const helpers = require('../helpers') | ||
const errors = require('../errors') | ||
@@ -20,3 +20,3 @@ const retry = require('../retry') | ||
// Clone request object to avoid side-effects | ||
var forwardReq = common.cloneRequest(req, opts) | ||
var forwardReq = helpers.cloneRequest(req, opts) | ||
@@ -23,0 +23,0 @@ // Run the forward phase middleware |
const http = require('http') | ||
const https = require('https') | ||
const parseUrl = require('url').parse | ||
const common = require('../common') | ||
const helpers = require('../helpers') | ||
const retry = require('../retry') | ||
@@ -22,8 +22,8 @@ const FakeResponse = require('../http/response') | ||
replayStrategy(targets, opts, replayer, next) | ||
chooseReplayStrategy(targets, opts, replayer, next) | ||
} | ||
function replayStrategy(replays, opts, replayer, done) { | ||
function chooseReplayStrategy(replays, opts, replayer, done) { | ||
if (opts.replaySequentially) { | ||
common.eachSeries(replays, replayer, done) | ||
helpers.eachSeries(replays, replayer, done) | ||
} else { | ||
@@ -55,3 +55,3 @@ replays.forEach(replayer) | ||
var replayRes = new FakeResponse | ||
var replayReq = common.cloneRequest(req, opts) | ||
var replayReq = helpers.cloneRequest(req, opts) | ||
replayReq.rocky.isReplay = true | ||
@@ -160,3 +160,3 @@ | ||
// Write data to request body | ||
// Write the property data in the request body | ||
const body = opts.replayOriginalBody | ||
@@ -163,0 +163,0 @@ ? req._originalBody |
@@ -1,3 +0,2 @@ | ||
const eachSeries = require('./common').eachSeries | ||
exports.passes = require('./passes') | ||
const eachSeries = require('./helpers').eachSeries | ||
@@ -25,1 +24,3 @@ exports.sequentially = function (args, done) { | ||
} | ||
exports.passes = require('./passes') |
@@ -10,9 +10,17 @@ const retry = require('retry') | ||
var _end = res.end | ||
// Set methods to noop | ||
res.writeHead = function () {} | ||
res.end = function () {} | ||
// Create the retrier | ||
var op = retry.operation(opts) | ||
op.attempt(function () { task(handler) }) | ||
// Do the first attempt | ||
op.attempt(runTask) | ||
function runTask() { | ||
task(handler) | ||
} | ||
function handler(err, proxyRes) { | ||
@@ -24,4 +32,5 @@ if (op.retry(failed(err, proxyRes))) { | ||
// Restore native methods | ||
res.end = _end | ||
res.writeHead = _writeHead | ||
res.end = _end | ||
res = options = null | ||
@@ -28,0 +37,0 @@ done(err) |
const router = require('router') | ||
const Route = require('./route') | ||
const Base = require('./base') | ||
const MwPool = require('./mwpool') | ||
const routeHandler = require('./handler') | ||
const createServer = require('./http/server') | ||
const assign = require('lodash').assign | ||
const MwPool = require('./mwpool') | ||
const toArray = require('./helpers').toArray | ||
@@ -64,3 +65,3 @@ module.exports = Rocky | ||
Rocky.prototype[method] = function (path) { | ||
const args = [ method ].concat(Array.apply(null, arguments)) | ||
const args = [ method ].concat(toArray(arguments)) | ||
return this.route.apply(this, args) | ||
@@ -67,0 +68,0 @@ } |
@@ -12,2 +12,3 @@ const midware = require('midware') | ||
this.path = path | ||
this.unregistered = false | ||
} | ||
@@ -14,0 +15,0 @@ |
{ | ||
"name": "rocky", | ||
"version": "0.3.10", | ||
"description": "Full-featured, middleware-oriented HTTP proxy with traffic replay and intercept", | ||
"version": "0.3.11", | ||
"description": "Full-featured, middleware-oriented HTTP proxy supporting traffic replay and intercept", | ||
"repository": "h2non/rocky", | ||
@@ -41,3 +41,2 @@ "author": "Tomas Aparicio", | ||
"devDependencies": { | ||
"async": "^1.4.2", | ||
"chai": "^3.0.0", | ||
@@ -44,0 +43,0 @@ "connect": "^3.4.0", |
@@ -5,3 +5,3 @@ # rocky [![Build Status](https://api.travis-ci.org/h2non/rocky.svg?branch=master&style=flat)](https://travis-ci.org/h2non/rocky) [![Code Climate](https://codeclimate.com/github/h2non/rocky/badges/gpa.svg)](https://codeclimate.com/github/h2non/rocky) [![NPM](https://img.shields.io/npm/v/rocky.svg)](https://www.npmjs.org/package/rocky) ![Downloads](https://img.shields.io/npm/dm/rocky.svg) | ||
**Pluggable**, **full-featured** and **middleware-oriented** **HTTP/S proxy** with versatile **routing layer**, **traffic interceptor and replay** to multiple backends, **built-in balancer**, **hierarchical configuration**, optional traffic **retry/backoff** logic and [more](#features). | ||
**Pluggable**, **full-featured** and **middleware-oriented** **HTTP/S proxy** with versatile **routing layer**, **traffic interceptor and replay** to multiple backends, **built-in balancer**, **hierarchical route-based configuration**, traffic **retry/backoff** logic and [more](#features). | ||
Built for [node.js](http://nodejs.org)/[io.js](https://iojs.org). Compatible with [connect](https://github.com/senchalabs/connect)/[express](http://expressjs.com). | ||
@@ -26,2 +26,3 @@ | ||
- [How does it work?](#how-does-it-work) | ||
- [Projects using rocky](#projects-using-rocky) | ||
- [Middleware layer](#middleware-layer) | ||
@@ -68,3 +69,3 @@ - [Hierarchies](#hierarchies) | ||
- As HTTP proxy for service migrations (e.g: APIs) | ||
- Replaying traffic to one or multiple backends | ||
- Replaying traffic to one or multiple backends (like using [hop-by-hop](https://en.wikipedia.org/wiki/Hop-by-hop_transport) mechanism) | ||
- As reverse proxy to forward traffic to a specified server. | ||
@@ -76,7 +77,11 @@ - As HTTP traffic interceptor transforming the request/response on the fly | ||
- As security proxy layer | ||
- As HTTP load balancer with full programmatic control | ||
- As dynamic HTTP load balancer with programmatic control | ||
- As embedded HTTP proxy in your node.js app | ||
- As HTTP cache or log server | ||
- As SSL terminator proxy | ||
- As HTTP proxy for performance testing | ||
- For HTTP session manipulation and debugging | ||
- For HTTP traffic recording and inspection | ||
- For A/B testing | ||
- For fuzz testing (see [toxy](https://github.com/h2non/toxy)) | ||
- As intermediate test server intercepting and generating random/fake responses | ||
@@ -97,3 +102,3 @@ - And whatever a programmatic HTTP proxy can be useful to | ||
`rocky` was originally created to become an useful tool to assist during a backend migration strategy, later on it was extended and improved to cover so many other [scenarios](#when-rocky-could-be-useful). | ||
`rocky` was originally created to become an useful tool to assist during a backend migration strategy, later on it was extended and improved to cover so many other [scenarios](#when-rocky-can-be-useful). | ||
@@ -104,9 +109,7 @@ `rocky` goal is to provide a full-featured and hackable programmatic HTTP proxy to build other services on it, like you can do with web services using express or connect. | ||
`rocky` design is driven by keeping versatility and extensibility in mind with a small core and code base. | ||
Extensibility is covered via the middleware layer, which is the core and more powerful feature of `rocky`. | ||
`rocky` design is influenced by the [UNIX philosophy](http://www.catb.org/esr/writings/taoup/html/ch01s06.html), especially by the rules of composition, modularity and simplicity. As result, `rocky` tend to be a lightweight package with small core designed for extensibility. | ||
Via the middleware you can completely rely on a consistent control flow when performing actions with the HTTP traffic flow, such as modifying, pausing or stopping it, even for both incoming/outgoing flows. | ||
This allows you to plug in intermediate jobs with your own custom logic which will be executed among different phases of the HTTP flow live cycle inside `rocky`. | ||
The most important feature in rocky is its nested multi phase middleware layer. Via the middleware layer you can rely in a consistent control flow when performing actions with the HTTP traffic flow, such as modifying, pausing or stopping it, even for both incoming and outgoing traffic flows. | ||
`rocky` middleware layer is based on `connect` middleware. | ||
This allows you to plug in intermediate jobs with your custom logic which will be executed among different phases of the HTTP flow live cycle. | ||
@@ -149,5 +152,11 @@ ### Stability | ||
### Projects using rocky | ||
- [toxy](https://github.com/h2non/toxy) - Hackable HTTP proxy to simulare server failures and network conditions | ||
Are you using rocky in your project? Open an issue or send a PR! | ||
## Middleware layer | ||
One of the more powerful features in `rocky` is its build-in domain specific middleware. | ||
One of the more powerful features in `rocky` is its build-in domain specific middleware, based on `connect/express` middleware. | ||
@@ -255,3 +264,3 @@ The middleware layer provides a simple and consistent way to augment the proxy functionality very easily, allowing you to attach third-party middleware (also known as plugins) to cover specific tasks which acts between different phases of the proxy, for instance handling incoming/outgoing traffic. | ||
.use(function (req, res, next) { | ||
if (req.param.name === 'admin') { | ||
if (req.params.name === 'admin') { | ||
// Overwrite the target URL only for this user | ||
@@ -520,6 +529,6 @@ req.rocky.options.target = 'http://new.server.net' | ||
- **changeOrigin** `boolean` - <true/false, Default: false - **changes** the origin of the host header to the target URL | ||
- **auth** `boolean` - Basic authentication i.e. 'user:password' to compute an Authorization header. | ||
- **hostRewrite** `boolean` - rewrites the location hostname on (301/302/307/308) redirects, Default: null. | ||
- **auth** `string` - Basic authentication i.e. 'user:password' to compute an Authorization header. | ||
- **hostRewrite** `string` - rewrites the location hostname on (301/302/307/308) redirects, Default: null. | ||
- **autoRewrite** `boolean` - rewrites the location host/port on (301/302/307/308) redirects based on requested host/port. Default: false. | ||
- **protocolRewrite** `boolean` - rewrites the location protocol on (301/302/307/308) redirects to 'http' or 'https'. Default: null. | ||
- **protocolRewrite** `string` - rewrites the location protocol on (301/302/307/308) redirects to 'http' or 'https'. Default: null. | ||
- **forwardOriginalBody** `boolean` - Only valid for **forward** request. Forward the original body instead of the transformed one. | ||
@@ -526,0 +535,0 @@ - **replayOriginalBody** `boolean` - Only valid for **replay** request. Forward the original body instead of the transformed one. |
const path = require('path') | ||
const fs = require('fs') | ||
const async = require('async') | ||
const eachSeries = require('../lib/helpers').eachSeries | ||
const expect = require('chai').expect | ||
@@ -17,3 +17,3 @@ const spawn = require('child_process').spawn | ||
var files = fs.readdirSync(examplesDir) | ||
async.eachSeries(files, function (file, next) { | ||
eachSeries(files, function (file, next) { | ||
if (~ignore.indexOf(file)) return next() | ||
@@ -20,0 +20,0 @@ |
@@ -12,4 +12,4 @@ const sinon = require('sinon') | ||
const route = new Emitter | ||
const length = handler.events.length | ||
route.useFor = function () {} | ||
const length = handler.events.length | ||
@@ -16,0 +16,0 @@ handler(rocky, route) |
@@ -8,3 +8,3 @@ const http = require('http') | ||
const port = 8088 | ||
const port = 9098 | ||
@@ -11,0 +11,0 @@ suite('replay', function () { |
@@ -427,2 +427,3 @@ const fs = require('fs') | ||
res.setHeader('x-custom', '1.0') | ||
res.removeHeader('content-type') | ||
assert(req, res) | ||
@@ -437,5 +438,6 @@ next() | ||
.expect(200) | ||
.expect('Content-Type', 'application/json') | ||
.expect({ 'hello': 'world' }) | ||
.end(function () {}) | ||
.expect('{"hello":"world"}') | ||
.end(function (err) { | ||
if (err) done(err) | ||
}) | ||
@@ -445,2 +447,3 @@ function assert(req, res) { | ||
expect(res.getHeader('x-custom')).to.be.equal('1.0') | ||
expect(res.getHeader('content-type')).to.not.exist | ||
expect(res.statusCode).to.be.equal(200) | ||
@@ -785,3 +788,3 @@ expect(res.body.toString()).to.be.equal('{"hello":"world"}') | ||
expect(res.statusCode).to.be.equal(502) | ||
expect(res.body.message).to.be.equal('Route not configured') | ||
expect(res.body.message).to.match(/^Route not configured/i) | ||
done() | ||
@@ -788,0 +791,0 @@ } |
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
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
155066
11
86
3780
1026
39