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:
sudo npm install moruga -g
Run moruga without any parameters to view available options.
moruga
HTTP Example
moruga -u http://duckduckgo.com -f filters.example.js -v
- Listen for HTTP requests on all IP addresses, using port 80
- Import the filters.example.js module and load its filters array
- Proxy requests to http://duckduckgo.com[PATH_AND_QUERY_STRING]
- Print requests/responses to/from the user agent
HTTPS Example
moruga -u http://duckduckgo.com -f filters.example.js --ssl-key=server-key.pem --ssl-cert=server-cert.pem
- Listen for HTTPS requests on all IP addresses, using port 443
- Use default list of CAs, including well-known ones like Verisign
- mport the filters.example.js module and load its filters array
- Proxy requests to http://duckduckgo.com[PATH_AND_QUERY_STRING]
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:
/^short-circuit, status=(\d+)$/
/^empty-reply, wait-sec=(\d+)$/
/^truncate-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:
X-Moruga-Control: short-circuit, status=745
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:
name: 'Chunky Bacon' path: '/chunky-bacon' // Connect-compatible middleware function { res; res; // 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.
name: 'Noop' path: /^\/.+/ { console; // Pass control to the next filter in the pipeline, if any ; }
And, finally, a more complex example showing how you can trigger different behaviors from a unit-test using a custom header:
name: 'Handler for X-Moruga-Control' path: /^\/.*/ { var control = reqheaders'x-moruga-control'; if !control ; return; var match = /^short-circuit, status=/; if match var code = ; res; res; return; ; }
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:
// This is a regular Node module, so you can do anything you likevar util = ; exportsfilters = name: 'Chunky Bacon' path: '/chunky-bacon' // Connect middleware { res; res; } name: 'Breakfast' path: '/(bacon|eggs|ham|sausage|pancakes|toast|juice|milk|coffee|spam|/)+$' 'i' // Connect middleware { res; res; } name: '503 on initial auth and randomly thereafter' path: /^\/v\d+.\d+\/agent\/auth$/i { var userAgent = reqheaders'user-agent'; // Return 503 10% of the time var trigger = Math > 090; if trigger || !this_authedByAgentuserAgent this_authedByAgentuserAgent = true; res; res; else ; } _authedByAgent: {}