Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

rocky

Package Overview
Dependencies
Maintainers
1
Versions
57
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

rocky

Pluggable, versatile and middleware-oriented full featured HTTP/S proxy with traffic replay and intercept

  • 0.2.3
  • Source
  • npm
  • Socket score

Version published
Weekly downloads
1.8K
increased by32.29%
Maintainers
1
Weekly downloads
 
Created
Source

rocky Build Status Code Climate NPM Downloads

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 and more. Built for node.js/io.js. Compatible with connect/express.

rocky can be fluently used programmatically or via command-line interface.

To get started, you can take a look to the how does it work, basic usage, middleware layer and examples

Requires node.js +0.12 or io.js +1.6

Contents

Features

  • Full-featured HTTP/S proxy (backed by http-proxy)
  • Replay traffic to multiple backends
  • Able to run as standalone HTTP/S server
  • Easily integrable with connect/express via middleware
  • Full-featured built-in router with regexp and params matching
  • Hierarchial router configuration
  • Hierarchial middleware layer (supports multiple hooks)
  • Able to capture traffic as interceptor pattern
  • Built-in traffic sniffer and transformer for request/response payloads
  • Built-in load balancer
  • Hierarchical configuration
  • Compatible with most of the existing connect/express middleware
  • Fluent, elegant and evented programmatic API
  • Simple command-line interface with declarative configuration file

When rocky could be useful?

  • As HTTP proxy for progressive migrations (e.g: APIs)
  • As HTTP traffic interceptor transforming the request/response on-the-fly
  • As intermediate HTTP proxy adapter for external services
  • As HTTP API gateway
  • As standard reverse HTTP proxy with powerful routing
  • As security proxy layer
  • As HTTP load balancer with programmatic control and zero-downtime
  • Replaying traffic to one or multiple backend
  • As SSL terminator proxy
  • For A/B testing
  • As intermediate test server intercepting and generating random/fake responses
  • And whatever a programmatic HTTP proxy could be useful to

Installation

npm install rocky --save

For command-line interface usage, install it as global package:

npm install -g rocky

Standalone binaries

Packaged using nar. Shipped with node.js 0.12.7

Usage
chmod +x rocky-0.2.1-linux-x64.nar
./rocky-0.2.1-linux-x64.nar exec --port 3000 --config rocky.toml

Introduction

Motivation

Migrating systems if not a trivial thing, and it's even more complex if we're talking about production systems that require high availability. Taking care of consistency and public interface contract should be a premise in most cases.

rocky was initially created to become an useful tool for assisting during a backend migration strategy, however it could be useful for many other scenarios.

Design

rocky design is driven by keeping versatility and extensibility in mind. The main goal is to remain it with a small core and codebase, hosting just the proper responsability and necessary built-in features, and making more efforts providing ways for extensibility. That level extensibility can be covered via its middleware layer, which is the core and more powerful feature of rocky.

The relevant difference between the middleware layer and a common event bus (which is very common in asynchronous programming) is the control flow capability. Via the middleware you can completely rely on a consistent control flow when performing some actiong with the HTTP traffic flow, continuing or stoping it accordingly. This approach allows you to plug in intermediate jobs with custom logic beetwen different stages of the HTTP flow live cycle.

Stability

rocky is relative young but production focused package. Version 0.1.x was wrote during my free time in less than 10 days (mostly at night during the weekend), therefore it could be considered in beta stage.

Version 0.2.x introduced significant improvements such as a more consistent API and a new hierarchical middleware layer. This version is focused on stability and production use, however it's only recommended to use it in non-hostile environments for now.

Versions

  • 0.1.x - First version. Initially released at 25.06.2015. Beta
  • 0.2.x - Released at 07.07.2015. Production focused version.

How does it work?

