index.js
1 /*! 2 * on-headers 3 * Copyright(c) 2014 Douglas Christopher Wilson 4 * MIT Licensed 5 */ 6 7 'use strict' 8 9 /** 10 * Module exports. 11 * @public 12 */ 13 14 module.exports = onHeaders 15 16 var http = require('http') 17 18 // older node versions don't have appendHeader 19 var isAppendHeaderSupported = typeof http.ServerResponse.prototype.appendHeader === 'function' 20 var set1dArray = isAppendHeaderSupported ? set1dArrayWithAppend : set1dArrayWithSet 21 22 /** 23 * Create a replacement writeHead method. 24 * 25 * @param {function} prevWriteHead 26 * @param {function} listener 27 * @private 28 */ 29 30 function createWriteHead (prevWriteHead, listener) { 31 var fired = false 32 33 // return function with core name and argument list 34 return function writeHead (statusCode) { 35 // set headers from arguments 36 var args = setWriteHeadHeaders.apply(this, arguments) 37 38 // fire listener 39 if (!fired) { 40 fired = true 41 listener.call(this) 42 43 // pass-along an updated status code 44 if (typeof args[0] === 'number' && this.statusCode !== args[0]) { 45 args[0] = this.statusCode 46 args.length = 1 47 } 48 } 49 50 return prevWriteHead.apply(this, args) 51 } 52 } 53 54 /** 55 * Execute a listener when a response is about to write headers. 56 * 57 * @param {object} res 58 * @return {function} listener 59 * @public 60 */ 61 62 function onHeaders (res, listener) { 63 if (!res) { 64 throw new TypeError('argument res is required') 65 } 66 67 if (typeof listener !== 'function') { 68 throw new TypeError('argument listener must be a function') 69 } 70 71 res.writeHead = createWriteHead(res.writeHead, listener) 72 } 73 74 /** 75 * Set headers contained in array on the response object. 76 * 77 * @param {object} res 78 * @param {array} headers 79 * @private 80 */ 81 82 function setHeadersFromArray (res, headers) { 83 if (headers.length && Array.isArray(headers[0])) { 84 // 2D 85 set2dArray(res, headers) 86 } else { 87 // 1D 88 if (headers.length % 2 !== 0) { 89 throw new TypeError('headers array is malformed') 90 } 91 92 set1dArray(res, headers) 93 } 94 } 95 96 /** 97 * Set headers contained in object on the response object. 98 * 99 * @param {object} res 100 * @param {object} headers 101 * @private 102 */ 103 104 function setHeadersFromObject (res, headers) { 105 var keys = Object.keys(headers) 106 for (var i = 0; i < keys.length; i++) { 107 var k = keys[i] 108 if (k) res.setHeader(k, headers[k]) 109 } 110 } 111 112 /** 113 * Set headers and other properties on the response object. 114 * 115 * @param {number} statusCode 116 * @private 117 */ 118 119 function setWriteHeadHeaders (statusCode) { 120 var length = arguments.length 121 var headerIndex = length > 1 && typeof arguments[1] === 'string' 122 ? 2 123 : 1 124 125 var headers = length >= headerIndex + 1 126 ? arguments[headerIndex] 127 : undefined 128 129 this.statusCode = statusCode 130 131 if (Array.isArray(headers)) { 132 // handle array case 133 setHeadersFromArray(this, headers) 134 } else if (headers) { 135 // handle object case 136 setHeadersFromObject(this, headers) 137 } 138 139 // copy leading arguments 140 var args = new Array(Math.min(length, headerIndex)) 141 for (var i = 0; i < args.length; i++) { 142 args[i] = arguments[i] 143 } 144 145 return args 146 } 147 148 function set2dArray (res, headers) { 149 var key 150 for (var i = 0; i < headers.length; i++) { 151 key = headers[i][0] 152 if (key) { 153 res.setHeader(key, headers[i][1]) 154 } 155 } 156 } 157 158 function set1dArrayWithAppend (res, headers) { 159 for (var i = 0; i < headers.length; i += 2) { 160 res.removeHeader(headers[i]) 161 } 162 163 var key 164 for (var j = 0; j < headers.length; j += 2) { 165 key = headers[j] 166 if (key) { 167 res.appendHeader(key, headers[j + 1]) 168 } 169 } 170 } 171 172 function set1dArrayWithSet (res, headers) { 173 var key 174 for (var i = 0; i < headers.length; i += 2) { 175 key = headers[i] 176 if (key) { 177 res.setHeader(key, headers[i + 1]) 178 } 179 } 180 }