Skip to content

Make pre-routed request.route property null #4433

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion API.md
Original file line number Diff line number Diff line change
@@ -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.

#### <a name="request.server" /> `request.server`

Access: read only and the public server interface.
2 changes: 1 addition & 1 deletion lib/compression.js
Original file line number Diff line number Diff line change
@@ -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]);
}
};
6 changes: 3 additions & 3 deletions lib/cors.js
Original file line number Diff line number Diff line change
@@ -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;
}
6 changes: 3 additions & 3 deletions lib/handler.js
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion lib/headers.js
Original file line number Diff line number Diff line change
@@ -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 ||
23 changes: 13 additions & 10 deletions lib/request.js
Original file line number Diff line number Diff line change
@@ -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,28 +326,27 @@ 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)
};
}
}

_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');
9 changes: 5 additions & 4 deletions lib/response.js
Original file line number Diff line number Diff line change
@@ -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) {
6 changes: 3 additions & 3 deletions lib/route.js
Original file line number Diff line number Diff line change
@@ -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'] });
}
};

2 changes: 1 addition & 1 deletion lib/security.js
Original file line number Diff line number Diff line change
@@ -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 });
2 changes: 1 addition & 1 deletion lib/toolkit.js
Original file line number Diff line number Diff line change
@@ -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] });
}
};

8 changes: 4 additions & 4 deletions lib/transmit.js
Original file line number Diff line number Diff line change
@@ -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,15 +301,15 @@ 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);

// Make inject throw a disconnect error

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) {
2 changes: 1 addition & 1 deletion lib/types/request.d.ts
Original file line number Diff line number Diff line change
@@ -451,7 +451,7 @@ export interface Request<Refs extends ReqRef = ReqRefDefaults> 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<Refs>;
readonly route: RequestRoute<Refs> | null;

/**
* Access: read only and the public server interface.
36 changes: 19 additions & 17 deletions lib/validation.js
Original file line number Diff line number Diff line change
@@ -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,36 +156,37 @@ 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;
}
}

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'] });
}
};

22 changes: 22 additions & 0 deletions test/core.js
Original file line number Diff line number Diff line change
@@ -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();
8 changes: 4 additions & 4 deletions test/response.js
Original file line number Diff line number Diff line change
@@ -1124,29 +1124,29 @@ 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');
});

it('returns a response with options (different order)', async () => {

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');
});

2 changes: 1 addition & 1 deletion test/types/index.ts
Original file line number Diff line number Diff line change
@@ -51,7 +51,7 @@ const route: ServerRoute<RequestDecorations> = {

check.type<Record<string, string>>(request.params);
check.type<number>(request.server.app.multi!);
check.type<string[]>(request.route.settings.app!.prefix);
check.type<string[]>(request.route!.settings.app!.prefix);

return 'hello!'
}