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

Adding support to ignore user specified HTTP headers when searching route handlers #161

Merged
merged 3 commits into from
Apr 22, 2017
Merged
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
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,11 @@ For HTTP headers such as Authorization, you may want Drakov to return them in th

Drakov includes many headers by default: `Origin, X-Requested-With, Content-Type, Accept` when CORS is enabled.

## Ignore Headers

In cases where strict HTTP headers matching against API blueprints is not necessary, you can use the `--ignoreHeader` argument:

`drakov -f "../com/foo/contracts/*.md" --ignoreHeader Cookie --ignoreHeader Authorization`

## Using as a Node.js module

Expand Down
3 changes: 3 additions & 0 deletions lib/arguments/arguments.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ module.exports = {
},
debugMode: {
description: 'Enables DEBUG mode. Mismatch requests will be dumped'
},
ignoreHeader: {
description: 'Ignore the HTTP header in API blueprints'
}

};
37 changes: 24 additions & 13 deletions lib/content.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,15 @@ function getBodyContent(req, parseToJson){
return body;
}

function areContentTypesSame(httpReq, specReq) {
var actual = getMediaTypeFromHttpReq(httpReq);
var expected = getMediaTypeFromSpecReq(specReq);

var result = !expected || actual === expected;
logger.log('[MATCHING]'.yellow,'by request content type:', expected, 'actual:', actual, logger.stringfy(result));
return result;
}

