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' +
'
' + 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) {