rocky can be useful in multiple scenarios, but a common and representative use case scenario could be the following:

         |==============|
         | The Internet |
         |==============|
               ||||
         |==============|
         |  HTTP proxy  |
         |~~~~~~~~~~~~~~|
         | Rocky Router |
         |~~~~~~~~~~~~~~|
         |  Middleware  |
         |==============|
            ||      |
  (duplex) //        \ (one-way)
          //          \
         //            \
   /----------\   /----------\    /----------\
   |  target  |   | replay 1 | -> | replay 2 | (*N)
   \----------/   \----------/    \----------/

Middleware layer

One of the more powerful features in rocky is its build-in middleware layer. rocky was designed with a main core idea: augment by default.

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.

rocky middleware layer has the same interface as connect/express middleware, and it's mostly compatible with existent middleware (see express example).

Hierarchies

rocky supports multiple middleware hierarchies:

  • global - Dispached on every incoming request matched by the router
  • route - Dispached only per route scope

Types of middleware

rocky introduces multiple types of middleware layers based on the same interface and behavior of connect/express middleware. This was introduced in order to achieve in a more responsive way multiple traffic flows in the specific scope and behavior nature of a programmatic HTTP proxy with traffic replay.

Those flows are intrinsicly correlated but might be handled in a completely different way. The goal is to allowing you to handle them accordingly, acting in the middle of those phases to augment some functionality or react to some event with better accuracy.

Supported types of middleware:

router
  • Scope: global
  • Description: Dispatched on every matched route.
  • Notation: .use([path], function (req, res, next))
forward
  • Scope: global, route
  • Description: Dispached before forwarding an incoming request.
  • Notation: .useForward(function (req, res, next))
replay
  • Scope: global, route
  • Description: Dispached before starting each replay request.
  • Notation: .useReplay(function (req, res, next))
param
  • Scope: global
  • Description: Dispached on every matched param on any route.
  • Notation: .useParam(function (req, res, next))

Middleware flow

Middleware functions are always executed in FIFO order. The following diagram represents the internal incoming request flow and how the different middleware layers are involved on it:

↓    ( Incoming request )   ↓
↓            |||            ↓
↓      ----------------     ↓
↓      |    Router    |     ↓ --> Match a route, dispatching its middleware if required
↓      ----------------     ↓
↓            |||            ↓
↓    ---------------------  ↓
↓    | Global middleware |  ↓ --> Dispatch on every incoming request (Global)
↓    ---------------------  ↓
↓            |||            ↓
↓           /   \           ↓
↓         /       \         ↓
↓       /           \       ↓
↓ [ Forward ]    [ Replay ] ↓ --> Dispatch both middleware in separated flows (Global, Route)
↓      \             /      ↓
↓       \           /       ↓
↓        \         /        ↓
↓     ------------------    ↓
↓     | HTTP dispacher |    ↓ --> Send requests over the network, separately
↓     ------------------    ↓

Middleware API

Middleware behavior and interface are the same like connect/express, so you can create middleware as you already know with the notation function(req, res, next)

rocky exposes as a sort of inversion of control in every http.ClientRequest object the following fields:

  • req.rocky object
    • .options object - Expose the configuration options for the current request.
    • .proxy Rocky - Expose the rocky instance. Use only for hacking purposes!
    • .route Route - Expose the current running route. Only available in route type middleware

This provides you way to extend or modify specific values from the middleware layer without having side-effects, for instance replacing the server target URL, like in the following example:

rocky()
  .get('/users/:name')
  .forward('http://old.server.net')
  .use(function (req, res, next) {
    if (req.param.name === 'admin') {
      // Overwrite the target URL only for this user
      req.rocky.options.target = 'http://new.server.net'
    }
    next()
  })

Third-party middleware

  • consul - Dynamic service discovery and balancing using Consul
  • vhost - vhost based proxy routing for rocky
  • version - HTTP API version based routing (uses http-version)

Note that you can use any other existent middleware plug in rocky as part of your connect/express app.

Additionally, rocky provides some built-in middleware functions that you can plug in different types of middleware.

Command-line

Start rocky HTTP proxy server
Usage: rocky [options]

