diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index cdcea51..7659a95 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -12,6 +12,11 @@ "from": "cookie@>=0.3.1 <0.4.0", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz" }, + "header-case-normalizer": { + "version": "1.0.3", + "from": "header-case-normalizer@latest", + "resolved": "https://registry.npmjs.org/header-case-normalizer/-/header-case-normalizer-1.0.3.tgz" + }, "lodash.clonedeep": { "version": "4.5.0", "from": "lodash.clonedeep@>=4.5.0 <5.0.0", diff --git a/package.json b/package.json index 8510d24..6cb7c3b 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "dependencies": { "accepts": "^1.3.3", "cookie": "^0.3.1", + "header-case-normalizer": "^1.0.3", "lodash.clonedeep": "^4.5.0", "lodash.get": "^4.4.2", "lodash.has": "^4.5.2", diff --git a/src/events/response.js b/src/events/response.js index e5c586e..b54465c 100644 --- a/src/events/response.js +++ b/src/events/response.js @@ -1,7 +1,9 @@ 'use strict' const _toString = require('lodash.tostring') +const _transform = require('lodash.transform') const cookie = require('cookie') const mime = require('mime') +const normalizeHeader = require('header-case-normalizer') const corsHeader = { 'Access-Control-Allow-Origin': '*', @@ -42,18 +44,11 @@ class Response { this.body = '' /** - * Map of headers to include with the request + * Map of lowercase headers to include with the request * @type {{}} */ this.headers = {} - /** - * Map of headers with lowercase keys - * @type {{}} - * @private - */ - this._headers = {} - if (typeof opts.headers === 'object') { for (const key in opts.headers) { this.set(key, opts.headers[key]) @@ -124,7 +119,9 @@ class Response { const res = { statusCode: this.statusCode, - headers: this.headers, + headers: _transform(this.headers, (result, val, key) => { + result[normalizeHeader(key)] = val + }), body: body, } @@ -199,7 +196,7 @@ class Response { * @public */ get (field) { - return this._headers[field.toLowerCase()] + return this.headers[field.toLowerCase()] } /** @@ -225,8 +222,7 @@ class Response { ? val.map(_toString) : _toString(val) - this.headers[field] = value - this._headers[field.toLowerCase()] = value + this.headers[field.toLowerCase()] = value } else { for (const key in field) { this.set(key, field[key]) diff --git a/test/events/response.test.js b/test/events/response.test.js index b8d53b6..1f49a42 100644 --- a/test/events/response.test.js +++ b/test/events/response.test.js @@ -5,23 +5,23 @@ const Response = require('../../src/events/response') describe('events', () => { describe('Response', () => { describe('#set', () => { - it('should set key with value in headers object', () => { + it('should set a lowercase key with value in headers object', () => { const res = new Response() expect(res.headers).to.eql({}) res.set('Some', 'Header') - expect(res.headers['Some']).to.eq('Header') + expect(res.headers['some']).to.eq('Header') }) it('should force a string', () => { const res = new Response() res.set('X-Limit', 2000) - expect(res.headers['X-Limit']).to.eq('2000') + expect(res.headers['x-limit']).to.eq('2000') }) it('should set a list of header values', () => { const res = new Response() res.set('X-Limits', [100, 200]) - expect(res.headers['X-Limits']).to.eql(['100', '200']) + expect(res.headers['x-limits']).to.eql(['100', '200']) }) it('should set a an object of headers', () => { @@ -30,8 +30,8 @@ describe('events', () => { 'X-Limit': 200, 'X-Blah': 'meh', }) - expect(res.headers['X-Limit']).to.eq('200') - expect(res.headers['X-Blah']).to.eq('meh') + expect(res.headers['x-limit']).to.eq('200') + expect(res.headers['x-blah']).to.eq('meh') }) it('should be chainable', () => { @@ -41,8 +41,8 @@ describe('events', () => { }) .set('X-Limit', 200) - expect(res.headers['X-Limit']).to.eq('200') - expect(res.headers['X-Blah']).to.eq('meh') + expect(res.headers['x-limit']).to.eq('200') + expect(res.headers['x-blah']).to.eq('meh') }) }) @@ -51,14 +51,14 @@ describe('events', () => { const res = new Response() expect(res.headers).to.eql({}) res.append('Some', 'Header') - expect(res.headers['Some']).to.eq('Header') + expect(res.headers['some']).to.eq('Header') }) it('should be overriden with set', () => { const res = new Response() res.append('X-Limits', [100, 200]) res.set('X-Limits', 300) - expect(res.headers['X-Limits']).to.eq('300') + expect(res.headers['x-limits']).to.eq('300') }) it('should be chainable', () => { @@ -66,7 +66,7 @@ describe('events', () => { res.append('X-Blah', 'meh') .append('X-Blah', 200) - expect(res.headers['X-Blah']).to.eql(['meh', '200']) + expect(res.headers['x-blah']).to.eql(['meh', '200']) }) }) @@ -82,13 +82,13 @@ describe('events', () => { it('should set content type with mime look up', () => { const res = new Response() res.contentType('html') - expect(res.headers['Content-Type']).to.eq('text/html') + expect(res.headers['content-type']).to.eq('text/html') }) it('should set content string with / content type', () => { const res = new Response() res.contentType('text/plain') - expect(res.headers['Content-Type']).to.eq('text/plain') + expect(res.headers['content-type']).to.eq('text/plain') }) }) @@ -109,7 +109,7 @@ describe('events', () => { it('should add cookie header', () => { const res = new Response() res.cookie('some', 'value') - expect(res.headers['Set-Cookie']).to.eql('some=value; Path=/') + expect(res.headers['set-cookie']).to.eql('some=value; Path=/') }) it('should be chainable', () => { @@ -117,25 +117,25 @@ describe('events', () => { res.cookie('some', 'value') .cookie('another', 'value') - expect(res.headers['Set-Cookie']).to.eql(['some=value; Path=/', 'another=value; Path=/']) + expect(res.headers['set-cookie']).to.eql(['some=value; Path=/', 'another=value; Path=/']) }) it('should handle a JSON-able object', () => { const res = new Response() res.cookie('guy', { blah: 'meh' }) - expect(res.headers['Set-Cookie']).to.eql(`guy=${encodeURIComponent('j:{"blah":"meh"}')}; Path=/`) + expect(res.headers['set-cookie']).to.eql(`guy=${encodeURIComponent('j:{"blah":"meh"}')}; Path=/`) }) it('should support options', () => { const res = new Response() res.cookie('some', 'value', { secure: true, httpOnly: true }) - expect(res.headers['Set-Cookie']).to.eql('some=value; Path=/; HttpOnly; Secure') + expect(res.headers['set-cookie']).to.eql('some=value; Path=/; HttpOnly; Secure') }) it('should set max-age relative to now', () => { const res = new Response() res.cookie('some', 'value', { maxAge: 1000 }) - expect(res.headers['Set-Cookie'][0]).not.to.contain('Thu, 01 Jan 1970 00:00:01 GMT') + expect(res.headers['set-cookie'][0]).not.to.contain('Thu, 01 Jan 1970 00:00:01 GMT') }) }) @@ -149,6 +149,17 @@ describe('events', () => { }) }) + it('should return normalized headers', () => { + const res = new Response({ + headers: { 'X-Some-Header': 'some-val' }, + }) + expect(res.send('blah')).to.eql({ + statusCode: 200, + headers: { 'Content-Type': 'text/plain', 'X-Some-Header': 'some-val' }, + body: 'blah', + }) + }) + it('should not overwrite content type if set', () => { const res = new Response() res.contentType('html') @@ -251,7 +262,7 @@ describe('events', () => { const res = new Response({ headers: { 'X-Some': 'Thing' }, }) - expect(res.headers['X-Some']).to.eq('Thing') + expect(res.headers['x-some']).to.eq('Thing') res.set('Blah', '1') expect(Object.keys(res.headers)).to.have.lengthOf(2) }) @@ -260,7 +271,7 @@ describe('events', () => { const res = new Response({ cors: true, }) - expect(res.headers['Access-Control-Allow-Origin']).to.eq('*') + expect(res.headers['access-control-allow-origin']).to.eq('*') }) }) })