racker/node-moruga

Name: node-moruga

Owner: racker

Description: HTTP proxy for API unit-testing and debugging

Created: 2012-08-23 21:08:40.0

Updated: 2013-11-07 18:20:38.0

Pushed: 2012-09-21 18:44:42.0

Homepage: null

Size: 132

Language: JavaScript

GitHub Committers

UserMost Recent Commit# Commits

Other Committers

UserEmailMost Recent Commit# Commits

README

Moruga

Moruga is a spider genus, a district in Trinidad, the hottest pepper in the world, and a transparent HTTP proxy for API unit-testing and debugging.

A few things remain to be done, but Moruga is far enough along to be useful.

Installation

Moruga requires Node.js and NPM.

To install Moruga as a binary in your PATH, run this in your console:

 npm install moruga -g

Run moruga without any parameters to view available options.

ga
HTTP Example
ga -u http://duckduckgo.com -f filters.example.js -v
HTTPS Example
ga -u http://duckduckgo.com -f filters.example.js --ssl-key=server-key.pem --ssl-cert=server-cert.pem
Built-in Filters

Moruga comes with two built-in filters. The first is a request/response logger, which is enabled with the -v option on the command line. Currently, the build-in logger only outputs headers, but adding an option to write out message bodies.

The second built-in filter is a handler for the custom X-Moruga-Control header. Using this header, you can trigger specific actions for each request. This is useful for writing unit tests.

The built-in X-Moruga-Control handler recognizes the following directives:

ort-circuit, status=(\d+)$/
pty-reply, wait-sec=(\d+)$/
uncate-body, location=(one-off|beginning|middle)$/

For example, to test response handling in your code for a particular HTTP status code, include this header line in the client's request:

ruga-Control: short-circuit, status=403

To cause Moruga to drop the connection after 2 seconds without returning anything:

ruga-Control: empty-reply, wait-sec=2

Or, to return only about half the body:

ruga-Control: truncate-body, location=middle

Alternatively, use `location=one-offto return all but the last byte of the response body, or ``location=beginning``` to return an empty body.

Filter Pipeline

Moruga uses the popular Connect library to create a filter pipeline for proxied HTTP requests. Each filter contains a human-readable name, URL path to match on, and an action. A custom actions may terminate the filter pipeline and return its own response, or allow processing to continue down the pipe.

For example, if I want to short-circuit every request to '/chunky-bacon' in order to express my approval of a certain type of breakfast meat, the following filter will do the trick:


me: 'Chunky Bacon',
th: '/chunky-bacon',

 Connect-compatible middleware function
tion: function(req, res, next) {
res.writeHead(200, {'X-Short-Circuit': true});
res.end('Soooooo chunky.');

// Uncomment if you want to allow remaining filters
// to run, but usually you won't do this after
// calling res.end()

// next();


The path may be a string or a RegEx-compatible object. In the latter case, the only requirment is that the object expose a test function that returns a truthy value for a successful match.

Here is another filter that matches all URLs except the root path, logs a message, and passes control to the next filter in the pipeline, if any.


me: 'Noop',
th: /^\/.+/,

tion: function(req, res, next) {
console.log('noop');

// Pass control to the next filter in the pipeline, if any
next();


And, finally, a more complex example showing how you can trigger different behaviors from a unit-test using a custom header:


me: 'Handler for X-Moruga-Control',
th: /^\/.*/,
tion: function(req, res, next) {
var control = req.headers['x-moruga-control'];

if (!control) {
  next();
  return;
}

var match = /^short-circuit, status=(\d+)/.exec(control);

if (match) {
  var code = parseInt(match[1]);
  res.writeHead(code, {'X-Short-Circuit': true});
  res.end();
  return;
}

next();


Custom Filters module

Moruga can load custom filters from a filter module file. The module simply needs to export an array named filters, containing a list of filter objects.

Note: Filters are installed in the pipeline in the same order as they appear in the array.

An example filters module:

his is a regular Node module, so you can do anything you like
util = require('util');

rts.filters = [

name: 'Chunky Bacon',
path: '/chunky-bacon',

// Connect middleware
action: function(req, res, next) {
  res.writeHead(200, {'X-Short-Circuit': true});
  res.end('Soooooo chunky.');
}


name: 'Breakfast',
path: new RegExp('/(bacon|eggs|ham|sausage|pancakes|toast|juice|milk|coffee|spam|/)+$', 'i'),

// Connect middleware
action: function(req, res, next) {
  res.writeHead(200, {'X-Short-Circuit': true});
  res.end("Let's eat!");
}


name: '503 on initial auth and randomly thereafter',
path: /^\/v\d+.\d+\/agent\/auth$/i,
action: function(req, res, next) {
  var userAgent = req.headers['user-agent'];

  // Return 503 10% of the time
  var trigger = Math.random() > 0.90;

  if (trigger || !this._authedByAgent[userAgent]) {
    this._authedByAgent[userAgent] = true;
    res.writeHead(503, {'X-Short-Circuit': true});
    res.end();
  }
  else {
    next();
  }
},

_authedByAgent: {}



This work is supported by the National Institutes of Health's National Center for Advancing Translational Sciences, Grant Number U24TR002306. This work is solely the responsibility of the creators and does not necessarily represent the official views of the National Institutes of Health.