Options:
  --help, -h     Show help                                             [boolean]
  --config, -c   File path to TOML config file
  --port, -p     rocky HTTP server port
  --forward, -f  Default forward server URL
  --replay, -r   Define a replay server URL
  --route, -t    Define one or multiple routes, separated by commas
  --key, -k      Path to SSL key file
  --cert, -e     Path to SSL certificate file
  --secure, -s   Enable SSL certification validation
  --balance, -b  Define server URLs to balance between, separated by commas
  --debug, -d    Enable debug mode                                     [boolean]
  -v, --version  Show version number                                   [boolean]

Examples:
  rocky -c rocky.toml \
  -f http://127.0.0.1:9000 \
  -r http://127.0.0.1
Examples

Passing the config file:

rocky --config rocky.toml --port 8080

Reading config from stdin:

cat rocky.toml | rocky --port 8080

Transparent rocky.toml file discovery in current and higher directories:

rocky --port 8080

Alternatively rocky can find the config file passing the ROCKY_CONFIG environment variable:

ROCKY_CONFIG=path/to/rocky.toml rocky --port 8080

Or for simple configurations you can setup a proxy without a config file, defining the routes via flag:

rocky --port --forward http://server --route "/download/*, /images/*, /*"

Configuration

Supported params

  • forward string - Default forward URL
  • debug boolean - Enable debug mode. Default false
  • target string - <url string to be parsed with the url module
  • replay array<string> - Optional replay server URLs. Via API you should use the replay() method
  • balance array<url> - Define the URLs to balance. Via API you should use the balance() method
  • forward string - url string to be parsed with the url module
  • timeout number - Timeout for request socket
  • proxyTimeout number - Timeout for proxy request socket
  • agent object - object to be passed to http(s).request. See node.js https docs
  • ssl object - object to be passed to https.createServer()
    • cert string - Path to SSL certificate file
    • key string - Path to SSL key file
  • ws boolean - true/false, if you want to proxy websockets
  • xfwd boolean - true/false, adds x-forward headers
  • secure boolean - true/false, verify SSL certificate
  • toProxy boolean - true/false, explicitly specify if we are proxying to another proxy
  • prependPath boolean - true/false, Default: true - specify whether you want to prepend the target's path to the proxy path
  • ignorePath boolean - true/false, Default: false - specify whether you want to ignore the proxy path of the incoming request
  • localAddress boolean - <Local interface string to bind for outgoing connections
  • 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.
  • 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.
  • forwardOriginalBody boolean - Only valid for forward request. Forward the original body instead of the transformed one.
  • replayOriginalBody boolean - Only valid for replay request. Forward the original body instead of the transformed one.
  • router object - Specific router params
    • strict boolean - When false trailing slashes are optional (default: false)
    • caseSensitive boolean - When true the routing will be case sensitive. (default: false)
    • mergeParams boolean - When true any req.params passed to the router will be merged into the router's req.params. (default: false)

Configuration file

Default configuration file name: rocky.toml

The configuration file must be declared in TOML language

port = 8080
forward = "http://google.com"
replay = ["http://duckduckgo.com"]

[ssl]
cert = "server.crt"
key  = "server.key"

[/users/:id]
method = "all"
forward = "http://new.server"

[/oauth]
method = "all"
forward = "http://auth.server"

[/*]
method = "GET"
forward = "http://old.server"

[/download/:file]
method = "GET"
timeout = 5000
balance = ["http://1.file.server", "http://2.file.server"]

[/photo/:name]
method = "GET"
[[replay]]
  target = "http://old.server"
  forwardHost = true
[[replay]]
  target = "http://backup.server"

Programmatic API

Usage

Example using Express

var rocky = require('rocky')
var express = require('express')

// Set up the express server
var app = express()
// Set up the rocky proxy
var proxy = rocky()

// Default proxy config
proxy
  .forward('http://new.server')
  .replay('http://old.server')
  .replay('http://log.server')
  .options({ forwardHost: true })

// Configure the routes to forward/replay
proxy
  .get('/users/:id')

proxy
  .get('/download/:file')
  .balance(['http://1.file.server', 'http://2.file.server'])

// Plug in the rocky middleware
app.use(proxy.middleware())

// Old route (won't be called since it will be intercepted by rocky)
app.get('/users/:id', function () { /* ... */ })

