diff --git a/lib/response.js b/lib/response.js index e3c309744fb..1ffe6219a4a 100644 --- a/lib/response.js +++ b/lib/response.js @@ -49,6 +49,39 @@ var res = Object.create(http.ServerResponse.prototype) module.exports = res +/** + * The results of `mime.contentType`, by the value it was given: a response + * sets one of a handful of types, and each lookup through the mime database + * costs a few hundred nanoseconds. + * @private + */ + +var contentTypeCache = new Map(); + +/** + * `mime.contentType`, memoised. + * + * @param {String} value + * @return {String|Boolean} + * @private + */ + +function cachedContentType(value) { + var resolved = contentTypeCache.get(value); + + if (resolved === undefined) { + resolved = mime.contentType(value); + + if (contentTypeCache.size >= 100) { + contentTypeCache.clear(); + } + + contentTypeCache.set(value, resolved); + } + + return resolved; +} + /** * Set the HTTP status code for the response. * @@ -507,7 +540,7 @@ res.download = function download (path, filename, options, callback) { res.contentType = res.type = function contentType(type) { var ct = type.indexOf('/') === -1 - ? (mime.contentType(type) || 'application/octet-stream') + ? (cachedContentType(type) || 'application/octet-stream') : type; return this.set('Content-Type', ct); @@ -678,7 +711,7 @@ res.header = function header(field, val) { if (Array.isArray(value)) { throw new TypeError('Content-Type cannot be set to an Array'); } - value = mime.contentType(value) + value = cachedContentType(value) } this.setHeader(field, value); diff --git a/lib/utils.js b/lib/utils.js index 4f21e7ef1e3..e9d1d46d2b0 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -213,6 +213,16 @@ exports.compileTrust = function(val) { return proxyaddr.compile(val || []); } +/** + * The results of setCharset, a map of type to result per charset: the same + * few types come through on every response, and parsing and formatting each + * costs a few hundred nanoseconds. One map per charset, since a key made by + * joining the two strings would be hashed again on every call. + * @private + */ + +var setCharsetCache = new Map(); + /** * Set the charset in a given Content-Type string. * @@ -227,14 +237,33 @@ exports.setCharset = function setCharset(type, charset) { return type; } - // parse type - var parsed = contentType.parse(type); + var byType = setCharsetCache.get(charset); + + if (byType === undefined) { + byType = new Map(); + setCharsetCache.set(charset, byType); + } + + var formatted = byType.get(type); + + if (formatted === undefined) { + // parse type + var parsed = contentType.parse(type); + + // set charset + parsed.parameters.charset = charset; + + // format type + formatted = contentType.format(parsed); - // set charset - parsed.parameters.charset = charset; + if (byType.size >= 100) { + byType.clear(); + } + + byType.set(type, formatted); + } - // format type - return contentType.format(parsed); + return formatted; }; /**