Skip to content
This repository has been archived by the owner on Mar 16, 2022. It is now read-only.

Commit

Permalink
feat(#1,#2): support features. refactors
Browse files Browse the repository at this point in the history
  • Loading branch information
h2non committed Jun 21, 2015
1 parent c6e997a commit b29a754
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 56 deletions.
4 changes: 4 additions & 0 deletions .codeclimate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
languages:
JavaScript: true
exclude_paths:
- "examples/*.js"
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# rocky
# rocky [![Build Status](https://api.travis-ci.org/h2non/rocky.svg?branch=master&style=flat)][travis] [![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)

Extensible and plugable HTTP migration library for [node.js](http://nodejs.org).
<img align="right" height="180" src="http://s22.postimg.org/f0jmde7o1/rocky.jpg" />

Plugable HTTP proxy and migration library for [node.js](http://nodejs.org).

`rocky` essentially acts as a reverse HTTP proxy forwaring the traffic to one or multiple backends with optional traffic replay.

Expand All @@ -10,11 +12,13 @@ It provides an elegant and fluent programmatic API with built-in features such a

## Features

- Full featured HTTP/S proxy (backed by [http-proxy](https://github.com/nodejitsu/node-http-proxy))
- Full-featured HTTP/S proxy (backed by [http-proxy](https://github.com/nodejitsu/node-http-proxy))
- Replay traffic to multiple backends
- Works as standalone HTTP/S server
- Or integrated with connect/express via middleware
- Full-featured path based routing (express based)
- Path based forwarding and replay
- Full-featured router (express-like)
- Routing support based on regexp and wildcards
- Built-in middleware layer
- HTTP traffic interceptors via events
- Fluent and elegant API
Expand Down
40 changes: 13 additions & 27 deletions lib/rocky.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
var parseUrl = require('url').parse
var router = require('router')
var http = require('http')
var https = require('https')
var httpProxy = require('http-proxy')
var Route = require('./route')
var router = require('router')
var httpProxy = require('http-proxy')
var parseUrl = require('url').parse
var Route = require('./route')
var createServer = require('./server')

module.exports = Rocky

Expand Down Expand Up @@ -53,30 +52,17 @@ Rocky.prototype.middleware = function () {
}

Rocky.prototype.listen = function (port, host) {
var handler = serverHandler.bind(this)

this.server = this.opts.ssl ?
https.createServer(this.opts.ssl, handler) :
http.createServer(handler)

this.server.listen(port, host)

var opts = { ssl: this.opts.ssl, port: port, host: host }
var handler = this.requestHandler.bind(this)
this.server = createServer(opts, handler)
return this
}

function serverHandler(req, res) {
this.requestHandler(req, res, function (err) {
if (err) {
return reply(res, 500, 'Server error: ' + err)
}
reply(res, 404, 'No route configured')
})
}

function reply(res, code, body) {
res.writeHead(code, { 'Content-Type': 'application/json' })
res.write(JSON.stringify({ message: body }))
res.end()
Rocky.prototype.close = function (cb) {
if (this.server) {
this.server.close(cb)
}
return this
}

;['get', 'post', 'delete', 'patch', 'put', 'options', 'all'].forEach(function (method) {
Expand Down
25 changes: 14 additions & 11 deletions lib/route.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
var http = require('http')
var _ = require('lodash')
var _ = require('lodash')
var http = require('http')
var Emitter = require('events').EventEmitter

module.exports = Route

function Route(path) {
this.target = null
this.path = path
this.target = null
this.replays = []
Emitter.call(this)
}
Expand All @@ -29,12 +29,13 @@ Route.prototype._handle = function (rocky) {
return function handler(req, res) {
var target = route.target || rocky.opts.target.href
var opts = _.assign({}, rocky.opts, { target: target })
var eventProxy = proxyHandler(route, res, res)

route.emit('start', opts, req, res)

// Forward the request to the main target
if (target) {
rocky.proxy.web(req, res, opts, proxyHandler('forward'))
rocky.proxy.web(req, res, opts, eventProxy('forward'))
}

// Replay the request if necessary
Expand All @@ -46,16 +47,18 @@ Route.prototype._handle = function (rocky) {
replays.forEach(function (url) {
var res = new http.ServerResponse(req)
var opts = _.assign({}, rocky.opts, { target: url })
rocky.proxy.web(req, res, opts, proxyHandler('replay'))
rocky.proxy.web(req, res, opts, eventProxy('replay'))
})
}
}

function proxyHandler(type) {
return function (err) {
if (err) {
return route.emit(type + ':error', err, req, res)
}
route.emit(type + ':success', req, res)
function proxyHandler(route, req, res) {
return function (type) {
return function (err) {
if (err) {
return route.emit(type + ':error', err, req, res)
}
route.emit(type + ':success', req, res)
}
}
}
34 changes: 34 additions & 0 deletions lib/server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
var http = require('http')
var https = require('https')
var version = require('../package.json').version

module.exports = createServer

function createServer(opts, router) {
var handler = serverHandler(router)

var server = opts.ssl
? https.createServer(opts.ssl, handler)
: http.createServer(handler)

server.listen(opts.port, opts.host)
return server
}

function serverHandler(router) {
return function (req, res) {
res.setHeader('Server', 'rocky ' + version)
router(req, res, function (err) {
if (err) {
return reply(res, 500, err)
}
reply(res, 404, 'No route configured')
})
}
}

function reply(res, code, body) {
res.writeHead(code, { 'Content-Type': 'application/json' })
res.write(JSON.stringify({ message: body }))
res.end()
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"author": "Tomas Aparicio",
"license": "MIT",
"scripts": {
"test": "./node_modules/.bin/mocha --timeout 5000 --reporter spec --ui tdd"
"test": "./node_modules/.bin/mocha --timeout 2000 --reporter spec --ui tdd"
},
"keywords": [
"http",
Expand All @@ -31,6 +31,6 @@
"chai": "^3.0.0",
"connect": "^3.4.0",
"mocha": "^2.2.5",
"nock": "^2.6.0"
"supertest": "^1.0.1"
}
}
151 changes: 139 additions & 12 deletions test/rocky.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,171 @@
const http = require('http')
const supertest = require('supertest')
const expect = require('chai').expect
const rocky = require('../')

const ports = { target: 9890, proxy: 9891 }
const ports = { target: 9890, proxy: 9891, replay: 9892 }
const baseUrl = 'http://127.0.0.1'
const proxyUrl = baseUrl + ':' + ports.proxy
const targetUrl = baseUrl + ':' + ports.target
const replayUrl = baseUrl + ':' + ports.replay
const noop = function () {}

suite('rocky', function () {
var proxy = null
var proxy, replay, server

afterEach(function () {
proxy.server.close()
beforeEach(function () {
proxy = replay = server = null
})

afterEach(function (done) {
if (replay) replay.close()
if (server) server.close()
if (proxy) proxy.server.close()
setTimeout(done, 10)
})

test('simple forward', function (done) {
proxy = rocky()
.forward('http://127.0.0.1:' + ports.target)
.forward(targetUrl)
.listen(ports.proxy)

server = createTestServer(assert)

proxy.get('/test')
http.get(proxyUrl + '/test', noop)

function assert(req, res) {
expect(req.url).to.be.equal('/test')
expect(res.statusCode).to.be.equal(200)
done()
}
})

test('forward and replay', function (done) {
proxy = rocky()
.forward(targetUrl)
.replay(replayUrl)
.listen(ports.proxy)

proxy.get('/test')

createTestServer(assert)
replay = createReplayServer(assertReplay)
server = createTestServer(assert)

http.get(targetUrl + '/test', noop)
supertest(proxyUrl)
.get('/test')
.expect(200)
.expect('Content-Type', 'application/json')
.expect({ 'hello': 'world' })
.end(done)

function assert(req, res) {
expect(req.url).to.be.equal('/test')
expect(res.statusCode).to.be.equal(200)
done()
}

function assertReplay(req, res) {
expect(req.url).to.be.equal('/test')
expect(res.statusCode).to.be.equal(204)
}
})

test('forward and replay to multiple backends', function (done) {
proxy = rocky()
.forward(targetUrl)
.replay(replayUrl)
.replay(replayUrl)
.replay(replayUrl)
.listen(ports.proxy)

proxy.get('/test')

replay = createReplayServer(assertReplay)
server = createTestServer(assert)

supertest(proxyUrl)
.get('/test')
.expect(200)
.expect('Content-Type', 'application/json')
.expect({ 'hello': 'world' })
.end(noop)

function assert(req, res) {
expect(req.url).to.be.equal('/test')
expect(res.statusCode).to.be.equal(200)
}

var asserts = 0
function assertReplay(req, res) {
expect(req.url).to.be.equal('/test')
expect(res.statusCode).to.be.equal(204)

asserts += 1
if (asserts === 3) {
done()
}
}
})

test('forward and replay payload data', function (done) {
proxy = rocky()
.forward(targetUrl)
.replay(replayUrl)
.listen(ports.proxy)

proxy.post('/test')

replay = createReplayServer(assertReplay)
server = createTestServer(assert)

supertest(proxyUrl)
.post('/test')
.send({ hello: 'world' })
.expect(200)
.expect('Content-Type', 'application/json')
.expect({ 'hello': 'world' })
.end(done)

function assert(req, res) {
expect(req.url).to.be.equal('/test')
expect(res.statusCode).to.be.equal(200)
expect(req.body).to.match(/hello/)
}

var asserts = 0
function assertReplay(req, res) {
expect(req.url).to.be.equal('/test')
expect(res.statusCode).to.be.equal(204)
expect(req.body).to.match(/hello/)
}
})

})

function createTestServer(assert) {
return createServer(ports.target, 200, assert)
}

function createReplayServer(assert) {
return createServer(ports.replay, 204, assert)
}

function createServer(port, code, assert) {
var server = http.createServer(function (req, res) {
res.writeHead(200)
res.end()
assert(req, res)
res.writeHead(code, { 'Content-Type': 'application/json' })
res.write(JSON.stringify({ 'hello': 'world' }))

var body = ''
req.on('data', function (data) {
body += data
})
req.on('end', function () {
req.body = body
assert(req, res)
res.end()
})
})
server.listen(ports.target)

server.listen(port)
return server
}

0 comments on commit b29a754

Please sign in to comment.