Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stubby should have an option to send response headers back unmolested #86

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CONTRIBUTERS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

[JJ4TH](https://github.com/jj4th)

[Aparna Sathyanarayan](https://github.com/aparna-sath)

# Special Thanks

Alexander Zagniotov
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Some systems require you to `sudo` before running services on certain ports (lik

```
stubby [-a <port>] [-c <file>] [-d <file>] [-h] [-k <file>] [-l <hostname>] [-p <file>] [-q]
[-s <port>] [-t <port>] [-v] [-w]
[-s <port>] [-t <port>] [-v] [-w] [-q] [-r]

-a, --admin <port> Port for admin portal. Defaults to 8889.
-c, --cert <file> Certificate file. Use with --key.
Expand All @@ -67,6 +67,7 @@ stubby [-a <port>] [-c <file>] [-d <file>] [-h] [-k <file>] [-l <hostname>] [-p
-t, --tls <port> Port for https stubs portal. Defaults to 7443.
-v, --version Prints stubby's version number.
-w, --watch Auto-reload data file when edits are made.
-r, --dittoResponse Ditto the response back to the client, without any modifications.
```

When used from the command-line, `stubby` responds to the `SIGHUP` signal to reload its configuration.
Expand Down Expand Up @@ -671,6 +672,7 @@ What can I do with it, you ask? Read on!
* `quiet`: defaults to `true`. Pass in `false` to have console output (if available)
* `_httpsOptions`: additional options to pass to the [underlying tls
server](http://nodejs.org/api/tls.html#tls_tls_createserver_options_secureconnectionlistener).
* `dittoResponse`: tells stubby to send the response back without lower-casing the headers
* `callback`: takes one parameter: the error message (if there is one), undefined otherwise

#### start([callback])
Expand Down
5 changes: 5 additions & 0 deletions src/console/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ var options = [{
name: 'watch',
flag: 'w',
description: 'Auto-reload data file when edits are made.'
}, {
name: 'dittoResponse',
flag: 'r',
default: false,
description: 'Maintain casing for response headers.'
}];

function help (go) {
Expand Down
1 change: 1 addition & 0 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ Stubby.prototype.start = function (o, cb) {

if (errors) { return callback(errors); }
if (options.datadir != null) { self.endpoints.datadir = options.datadir; }
if (options.dittoResponse != null) { self.endpoints.dittoResponse = options.dittoResponse; }

self.endpoints.create(options.data, onEndpointLoaded);

Expand Down
19 changes: 12 additions & 7 deletions src/models/endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ var http = require('http');
var q = require('querystring');
var out = require('../console/out');

function Endpoint (endpoint, datadir) {
function Endpoint (endpoint, datadir, dittoResponse) {
if (endpoint == null) { endpoint = {}; }
if (datadir == null) { datadir = process.cwd(); }
if (dittoResponse == null) { dittoResponse = false; }

Object.defineProperty(this, 'datadir', { value: datadir });

this.request = purifyRequest(endpoint.request);
this.response = purifyResponse(this, endpoint.response);
this.response = purifyResponse(this, endpoint.response, dittoResponse);
this.hits = 0;
}

Expand Down Expand Up @@ -117,7 +118,7 @@ function purifyRequest (incoming) {
outgoing = {
url: incoming.url,
method: incoming.method == null ? 'GET' : incoming.method,
headers: purifyHeaders(incoming.headers),
headers: purifyHeaders(incoming.headers, true),
query: incoming.query,
file: incoming.file,
post: incoming.post,
Expand All @@ -133,7 +134,7 @@ function purifyRequest (incoming) {
return outgoing;
}

function purifyResponse (me, incoming) {
function purifyResponse (me, incoming, dittoResponse) {
var outgoing = [];

if (incoming == null) { incoming = []; }
Expand All @@ -145,7 +146,7 @@ function purifyResponse (me, incoming) {
outgoing.push(record(me, response));
} else {
outgoing.push(pruneUndefined({
headers: purifyHeaders(response.headers),
headers: purifyHeaders(response.headers, !dittoResponse),
status: parseInt(response.status, 10) || 200,
latency: parseInt(response.latency, 10) || null,
file: response.file,
Expand All @@ -157,13 +158,17 @@ function purifyResponse (me, incoming) {
return outgoing;
}

function purifyHeaders (incoming) {
function purifyHeaders (incoming, shouldLowerCase) {
var prop;
var outgoing = {};

for (prop in incoming) {
if (Object.prototype.hasOwnProperty.call(incoming, prop)) {
outgoing[prop.toLowerCase()] = incoming[prop];
if (shouldLowerCase) {
outgoing[prop.toLowerCase()] = incoming[prop];
} else {
outgoing[prop] = incoming[prop];
}
}
}

Expand Down
3 changes: 2 additions & 1 deletion src/models/endpoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ function Endpoints (data, callback, datadir) {
if (callback == null) { callback = noop; }
if (datadir == null) { datadir = process.cwd(); }

this.dittoResponse = false;
this.datadir = datadir;
this.db = {};
this.lastId = 0;
Expand All @@ -27,7 +28,7 @@ Endpoints.prototype.create = function (data, callback) {
if (callback == null) { callback = noop; }

function insert (item) {
item = new Endpoint(item, self.datadir);
item = new Endpoint(item, self.datadir, self.dittoResponse);
item.id = ++self.lastId;
self.db[item.id] = item;
callback(null, clone(item));
Expand Down
21 changes: 21 additions & 0 deletions test/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,26 @@ describe('CLI', function () {
});
});

describe('-r, --dittoResponse', function () {
it('should return default if no flag provided', function () {
const expected = false;
const actual = sut.getArgs([]);
assert.strictEqual(actual.dittoResponse, expected);
});

it('should return supplied value when provided', function () {
const expected = true;
const actual = sut.getArgs(['-r', expected]);
assert.strictEqual(actual.dittoResponse, expected);
});

it('should return supplied value when provided with full flag', function () {
const expected = true;
const actual = sut.getArgs(['--dittoResponse', expected]);
assert.strictEqual(actual.dittoResponse, expected);
});
});

describe('getArgs', function () {
it('should gather all arguments', function () {
var actual;
Expand All @@ -247,6 +267,7 @@ describe('CLI', function () {
quiet: true,
watch: filename,
datadir: process.cwd(),
dittoResponse: false,
help: undefined, // eslint-disable-line no-undefined
version: (require('../package.json')).version
};
Expand Down
27 changes: 27 additions & 0 deletions test/endpoint.js
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,33 @@ describe('Endpoint', function () {
assert.deepStrictEqual(actual.request.headers, expected.request);
});

it('should not lower case response headers properties if dittoResponse is true', function () {
var actual, expected;
this.data.request = {
headers: {
'Content-Type': 'application/json'
}
};
this.data.response = {
headers: {
'Content-Type': 'application/json'
}
};
expected = {
request: {
'content-type': 'application/json'
},
response: {
'Content-Type': 'application/json'
}
};

actual = new Endpoint(this.data, null, true);

assert.deepStrictEqual(actual.response[0].headers, expected.response);
assert.deepStrictEqual(actual.request.headers, expected.request);
});

it('should define multiple headers with same name', function () {
var actual, expected;
this.data.request = {
Expand Down