app.listen(3000)

Example using the built-in HTTP server

var rocky = require('rocky')

var proxy = rocky()

// Default proxy config
proxy
  .forward('http://new.server')
  .replay('http://old.server', { replayOriginalBody: true })
  .options({ forwardHost: true })
  .on('proxy:error', function (err) {
    console.error('Error:', err)
  })
  .on('proxyReq', function (proxyReq, req, res, opts) {
    console.log('Proxy request:', req.url, 'to', opts.target)
  })
  .on('proxyRes', function (proxyRes, req, res) {
    console.log('Proxy response:', req.url, 'with status', res.statusCode)
  })

// Configure the routes to forward/replay
proxy
  .get('/users/:id')
  // Overwrite the path
  .toPath('/profile/:id')
  // Add custom headers
  .headers({
    'Authorization': 'Bearer 0123456789'
  })

proxy
  .get('/search')
  // Overwrite the forward URL for this route
  .forward('http://another.server')
  // Use a custom middleware for validation purposes
  .use(function (req, res, next) {
    if (req.headers['Autorization'] !== 'Bearer 012345678') {
      res.statusCode = 401
      return res.end()
    }
    next()
  })
  // Intercept and transform the response body before sending it to the client
  .transformResponseBody(function (req, res, next) {
    // Get the body buffer and parse it (assuming it's a JSON)
    var body = JSON.parse(res.body.toString())

    // Compose the new body
    var newBody = JSON.stringify({ salutation: 'hello ' + body.hello })

    // Send the new body in the request
    next(null, newBody)
  })

proxy.listen(3000)

For more usage cases, take a look at the examples

rocky([ options ])

Creates a new rocky instance with the given options.

You can pass any of the allowed params at configuration level and any supported http-proxy options

rocky#forward(url)

Aliases: target, forwardTo

Define a default target URL to forward the request

rocky#replay(url, [ opts ])

Alias: replayTo

Add a server URL to replay the incoming request

opts param provide specific replay options, overwritting the parent options.

rocky#options(options)

Define/overwrite rocky server options. You can pass any of the supported options by http-proxy.

rocky#use([ path ], ...middleware)

Use the given middleware to handle all http methods on the given path, defaulting to the root path.

rocky#useParam(param, ...middleware)

Alias: param()

Maps the specified path parameter name to a specialized param-capturing middleware. The middleware stack is the same as .use().

rocky#useReplay(...middleware)

Use a middleware for all the incoming traffic in the HTTP replay phase. This middleware stack can be useful to differ between forward/replay traffic, applying separated flows of middleware.

rocky#useForward(...middleware)

Use a middleware for all the incoming traffic only for the HTTP request forward phase. For most cases you will only use .use(), but for particular modifications only for the forwarded traffic, this middleware can be useful.

rocky#useFor(name, ...middleware)

Use a custom middleware for a specific phase. Supported phase names are: forward, 'replay'.

This method is used internally, however it's also public since it could be useful for dynamic middleware configurations instead of using the shortcut methods such as: useReplay or useForward.

rocky#balance(...urls)

Define a set of URLs to balance between with a simple round-robin like scheduler.

rocky#on(event, handler)

Subscribe to a proxy event. See support events here

rocky#once(event, handler)

Remove an event by its handler function. See support events here

rocky#off(event, handler)

Remove an event by its handler function. See support events here

rocky#removeAllListeners(event)

Remove all the subscribers to the given event. See support events here

rocky#middleware()

Return: Function(req, res, next)

Return a connect/express compatible middleware

rocky#requestHandler(req, res, next)

Raw HTTP request/response handler.

rocky#listen(port, [ host ])

Starts a HTTP proxy server in the given port

rocky#close([ callback ])

