diff --git a/HISTORY.md b/HISTORY.md index 4bc1850..af93d23 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,3 +1,8 @@ +unreleased +========== + + * feat: add `text/plain` fallback response + v2.1.0 / 2025-03-05 ================== diff --git a/index.js b/index.js index bf15e48..6834b0d 100644 --- a/index.js +++ b/index.js @@ -11,6 +11,7 @@ * @private */ +var Negotiator = require('negotiator') var debug = require('debug')('finalhandler') var encodeUrl = require('encodeurl') var escapeHtml = require('escape-html') @@ -32,21 +33,40 @@ var isFinished = onFinished.isFinished * @private */ -function createHtmlDocument (message) { - var body = escapeHtml(message) +function createHtmlBody (message) { + var msg = escapeHtml(message) .replaceAll('\n', '
') .replaceAll(' ', '  ') - return '\n' + + var html = '\n' + '\n' + '\n' + '\n' + 'Error\n' + '\n' + '\n' + - '
' + body + '
\n' + + '
' + msg + '
\n' + '\n' + '\n' + + var body = Buffer.from(html, 'utf8') + body.type = 'text/html; charset=utf-8' + return body +} + +/** + * Get plain text body string + * + * @param {number} status + * @param {string} message + * @return {Buffer} + * @private + */ + +function createTextBody (message) { + var body = Buffer.from(message + '\n', 'utf8') + body.type = 'text/plain; charset=utf-8' + return body } /** @@ -79,6 +99,7 @@ function finalhandler (req, res, options) { var headers var msg var status + var body // ignore 404 on in-flight response if (!err && res.headersSent) { @@ -123,8 +144,23 @@ function finalhandler (req, res, options) { return } + // negotiate + var negotiator = new Negotiator(req) + var type = negotiator.mediaType(['text/html']) + + // construct body + switch (type) { + case 'text/html': + body = createHtmlBody(msg) + break + default: + // default to plain text + body = createTextBody(msg) + break + } + // send response - send(req, res, status, headers, msg) + send(req, res, status, headers, body) } } @@ -241,11 +277,8 @@ function getResponseStatusCode (res) { * @private */ -function send (req, res, status, headers, message) { +function send (req, res, status, headers, body) { function write () { - // response body - var body = createHtmlDocument(message) - // response status res.statusCode = status @@ -268,8 +301,8 @@ function send (req, res, status, headers, message) { res.setHeader('X-Content-Type-Options', 'nosniff') // standard headers - res.setHeader('Content-Type', 'text/html; charset=utf-8') - res.setHeader('Content-Length', Buffer.byteLength(body, 'utf8')) + res.setHeader('Content-Type', body.type) + res.setHeader('Content-Length', body.length) if (req.method === 'HEAD') { res.end() diff --git a/package.json b/package.json index 992b306..99c92ed 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "debug": "^4.4.0", "encodeurl": "^2.0.0", "escape-html": "^1.0.3", + "negotiator": "^1.0.0", "on-finished": "^2.4.1", "parseurl": "^1.3.3", "statuses": "^2.0.1" diff --git a/test/test.js b/test/test.js index 516e088..bfb9ddd 100644 --- a/test/test.js +++ b/test/test.js @@ -297,6 +297,28 @@ var topDescribe = function (type, createServer) { test.write(buf) test.expect(404, done) }) + + describe('when HTML acceptable', function () { + it('should respond with HTML', function (done) { + var server = createServer() + wrapper(request(server) + .get('/foo')) + .set('Accept', 'text/html') + .expect('Content-Type', 'text/html; charset=utf-8') + .expect(404, /= 400', function (done) { var server = createServer(function (req, res) {