diff --git a/API.md b/API.md
index 40d8ffe88..7a6dccc72 100755
--- a/API.md
+++ b/API.md
@@ -3619,7 +3619,7 @@ the same. The following is the complete list of steps a request can go through:
- always called when `onRequest` extensions exist.
- the request path and method can be modified via the [`request.setUrl()`](#request.setUrl()) and [`request.setMethod()`](#request.setMethod()) methods. Changes to the request path or method will impact how the request is routed and can be used for rewrite rules.
- [`request.payload`](#request.payload) is `undefined` and can be overridden with any non-`undefined` value to bypass payload processing.
- - [`request.route`](#request.route) is unassigned.
+ - [`request.route`](#request.route) is `null`.
- [`request.url`](#request.url) can be `null` if the incoming request path is invalid.
- [`request.path`](#request.path) can be an invalid path.
@@ -4789,6 +4789,8 @@ The request route information object, where:
- `settings` - the [route options](#route-options) object with all defaults applied.
- `fingerprint` - the route internal normalized string representing the normalized path.
+Returns `null` until the "onRequest" [request lifecycle](https://github.com/hapijs/hapi/blob/master/API.md#request-lifecycle) step is complete.
+
#### `request.server`
Access: read only and the public server interface.
diff --git a/lib/compression.js b/lib/compression.js
index 3e4c692e4..72ee1ff41 100755
--- a/lib/compression.js
+++ b/lib/compression.js
@@ -114,6 +114,6 @@ exports = module.exports = internals.Compression = class {
const encoder = this.encoders[encoding];
Hoek.assert(encoder !== undefined, `Unknown encoding ${encoding}`);
- return encoder(request.route.settings.compression[encoding]);
+ return encoder(request._route.settings.compression[encoding]);
}
};
diff --git a/lib/cors.js b/lib/cors.js
index 6600531f2..ffd4b13a0 100755
--- a/lib/cors.js
+++ b/lib/cors.js
@@ -158,14 +158,14 @@ internals.handler = function (request, h) {
exports.headers = function (response) {
const request = response.request;
- const settings = request.route.settings.cors;
+ const settings = request._route.settings.cors;
if (settings._origin !== false) {
response.vary('origin');
}
- if ((request.info.cors && !request.info.cors.isOriginMatch) || // After route lookup
- !exports.matchOrigin(request.headers.origin, request.route.settings.cors)) { // Response from onRequest
+ if ((request.info.cors && !request.info.cors.isOriginMatch) || // After route lookup
+ !exports.matchOrigin(request.headers.origin, request._route.settings.cors)) { // Response from onRequest
return;
}
diff --git a/lib/handler.js b/lib/handler.js
index 9ae261022..2f0632037 100755
--- a/lib/handler.js
+++ b/lib/handler.js
@@ -28,7 +28,7 @@ exports.execute = async function (request) {
// Handler
- const result = await internals.handler(request, request.route.settings.handler);
+ const result = await internals.handler(request, request._route.settings.handler);
if (result._takeover ||
typeof result === 'symbol') {
@@ -41,8 +41,8 @@ exports.execute = async function (request) {
internals.handler = async function (request, method, pre) {
- const bind = request.route.settings.bind;
- const realm = request.route.realm;
+ const bind = request._route.settings.bind;
+ const realm = request._route.realm;
let response = await request._core.toolkit.execute(method, request, { bind, realm, continue: 'null' });
// Handler
diff --git a/lib/headers.js b/lib/headers.js
index 69a528e18..15310dba9 100755
--- a/lib/headers.js
+++ b/lib/headers.js
@@ -16,7 +16,7 @@ exports.cache = function (response) {
return;
}
- const settings = request.route.settings.cache;
+ const settings = request._route.settings.cache;
const policy = settings && request._route._cache && (settings._statuses.has(response.statusCode) || (response.statusCode === 304 && settings._statuses.has(200)));
if (policy ||
diff --git a/lib/request.js b/lib/request.js
index 6efaa1511..dd62cdb3a 100755
--- a/lib/request.js
+++ b/lib/request.js
@@ -54,7 +54,6 @@ exports = module.exports = internals.Request = class {
this.preResponses = {}; // Pre response values
this.raw = { req, res };
this.response = null;
- this.route = this._route.public;
this.query = null;
this.server = server;
this.state = null;
@@ -121,6 +120,11 @@ exports = module.exports = internals.Request = class {
return this._parseUrl(this.raw.req.url, this._core.settings.router);
}
+ get route() {
+
+ return this.params === null ? null : this._route.public;
+ }
+
_initializeUrl() {
try {
@@ -322,15 +326,14 @@ exports = module.exports = internals.Request = class {
this._allowInternals) {
this._route = match.route;
- this.route = this._route.public;
}
this.params = match.params ?? {};
this.paramsArray = match.paramsArray ?? [];
- if (this.route.settings.cors) {
+ if (this._route.settings.cors) {
this.info.cors = {
- isOriginMatch: Cors.matchOrigin(this.headers.origin, this.route.settings.cors)
+ isOriginMatch: Cors.matchOrigin(this.headers.origin, this._route.settings.cors)
};
}
}
@@ -338,12 +341,12 @@ exports = module.exports = internals.Request = class {
_setTimeouts() {
if (this.raw.req.socket &&
- this.route.settings.timeout.socket !== undefined) {
+ this._route.settings.timeout.socket !== undefined) {
- this.raw.req.socket.setTimeout(this.route.settings.timeout.socket || 0); // Value can be false or positive
+ this.raw.req.socket.setTimeout(this._route.settings.timeout.socket || 0); // Value can be false or positive
}
- let serverTimeout = this.route.settings.timeout.server;
+ let serverTimeout = this._route.settings.timeout.server;
if (!serverTimeout) {
return;
}
@@ -580,7 +583,7 @@ exports = module.exports = internals.Request = class {
_log(tags, data, channel = 'internal') {
if (!this._core.events.hasListeners('request') &&
- !this.route.settings.log.collect) {
+ !this._route.settings.log.collect) {
return;
}
@@ -597,7 +600,7 @@ exports = module.exports = internals.Request = class {
event = () => [this, { request: this.info.id, timestamp, tags, data: data(), channel }];
}
- if (this.route.settings.log.collect) {
+ if (this._route.settings.log.collect) {
if (typeof data === 'function') {
event = event();
}
@@ -735,7 +738,7 @@ internals.event = function ({ request }, event, err) {
// called _reply(), in which case this call is ignored and the transmit logic is responsible for
// handling the abort.
- request._reply(new Boom.Boom('Request aborted', { statusCode: request.route.settings.response.disconnectStatusCode, data: request.response }));
+ request._reply(new Boom.Boom('Request aborted', { statusCode: request._route.settings.response.disconnectStatusCode, data: request.response }));
if (request._events) {
request._events.emit('disconnect');
diff --git a/lib/response.js b/lib/response.js
index 2acd8320a..5cbdbcd4c 100755
--- a/lib/response.js
+++ b/lib/response.js
@@ -600,11 +600,12 @@ exports = module.exports = internals.Response = class {
let payload = source;
if (jsonify) {
+ const fallback = this.request._route.settings.json;
const options = this.settings.stringify ?? {};
- const space = options.space ?? this.request.route.settings.json.space;
- const replacer = options.replacer ?? this.request.route.settings.json.replacer;
- const suffix = options.suffix ?? this.request.route.settings.json.suffix ?? '';
- const escape = this.request.route.settings.json.escape;
+ const space = options.space ?? fallback.space;
+ const replacer = options.replacer ?? fallback.replacer;
+ const suffix = options.suffix ?? fallback.suffix;
+ const escape = options.escape ?? fallback.escape;
try {
if (replacer || space) {
diff --git a/lib/route.js b/lib/route.js
index 0fb4e0d49..491b78ed4 100755
--- a/lib/route.js
+++ b/lib/route.js
@@ -396,7 +396,7 @@ internals.state = async function (request) {
parseError.header = cookies;
- return request._core.toolkit.failAction(request, request.route.settings.state.failAction, parseError, { tags: ['state', 'error'] });
+ return request._core.toolkit.failAction(request, request._route.settings.state.failAction, parseError, { tags: ['state', 'error'] });
};
@@ -417,7 +417,7 @@ internals.payload = async function (request) {
}
try {
- const { payload, mime } = await Subtext.parse(request.raw.req, request._tap(), request.route.settings.payload);
+ const { payload, mime } = await Subtext.parse(request.raw.req, request._tap(), request._route.settings.payload);
request._isPayloadPending = !!payload?._readableState;
request.mime = mime;
@@ -433,7 +433,7 @@ internals.payload = async function (request) {
request.mime = err.mime;
request.payload = null;
- return request._core.toolkit.failAction(request, request.route.settings.payload.failAction, err, { tags: ['payload', 'error'] });
+ return request._core.toolkit.failAction(request, request._route.settings.payload.failAction, err, { tags: ['payload', 'error'] });
}
};
diff --git a/lib/security.js b/lib/security.js
index de8776114..13c1f3a01 100755
--- a/lib/security.js
+++ b/lib/security.js
@@ -55,7 +55,7 @@ exports.route = function (settings) {
exports.headers = function (response) {
- const security = response.request.route.settings.security;
+ const security = response.request._route.settings.security;
if (security._hsts) {
response._header('strict-transport-security', security._hsts, { override: false });
diff --git a/lib/toolkit.js b/lib/toolkit.js
index ded406f5f..e6b1f626c 100755
--- a/lib/toolkit.js
+++ b/lib/toolkit.js
@@ -130,7 +130,7 @@ exports.Manager = class {
throw err;
}
- return this.execute(failAction, request, { realm: request.route.realm, args: [options.details ?? err] });
+ return this.execute(failAction, request, { realm: request._route.realm, args: [options.details ?? err] });
}
};
diff --git a/lib/transmit.js b/lib/transmit.js
index 7a1424153..47a2a97c9 100755
--- a/lib/transmit.js
+++ b/lib/transmit.js
@@ -143,7 +143,7 @@ internals.length = function (response) {
if (length === 0 &&
!response._statusCode &&
response.statusCode === 200 &&
- request.route.settings.response.emptyStatusCode !== 200) {
+ request._route.settings.response.emptyStatusCode !== 200) {
response.code(204);
delete response.headers['content-length'];
@@ -158,7 +158,7 @@ internals.range = function (response, length) {
const request = response.request;
if (!length ||
- !request.route.settings.response.ranges ||
+ !request._route.settings.response.ranges ||
request.method !== 'get' ||
response.statusCode !== 200) {
@@ -301,7 +301,7 @@ internals.end = function (env, event, err) {
const origResponse = request.response;
const error = err ? Boom.boomify(err) :
- new Boom.Boom(`Request ${event}`, { statusCode: request.route.settings.response.disconnectStatusCode, data: origResponse });
+ new Boom.Boom(`Request ${event}`, { statusCode: request._route.settings.response.disconnectStatusCode, data: origResponse });
request._setResponse(error);
@@ -309,7 +309,7 @@ internals.end = function (env, event, err) {
if (request.raw.res[Config.symbol]) {
request.raw.res[Config.symbol].error = event ? error :
- new Boom.Boom(`Response error`, { statusCode: request.route.settings.response.disconnectStatusCode, data: origResponse });
+ new Boom.Boom(`Response error`, { statusCode: request._route.settings.response.disconnectStatusCode, data: origResponse });
}
if (event) {
diff --git a/lib/types/request.d.ts b/lib/types/request.d.ts
index f0711afdd..ade71e661 100644
--- a/lib/types/request.d.ts
+++ b/lib/types/request.d.ts
@@ -451,7 +451,7 @@ export interface Request extends Podium {
* [See docs](https://github.com/hapijs/hapi/blob/master/API.md#-requestroute)
* [See docs](https://github.com/hapijs/hapi/blob/master/API.md#-requestrouteauthaccessrequest)
*/
- readonly route: RequestRoute;
+ readonly route: RequestRoute | null;
/**
* Access: read only and the public server interface.
diff --git a/lib/validation.js b/lib/validation.js
index e94bf1349..6de00bd53 100755
--- a/lib/validation.js
+++ b/lib/validation.js
@@ -104,6 +104,7 @@ exports.state = function (request) {
internals.input = async function (source, request) {
+ const settings = request._route.settings.validate;
const localOptions = {
context: {
headers: request.headers,
@@ -113,18 +114,18 @@ internals.input = async function (source, request) {
state: request.state,
auth: request.auth,
app: {
- route: request.route.settings.app,
+ route: request._route.settings.app,
request: request.app
}
}
};
delete localOptions.context[source];
- Hoek.merge(localOptions, request.route.settings.validate.options);
+ Hoek.merge(localOptions, settings.options);
try {
- const schema = request.route.settings.validate[source];
- const bind = request.route.settings.bind;
+ const schema = settings[source];
+ const bind = request._route.settings.bind;
var value = await (typeof schema !== 'function' ? internals.validate(request[source], schema, localOptions) : schema.call(bind, request[source], localOptions));
return;
@@ -139,7 +140,7 @@ internals.input = async function (source, request) {
}
}
- if (request.route.settings.validate.failAction === 'ignore') {
+ if (settings.failAction === 'ignore') {
return;
}
@@ -155,21 +156,22 @@ internals.input = async function (source, request) {
}
}
- if (request.route.settings.validate.errorFields) {
- for (const field in request.route.settings.validate.errorFields) {
- detailedError.output.payload[field] = request.route.settings.validate.errorFields[field];
+ if (settings.errorFields) {
+ for (const field in settings.errorFields) {
+ detailedError.output.payload[field] = settings.errorFields[field];
}
}
- return request._core.toolkit.failAction(request, request.route.settings.validate.failAction, defaultError, { details: detailedError, tags: ['validation', 'error', source] });
+ return request._core.toolkit.failAction(request, settings.failAction, defaultError, { details: detailedError, tags: ['validation', 'error', source] });
};
exports.response = async function (request) {
- if (request.route.settings.response.sample) {
+ const settings = request._route.settings.response;
+ if (settings.sample) {
const currentSample = Math.ceil(Math.random() * 100);
- if (currentSample > request.route.settings.response.sample) {
+ if (currentSample > settings.sample) {
return;
}
}
@@ -177,14 +179,14 @@ exports.response = async function (request) {
const response = request.response;
const statusCode = response.isBoom ? response.output.statusCode : response.statusCode;
- const statusSchema = request.route.settings.response.status[statusCode];
+ const statusSchema = settings.status[statusCode];
if (statusCode >= 400 &&
!statusSchema) {
return; // Do not validate errors by default
}
- const schema = statusSchema !== undefined ? statusSchema : request.route.settings.response.schema;
+ const schema = statusSchema !== undefined ? statusSchema : settings.schema;
if (schema === null) {
return; // No rules
}
@@ -204,14 +206,14 @@ exports.response = async function (request) {
state: request.state,
auth: request.auth,
app: {
- route: request.route.settings.app,
+ route: request._route.settings.app,
request: request.app
}
}
};
const source = response.isBoom ? response.output.payload : response.source;
- Hoek.merge(localOptions, request.route.settings.response.options);
+ Hoek.merge(localOptions, settings.options);
try {
let value;
@@ -224,7 +226,7 @@ exports.response = async function (request) {
}
if (value !== undefined &&
- request.route.settings.response.modify) {
+ settings.modify) {
if (response.isBoom) {
response.output.payload = value;
@@ -235,7 +237,7 @@ exports.response = async function (request) {
}
}
catch (err) {
- return request._core.toolkit.failAction(request, request.route.settings.response.failAction, err, { tags: ['validation', 'response', 'error'] });
+ return request._core.toolkit.failAction(request, settings.failAction, err, { tags: ['validation', 'response', 'error'] });
}
};
diff --git a/test/core.js b/test/core.js
index 58a7a18d6..442d55e9d 100755
--- a/test/core.js
+++ b/test/core.js
@@ -1660,6 +1660,28 @@ describe('Core', () => {
describe('onRequest', () => {
+ it('request object has null route and params', async () => {
+
+ const server = Hapi.server();
+
+ const onRequest = new Promise((resolve) => {
+
+ server.ext('onRequest', (request, h) => {
+
+ resolve(request);
+ return h.continue;
+ });
+ });
+
+ const req = server.inject('/');
+ const request = await onRequest;
+ expect(request.route).to.be.null();
+ expect(request.params).to.be.null();
+
+ const res = await req;
+ expect(res.statusCode).to.equal(404);
+ });
+
it('replies with custom response', async () => {
const server = Hapi.server();
diff --git a/test/response.js b/test/response.js
index 61056a1ba..1c6d22b02 100755
--- a/test/response.js
+++ b/test/response.js
@@ -1124,14 +1124,14 @@ describe('Response', () => {
const handler = (request, h) => {
- return h.response({ a: 1, b: 2, '<': '&' }).type('application/x-test').spaces(2).replacer(['a']).suffix('\n').escape(false);
+ return h.response({ a: '&', b: 2, '<': '&' }).type('application/x-test').spaces(2).replacer(['a']).suffix('\n').escape(true);
};
const server = Hapi.server();
server.route({ method: 'GET', path: '/', handler });
const res = await server.inject('/');
- expect(res.payload).to.equal('{\n \"a\": 1\n}\n');
+ expect(res.payload).to.equal('{\n \"a\": "\\u0026"\n}\n');
expect(res.headers['content-type']).to.equal('application/x-test');
});
@@ -1139,14 +1139,14 @@ describe('Response', () => {
const handler = (request, h) => {
- return h.response({ a: 1, b: 2, '<': '&' }).type('application/x-test').escape(false).replacer(['a']).suffix('\n').spaces(2);
+ return h.response({ a: '&', b: 2, '<': '&' }).type('application/x-test').escape(true).replacer(['a']).suffix('\n').spaces(2);
};
const server = Hapi.server();
server.route({ method: 'GET', path: '/', handler });
const res = await server.inject('/');
- expect(res.payload).to.equal('{\n \"a\": 1\n}\n');
+ expect(res.payload).to.equal('{\n \"a\": "\\u0026"\n}\n');
expect(res.headers['content-type']).to.equal('application/x-test');
});
diff --git a/test/types/index.ts b/test/types/index.ts
index 3b18fe611..fb9216dfc 100644
--- a/test/types/index.ts
+++ b/test/types/index.ts
@@ -51,7 +51,7 @@ const route: ServerRoute = {
check.type>(request.params);
check.type(request.server.app.multi!);
- check.type(request.route.settings.app!.prefix);
+ check.type(request.route!.settings.app!.prefix);
return 'hello!'
}