Skip to content

Commit

Permalink
lowercase all headers, then normalize on the way out
Browse files Browse the repository at this point in the history
  • Loading branch information
geoffdutton committed Feb 1, 2017
1 parent 5447008 commit ff7c785
Show file tree
Hide file tree
Showing 4 changed files with 45 additions and 32 deletions.
5 changes: 5 additions & 0 deletions npm-shrinkwrap.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
20 changes: 8 additions & 12 deletions src/events/response.js
Original file line number Diff line number Diff line change
@@ -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': '*',
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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,
}

Expand Down Expand Up @@ -199,7 +196,7 @@ class Response {
* @public
*/
get (field) {
return this._headers[field.toLowerCase()]
return this.headers[field.toLowerCase()]
}

/**
Expand All @@ -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])
Expand Down
51 changes: 31 additions & 20 deletions test/events/response.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand All @@ -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', () => {
Expand All @@ -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')
})
})

Expand All @@ -51,22 +51,22 @@ 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', () => {
const res = new Response()
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'])
})
})

Expand All @@ -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')
})
})

Expand All @@ -109,33 +109,33 @@ 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', () => {
const res = new Response()
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')
})
})

Expand All @@ -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')
Expand Down Expand Up @@ -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)
})
Expand All @@ -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('*')
})
})
})
Expand Down

0 comments on commit ff7c785

Please sign in to comment.