Close the HTTP proxy server, if exists. Shortcut to rocky#server.close(cb)

rocky#all(path, [ ...middleware ])

Return: Route

Add a route handler for the given path for all HTTP methods

rocky#get(path, [ ...middleware ])

Return: Route

Configure a new route the given path with GET method

rocky#post(path, [ ...middleware ])

Return: Route

Configure a new route the given path with POST method

rocky#put(path, [ ...middleware ])

Return: Route

Configure a new route the given path with PUT method

rocky#delete(path, [ ...middleware ])

Return: Route

Configure a new route the given path with DELETE method

rocky#patch(path, [ ...middleware ])

Return: Route

Configure a new route the given path with PATCH method

rocky#head(path, [ ...middleware ])

Return: Route

Configure a new route the given path with HEAD method

rocky#router

Internal router instance

rocky#server

HTTP/HTTPS server instance. Only present if listen() was called starting the built-in server.

Route(path)

route#forward(url)

Aliases: target, forwardTo

Overwrite forward server for the current route.

route#replay(url, [ opts ])

Alias: replyTo

Overwrite replay servers for the current route.

opts param provide specific replay options, overwritting the parent options.

route#balance(urls)

Define a set of URLs to balance between with a simple round-robin like scheduler. urls param must be an array of strings.

route#reply(status, [ headers, body ])

Shortcut method to intercept and reply the incoming request. If used, body param must be a string or buffer

route#toPath(url, [ params ])

Overwrite the request path, defining additional optional params.

route#headers(headers)

Define or overwrite request headers for the current route.

route#host(host)

Overwrite the Host header value when forward the request.

route#redirect(url)

Redirect the incoming request for the current route.

route#bufferBody([ filter ])

Intercept and cache in a buffer the request payload data. Body will be exposed in req.body.

Note: use it only for small payloads

route#transformRequest(middleware, [ filter ])

Alias: transformRequestBody()

This method implements a non-instrusive native http.IncommingMessage stream wrapper that allow you to intercept and transform the request body received from the client before sending it to the target server.

The middleware argument must a function which accepts the following arguments: function(req, res, next) The filter arguments is optional and it can be a string, regexp or function(req) which should return boolean if the request passes the filter. The default check value by string or regexp test is the Content-Type header.

In the middleware function must call the next function, which accepts the following arguments: err, newBody, encoding You can see an usage example here.

Caution: using this middleware could generate in some scenarios negative performance side-effects, since the whole payload data will be buffered in the heap until it's finished. Don't use it if you need to handle large payloads.

The body will be exposed as raw Buffer or String on both properties body and originalBody in http.ClientRequest:

rocky
  .post('/users')
  .transformRequest(function (req, res, next) {
    // Get the body buffer and parse it (assuming it's a JSON)
    var body = JSON.parse(req.body.toString())

    // Compose the new body
    var newBody = JSON.stringify({ salutation: 'hello ' + body.hello })

    // Set the new body
    next(null, newBody, 'utf8')
  }, function (req) {
    // Custom filter
    return /application\/json/i.test(req.headers['content-type'])
  })
route#transformResponse(middleware, [ filter ])

Alias: transformResponseBody()

This method implements a non-instrusive native http.RequestResponse stream wrapper that allow you to intercept and transform the response body received from the target server before sending it to the client.

The middleware argument must a function which accepts the following arguments: function(req, res, next) The filter arguments is optional and it can be a string, regexp or function(res) which should return boolean if the request passes the filter. The default check value by string or regexp test is the Content-Type header.

In the middleware function must call the next function, which accepts the following arguments: err, newBody, encoding You can see an usage example here.

Caution: using this middleware could generate in some scenarios negative performance side-effects since the whole payload data will be buffered in the heap until it's finished. Don't use it if you need to handle large payloads.

The body will be exposed as raw Buffer or String on both properties body and originalBody in http.ClientResponse:

