const common = require('./common');
const ContentNegotiation = require('./content-negotiation');
const Enum = require('./enum');
-const { DingusError, ResponseError, RouterNoMethodError, RouterNoPathError } = require('./errors');
+const { ResponseError, RouterNoPathError, RouterNoMethodError } = require('./errors');
const { extensionToMime } = require('./mime-helper');
const Router = require('./router');
const Template = require('./template');
staticMetadata: true,
staticPath: undefined, // No reasonable default
trustProxy: true,
+ intrinsicHeadMethod: true,
+ intrinsicHeadPersistBody: false,
querystring,
};
* @param {string} options.selfBaseUrl for constructing links
* @param {Boolean} options.staticMetadata serve static headers with static files
* @param {Boolean} options.trustProxy trust some header data to be provided by proxy
+ * @param {Boolean} options.intrinsicHeadMethod handle HEAD requests automatically if not specified as a route method
+ * @param {Boolean} options.intrinsicHeadPersistBody include un-sent body on ctx for automatic HEAD requests
* @param {Object} options.querystring alternate qs parser to use
*/
constructor(logger = console, options = {}) {
/**
- * Dispatch the handler for a request
- * @param {http.ClientRequest} req
- * @param {http.ServerResponse} res
- * @param {object} ctx
+ * Resolve the handler to invoke for a request.
+ * @param {http.ClientRequest} req
+ * @param {http.ServerResponse} res
+ * @param {object} ctx
+ * @returns {object}
*/
- async dispatch(req, res, ctx = {}) {
- const _scope = _fileScope('dispatch');
+ _determineHandler(req, res, ctx) {
+ const _scope = _fileScope('_determineHandler');
const { pathPart, queryParams } = this._splitUrl(req.url);
ctx.queryParams = queryParams;
try {
({ handler, handlerArgs } = this.router.lookup(req.method, pathPart, ctx));
} catch (e) {
- if (e instanceof RouterNoPathError) {
+ if (e instanceof URIError) {
+ handler = this.handlerBadRequest.bind(this);
+ } else if (e instanceof RouterNoPathError) {
handler = this.handlerNotFound.bind(this);
} else if (e instanceof RouterNoMethodError) {
- handler = this.handlerMethodNotAllowed.bind(this);
- } else if (e instanceof DingusError) {
- this.logger.error(_scope, 'unknown dingus error', { error: e });
+ if (this.intrinsicHeadMethod && req.method === 'HEAD') {
+ ({ handler, handlerArgs } = this._determineHeadHandler(req, res, ctx, pathPart));
+ } else {
+ handler = this.handlerMethodNotAllowed.bind(this);
+ }
+ } else {
+ this.logger.error(_scope, 'unexpected error', { error: e });
handler = this.handlerInternalServerError.bind(this);
- } else if (e instanceof URIError) {
- handler = this.handlerBadRequest.bind(this);
+ }
+ }
+ return { handler, handlerArgs };
+ }
+
+
+ /**
+ * For intrinsic HEAD requests, resolve the handler to invoke.
+ * @param {http.ClientRequest} req
+ * @param {http.ServerResponse} res
+ * @param {object} ctx
+ * @param {string} pathPart
+ * @returns {object}
+ */
+ _determineHeadHandler(req, res, ctx, pathPart) {
+ const _scope = _fileScope('_determineHeadHandler');
+ let handler, handlerArgs = [];
+ try {
+ ({ handler, handlerArgs } = this.router.lookup('GET', pathPart, ctx));
+ Dingus.setHeadHandler(req, res, ctx, this.intrinsicHeadPersistBody);
+ } catch (e) {
+ if (e instanceof RouterNoMethodError) {
+ handler = this.handlerMethodNotAllowed.bind(this);
} else {
- this.logger.error(_scope, 'lookup failure', { error: e });
+ this.logger.error(_scope, 'unexpected error', { error: e });
handler = this.handlerInternalServerError.bind(this);
}
}
+ return { handler, handlerArgs };
+ }
+
+ /**
+ * Dispatch the handler for a request
+ * @param {http.ClientRequest} req
+ * @param {http.ServerResponse} res
+ * @param {object} ctx
+ */
+ async dispatch(req, res, ctx = {}) {
+ const { handler, handlerArgs } = this._determineHandler(req, res, ctx);
try {
await this.preHandler(req, res, ctx);
return await handler(req, res, ctx, ...handlerArgs);
const fs = require('fs');
const Dingus = require('../../lib/dingus');
-const { DingusError } = require('../../lib/errors');
+const { DingusError, RouterNoMethodError } = require('../../lib/errors');
const Enum = require('../../lib/enum');
const noExpectedException = 'did not get expected exception';
sinon.spy(dingus, 'handlerNotFound');
sinon.spy(dingus, 'handlerBadRequest');
sinon.spy(dingus, 'handlerInternalServerError');
+ sinon.spy(Dingus, 'setHeadHandler');
stubHandler = sinon.stub();
});
afterEach(function () {
assert.strictEqual(stubHandler.args[0][3], 'foo');
assert.strictEqual(stubHandler.args[0][4], 'bar');
});
+ describe('intrinsic HEAD handling', function () {
+ it('covers no intrinsic HEAD handling', async function () {
+ dingus.intrinsicHeadMethod = false;
+ dingus.on('GET', '/', stubHandler);
+ req.method = 'HEAD';
+ await dingus.dispatch(req, res, ctx);
+ assert(!stubHandler.called);
+ assert(dingus.handlerMethodNotAllowed.called);
+ });
+ it('calls HEAD setup and GET handler', async function () {
+ dingus.on('GET', '/', stubHandler);
+ req.method = 'HEAD';
+ await dingus.dispatch(req, res, ctx);
+ assert(Dingus.setHeadHandler.called);
+ assert(stubHandler.called);
+ });
+ it('covers no GET handler', async function () {
+ dingus.on('POST', '/', stubHandler);
+ req.method = 'HEAD';
+ await dingus.dispatch(req, res, ctx);
+ assert(!stubHandler.called);
+ assert(dingus.handlerMethodNotAllowed.called);
+ });
+ it('covers unexpected router error', async function () {
+ sinon.stub(dingus.router, 'lookup')
+ .onFirstCall().throws(new RouterNoMethodError())
+ .onSecondCall().throws(new DingusError())
+ ;
+ dingus.on('GET', '/', stubHandler);
+ req.method = 'HEAD';
+ await dingus.dispatch(req, res, ctx);
+ assert(dingus.handlerInternalServerError.called);
+ });
+ });
}); // dispatch
describe('parseBody', function () {