exports.contentTypeComparator = function(specA) {

function hasContentTypeHeader(spec){
Expand All @@ -66,15 +75,6 @@ exports.contentTypeComparator = function(specA) {
return !hasContentTypeHeader(specA)? 1 : -1;
};

exports.areContentTypesSame = function(httpReq, specReq) {
var actual = getMediaTypeFromHttpReq(httpReq);
var expected = getMediaTypeFromSpecReq(specReq);

var result = !expected || actual === expected;
logger.log('[MATCHING]'.yellow,'by request content type:', expected, 'actual:', actual, logger.stringfy(result));
return result;
};

exports.matchesBody = function(httpReq, specReq) {
if (!specReq) {
return true;
Expand Down Expand Up @@ -106,13 +106,23 @@ exports.matchesSchema = function(httpReq, specReq) {
return result;
};

exports.matchesHeader = function(httpReq, specReq) {
exports.matchesHeader = function(httpReq, specReq, ignoreHeaders) {
if (!specReq || !specReq.headers){
return true;
}

function removeContentTypeHeader(header){
return header.name.toLowerCase() !== 'content-type';
ignoreHeaders = (ignoreHeaders && ignoreHeaders.map(function (header) {
return header.toLowerCase();
})) || [];

function shouldIgnoreHeader(headerName){
return ignoreHeaders.indexOf(headerName.toLowerCase()) > -1;
}

function headersForEvaluation(header) {
return header.name &&
(header.name.toLowerCase() !== 'content-type' &&
!shouldIgnoreHeader(header.name));
}

function containsHeader( header ){
Expand All @@ -124,5 +134,6 @@ exports.matchesHeader = function(httpReq, specReq) {
return result;
}

return specReq.headers.filter(removeContentTypeHeader).every(containsHeader);
return specReq.headers.filter(headersForEvaluation).every(containsHeader) &&
(shouldIgnoreHeader('content-type') || areContentTypesSame(httpReq, specReq));
};
17 changes: 4 additions & 13 deletions lib/handler-filter.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
var content = require('./content');
var queryComparator = require('./query-comparator');

var filterContentType = function (req) {
var filterRequestHeader = function (req, ignoreHeaders) {
return function (handler) {
return content.areContentTypesSame(req, handler.request);
};
};

var filterRequestHeader = function (req) {
return function (handler) {
return content.matchesHeader(req, handler.request);
return content.matchesHeader(req, handler.request, ignoreHeaders);
};
};

Expand All @@ -25,7 +19,7 @@ var filterSchema = function (req) {
};
};

exports.filterHandlers = function (req, handlers) {
exports.filterHandlers = function (req, handlers, ignoreHeaders) {
if (handlers) {
var filteredHandlers;

Expand All @@ -39,9 +33,7 @@ exports.filterHandlers = function (req, handlers) {
handlers.sort(queryComparator.queryParameterComparator);
}

filteredHandlers = handlers
.filter(filterRequestHeader(req))
.filter(filterContentType(req));
filteredHandlers = handlers.filter(filterRequestHeader(req, ignoreHeaders));

var matchRequestBodyHandlers = filteredHandlers.filter(filterRequestBody(req));

Expand All @@ -58,4 +50,3 @@ exports.filterHandlers = function (req, handlers) {

return null;
};

4 changes: 3 additions & 1 deletion lib/middleware/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ var bootstrapMiddleware = function(app, argv) {

exports.init = function(app, argv, cb) {
bootstrapMiddleware(app, argv);
var options = {sourceFiles: argv.sourceFiles, autoOptions: argv.autoOptions};
var options = {sourceFiles: argv.sourceFiles,
autoOptions: argv.autoOptions,
ignoreHeaders: argv.ignoreHeader};
routeHandlers(options, function(err, middleware) {
cb(err, middleware);
});
Expand Down
3 changes: 2 additions & 1 deletion lib/middleware/route-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ module.exports = function(options, cb) {
var match = regex.exec(req.path);
logger.log('[MATCHING]'.yellow, 'by url pattern:', urlPattern.yellow, logger.stringfy(match));
if (match) {
handler = filter.filterHandlers(req, routeMap[urlPattern].methods[req.method.toUpperCase()]);
var handlers = routeMap[urlPattern].methods[req.method.toUpperCase()];
handler = filter.filterHandlers(req, handlers, options.ignoreHeaders);
}
});

Expand Down
23 changes: 13 additions & 10 deletions test/api/debugger-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,20 +37,23 @@ describe('Debug Mode', function(){
});

it('should respond with spec response', function(done){
var expectedResponsePayload = {
originalUrl:'/api/things-not-found',
body: {'key1':'value1','key2':2},
method: 'POST',
headers:{'host':'localhost:3003','accept-encoding':'gzip, deflate','user-agent':'node-superagent/3.5.1','content-type':'application/json','content-length':'26','connection':'close'},
query:{}
};

function matchingReturnedBody(expectedBody) {
return function (response) {
var actualBodyString = JSON.stringify(response.body.body);
var expectedBodyString = JSON.stringify(expectedBody);
if (actualBodyString !== expectedBodyString) {
throw new Error('Expected: ' + expectedBodyString + ', but got: ' + actualBodyString);
}
};
}

var content = { key1: 'value1', key2: 2 };
request.post('/api/things-not-found')
.set('Content-type', 'application/json')
.send({ key1: 'value1', key2: 2 })
.send(content)
.expect(404)
.expect('Content-type', 'application/json; charset=utf-8')
.expect(expectedResponsePayload)
.expect(matchingReturnedBody(content))
.end(helper.endCb(done));

});
Expand Down
23 changes: 22 additions & 1 deletion test/api/headers-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ var request = helper.getRequest();
describe('HEADERS', function(){

before(function (done) {
helper.drakov.run({sourceFiles: 'test/example/md/headers.md'}, done);
helper.drakov.run({sourceFiles: 'test/example/md/headers.md',
ignoreHeader: ['Cookie']}, done);
});

after(function (done) {
Expand Down Expand Up @@ -32,6 +33,26 @@ describe('HEADERS', function(){
});
});

describe('/ignore_headers', function(){

describe('GET', function(){
it('should respond with HTTP 200 when header does not exist', function(done){
request.get('/ignore_headers')
.expect(200)
.end(helper.endCb(done));
});
});

describe('GET', function(){
it('should respond with HTTP 200 when header does not match', function(done){
request.get('/ignore_headers')
.set('Cookie', 'key=value2')
.expect(200)
.end(helper.endCb(done));
});
});
});

describe('/things', function(){

describe('GET', function(){
Expand Down
24 changes: 19 additions & 5 deletions test/example/md/headers.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,26 +29,40 @@ Respond to deletion of object

+ Response 200


## Things [/ignore_headers]

### Retrieve a dummy object [GET]
Check Cookie header

+ Request

+ Headers

Cookie: key=value

+ Response 200

## Things [/things]

### Retrieve all the things with no header [GET]

+ Response 200 (application/json;charset=UTF-8)

+ Body

{
"header":"absent"
}

+ Request JSON Message

+ Headers

content-type: application/json

+ Response 200

+ Response 200

+ Headers

Expand Down
Loading