rocky
  .post('/users')
  .transformResponse(function (req, res, next) {
    // Get the body buffer and parse it (assuming it's a JSON)
    var body = JSON.parse(res.body.toString())

    // Compose the new body
    var newBody = JSON.stringify({ salutation: 'hello ' + body.hello })

    // Set the new body
    next(null, newBody, 'utf8')
  }, function (res) {
    // Custom filter
    return /application\/json/i.test(res.getHeader('content-type'))
  })
route#options(options)

Overwrite default proxy options for the current route. You can pass any supported option by http-proxy

route#use(...middleware)

Use a middleware for the incoming traffic for the current route for both replay/forward phases.

route#useReplay(...middleware)

Use a middleware for current route incoming traffic in the HTTP replay phase. This middleware stack can be useful to differ between forward/replay traffic, applying separated flows of middleware.

route#useForward(...middleware)

Use a middleware for current route incoming traffic only for the HTTP request forward phase. For most cases you will only use .use(), but for particular modifications only for the forwarded traffic, this middleware can be useful.

route#useFor(name, ...middleware)

This method is used internally, however it's also public since it could be useful for dynamic middleware configurations instead of using the shortcut methods such as: useReplay or useForward.

route#on(event, ...handler)

Subscribes to a specific event for the given route. Useful to incercept the status or modify the options on-the-fly

Events
  • proxyReq opts, proxyReq, req, res - Fired when the request forward starts
  • proxyRes opts, proxyRes, req, res - Fired when the target server respond
  • proxy:error err, req, res - Fired when the proxy request fails
  • route:error err, req, res - Fired when cannot forward/replay the request or middleware error
  • replay:start params, opts, req - Fired before a replay request starts
  • replay:error opts, err, req, res - Fired when the replay request fails
  • server:error err, req, res - Fired on server middleware error. Only available if running as standalone HTTP server
  • route:missing req, res - Fired on missing route. Only available if running as standalone HTTP server

For more information about events, see the events fired by http-proxy

route#once(event, ...handler)

Subscribes to a specific event for the given route, and unsubscribes after dispatched

route#off(event, handler)

Remove an event by its handler function in the current route

rocky.create(config)

Create a standalone rocky server with the given config options. See the supported config fields

var config = {
  'forward': 'http://google.com',
  '/search': {
    method: 'GET',
    forward: 'http://duckduckgo.com'
    replay: ['http://bing.com', 'http://yahoo.com']
  },
  '/users/:id': {
    method: 'all'
  },
  '/*': {
    method: 'all',
    forward: 'http://bing.com'
  }
}

rocky.create(config)

rocky.middleware

Expose the built-in internal middleware functions.

You can reuse them as standard middleware in diferent ways, like this:

rocky()
  .all('/*')
  .use(rocky.middleware.headers({
    'Authorization': 'Bearer 0123456789'
  }))
  .useReplay(rocky.middleware.host('replay.server.net'))
rocky.middleware.requestBody(middleware)

Intercept and optionally transform/replace the request body before forward it to the target server.

See rocky#transformRequestBody for more details.

rocky.middleware.responseBody(middleware)

Intercept and optionally transform/replace the response body from the server before send it to the client.

See rocky#transformResponseBody for more details.

rocky.middleware.toPath(path, [ params ])

Overrites the request URL path of the incoming request before forward/replay it.

rocky.middleware.headers(headers)

Add/extend custom headers to the incoming request before forward/replay it.

rocky.middleware.host(host)

Overwrite the Host header before forwarding/replaying the request. Useful for some scenarios (e.g Heroku).

rocky.middleware.reply(status, [ headers, body ])

Shortcut method to reply the intercepted request from the middleware, with optional headers and body data.

rocky.middleware.redirect(url)

Shortcut method to redirect the current request.

rocky.httpProxy

Accessor for the http-proxy API

rocky.VERSION

Current rocky package semver

Special Thanks

  • http-proxy package creators and maintainers
  • router package creators and maintainers

License

MIT - Tomas Aparicio

Keywords

FAQs

Package last updated on 14 Jul 2015

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc