diff --git a/docs/Reference/Request.md b/docs/Reference/Request.md index 93af618f9c..1472fd2d0d 100644 --- a/docs/Reference/Request.md +++ b/docs/Reference/Request.md @@ -42,6 +42,17 @@ Request is a core Fastify object containing the following fields: handling the request - `routeConfig` - The route [`config`](./Routes.md#routes-config) object. +- `routeOptions` - The route [`option`](./Routes.md#routes-options) object + - `bodyLimit` - either server limit or route limit + - `method` - the http method for the route + - `url` - the path of the URL to match this route + - `attachValidation` - attach `validationError` to request + (if there is a schema defined) + - `logLevel` - log level defined for this route + - `version` - a semver compatible string that defines the version of the endpoint + - `exposeHeadRoute` - creates a sibling HEAD route for any GET routes + - `prefixTrailingSlash` - string used to determine how to handle passing / + as a route with a prefix. - [.getValidationFunction(schema | httpPart)](#getvalidationfunction) - Returns a validation function for the specified schema or http part, if any of either are set or cached. @@ -90,7 +101,15 @@ fastify.post('/:params', options, function (request, reply) { console.log(request.protocol) console.log(request.url) console.log(request.routerMethod) - console.log(request.routerPath) + console.log(request.routeOptions.bodyLimit) + console.log(request.routeOptions.method) + console.log(request.routeOptions.url) + console.log(request.routeOptions.attachValidation) + console.log(request.routeOptions.logLevel) + console.log(request.routeOptions.version) + console.log(request.routeOptions.exposeHeadRoute) + console.log(request.routeOptions.prefixTrailingSlash) + console.log(request.routerPath.logLevel) request.log.info('some info') }) ``` diff --git a/lib/context.js b/lib/context.js index 91953f947c..226d09280d 100644 --- a/lib/context.js +++ b/lib/context.js @@ -31,6 +31,8 @@ function Context ({ serializerCompiler, replySerializer, schemaErrorFormatter, + exposeHeadRoute, + prefixTrailingSlash, server, isFastify }) { @@ -52,6 +54,8 @@ function Context ({ this._parserOptions = { limit: bodyLimit || server[kBodyLimit] } + this.exposeHeadRoute = exposeHeadRoute + this.prefixTrailingSlash = prefixTrailingSlash this.logLevel = logLevel || server[kLogLevel] this.logSerializers = logSerializers this[kFourOhFourContext] = null diff --git a/lib/request.js b/lib/request.js index ab47c6f030..25d4c4ec0c 100644 --- a/lib/request.js +++ b/lib/request.js @@ -165,6 +165,25 @@ Object.defineProperties(Request.prototype, { return this[kRouteContext].config.url } }, + routeOptions: { + get () { + const context = this[kRouteContext] + const routeLimit = context._parserOptions.limit + const serverLimit = context.server.initialConfig.bodyLimit + const version = context.server.hasConstraintStrategy('version') ? this.raw.headers['accept-version'] : undefined + const options = { + method: context.config.method, + url: context.config.url, + bodyLimit: (routeLimit || serverLimit), + attachValidation: context.attachValidation, + logLevel: context.logLevel, + exposeHeadRoute: context.exposeHeadRoute, + prefixTrailingSlash: context.prefixTrailingSlash, + version + } + return Object.freeze(options) + } + }, routerMethod: { get () { return this[kRouteContext].config.method diff --git a/lib/route.js b/lib/route.js index 621aa871a1..f48467de88 100644 --- a/lib/route.js +++ b/lib/route.js @@ -278,6 +278,8 @@ function buildRouting (options) { replySerializer: this[kReplySerializerDefault], validatorCompiler: opts.validatorCompiler, serializerCompiler: opts.serializerCompiler, + exposeHeadRoute: shouldExposeHead, + prefixTrailingSlash: (opts.prefixTrailingSlash || 'both'), server: this, isFastify }) diff --git a/test/bodyLimit.test.js b/test/bodyLimit.test.js index f1c50b8a37..980dd23b2a 100644 --- a/test/bodyLimit.test.js +++ b/test/bodyLimit.test.js @@ -44,3 +44,82 @@ test('bodyLimit', t => { }) }) }) + +test('default request.routeOptions.bodyLimit should be 1048576', t => { + t.plan(4) + const fastify = Fastify() + fastify.post('/default-bodylimit', { + handler (request, reply) { + t.equal(1048576, request.routeOptions.bodyLimit) + reply.send({ }) + } + }) + fastify.listen({ port: 0 }, function (err) { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'POST', + url: 'http://localhost:' + fastify.server.address().port + '/default-bodylimit', + headers: { 'Content-Type': 'application/json' }, + body: [], + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + }) + }) +}) + +test('request.routeOptions.bodyLimit should be equal to route limit', t => { + t.plan(4) + const fastify = Fastify({ bodyLimit: 1 }) + fastify.post('/route-limit', { + bodyLimit: 1000, + handler (request, reply) { + t.equal(1000, request.routeOptions.bodyLimit) + reply.send({}) + } + }) + fastify.listen({ port: 0 }, function (err) { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'POST', + url: 'http://localhost:' + fastify.server.address().port + '/route-limit', + headers: { 'Content-Type': 'application/json' }, + body: [], + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + }) + }) +}) + +test('request.routeOptions.bodyLimit should be equal to server limit', t => { + t.plan(4) + const fastify = Fastify({ bodyLimit: 100 }) + fastify.post('/server-limit', { + handler (request, reply) { + t.equal(100, request.routeOptions.bodyLimit) + reply.send({}) + } + }) + fastify.listen({ port: 0 }, function (err) { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'POST', + url: 'http://localhost:' + fastify.server.address().port + '/server-limit', + headers: { 'Content-Type': 'application/json' }, + body: [], + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + }) + }) +}) diff --git a/test/request-error.test.js b/test/request-error.test.js index a3be6e1870..9108f9aa4e 100644 --- a/test/request-error.test.js +++ b/test/request-error.test.js @@ -1,6 +1,7 @@ 'use strict' const { connect } = require('net') +const sget = require('simple-get').concat const t = require('tap') const test = t.test const Fastify = require('..') @@ -313,3 +314,98 @@ test('default clientError replies with bad request on reused keep-alive connecti client.write('\r\n\r\n') }) }) + +test('request.routeOptions should be immutable', t => { + t.plan(14) + const fastify = Fastify() + const handler = function (req, res) { + t.equal('POST', req.routeOptions.method) + t.equal('/', req.routeOptions.url) + t.throws(() => { req.routeOptions = null }, new TypeError('Cannot set property routeOptions of # which has only a getter')) + t.throws(() => { req.routeOptions.method = 'INVALID' }, new TypeError('Cannot assign to read only property \'method\' of object \'#\'')) + t.throws(() => { req.routeOptions.url = '//' }, new TypeError('Cannot assign to read only property \'url\' of object \'#\'')) + t.throws(() => { req.routeOptions.bodyLimit = 0xDEADBEEF }, new TypeError('Cannot assign to read only property \'bodyLimit\' of object \'#\'')) + t.throws(() => { req.routeOptions.attachValidation = true }, new TypeError('Cannot assign to read only property \'attachValidation\' of object \'#\'')) + t.throws(() => { req.routeOptions.logLevel = 'invalid' }, new TypeError('Cannot assign to read only property \'logLevel\' of object \'#\'')) + t.throws(() => { req.routeOptions.version = '95.0.1' }, new TypeError('Cannot assign to read only property \'version\' of object \'#\'')) + t.throws(() => { req.routeOptions.prefixTrailingSlash = true }, new TypeError('Cannot assign to read only property \'prefixTrailingSlash\' of object \'#\'')) + t.throws(() => { req.routeOptions.newAttribute = {} }, new TypeError('Cannot add property newAttribute, object is not extensible')) + + for (const key of Object.keys(req.routeOptions)) { + if (typeof req.routeOptions[key] === 'object' && req.routeOptions[key] !== null) { + t.fail('Object.freeze must run recursively on nested structures to ensure that routeOptions is immutable.') + } + } + + res.send({}) + } + fastify.post('/', { + bodyLimit: 1000, + handler + }) + fastify.listen({ port: 0 }, function (err) { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'POST', + url: 'http://localhost:' + fastify.server.address().port, + headers: { 'Content-Type': 'application/json' }, + body: [], + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + }) + }) +}) + +test('test request.routeOptions.version', t => { + t.plan(7) + const fastify = Fastify() + + fastify.route({ + method: 'POST', + url: '/version', + constraints: { version: '1.2.0' }, + handler: function (request, reply) { + t.equal('1.2.0', request.routeOptions.version) + reply.send({}) + } + }) + + fastify.route({ + method: 'POST', + url: '/version-undefined', + handler: function (request, reply) { + t.equal(undefined, request.routeOptions.version) + reply.send({}) + } + }) + fastify.listen({ port: 0 }, function (err) { + t.error(err) + t.teardown(() => { fastify.close() }) + + sget({ + method: 'POST', + url: 'http://localhost:' + fastify.server.address().port + '/version', + headers: { 'Content-Type': 'application/json', 'Accept-Version': '1.2.0' }, + body: [], + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + }) + + sget({ + method: 'POST', + url: 'http://localhost:' + fastify.server.address().port + '/version-undefined', + headers: { 'Content-Type': 'application/json' }, + body: [], + json: true + }, (err, response, body) => { + t.error(err) + t.equal(response.statusCode, 200) + }) + }) +}) diff --git a/test/types/request.test-d.ts b/test/types/request.test-d.ts index 6b041dab30..5f5cd827e4 100644 --- a/test/types/request.test-d.ts +++ b/test/types/request.test-d.ts @@ -17,7 +17,7 @@ import fastify, { } from '../../fastify' import { RequestParamsDefault, RequestHeadersDefault, RequestQuerystringDefault } from '../../types/utils' import { FastifyLoggerInstance } from '../../types/logger' -import { FastifyRequest } from '../../types/request' +import { FastifyRequest, RequestRouteOptions } from '../../types/request' import { FastifyReply } from '../../types/reply' import { FastifyInstance } from '../../types/instance' import { RouteGenericInterface } from '../../types/route' @@ -66,6 +66,7 @@ const getHandler: RouteHandler = function (request, _reply) { expectType(request.method) expectType(request.routerPath) expectType(request.routerMethod) + expectType>(request.routeOptions) expectType(request.is404) expectType(request.hostname) expectType(request.ip) diff --git a/types/request.d.ts b/types/request.d.ts index 2944fca6d4..a23ba41f39 100644 --- a/types/request.d.ts +++ b/types/request.d.ts @@ -20,6 +20,17 @@ export interface ValidationFunction { errors?: null | ErrorObject[]; } +export interface RequestRouteOptions { + method: string, + url: string, + bodyLimit:number, + attachValidation:boolean, + logLevel:string, + version: string | undefined, + exposeHeadRoute: boolean, + prefixTrailingSlash: string +} + /** * FastifyRequest is an instance of the standard http or http2 request objects. * It defaults to http.IncomingMessage, and it also extends the relative request object. @@ -66,6 +77,7 @@ export interface FastifyRequest readonly is404: boolean; readonly socket: RawRequest['socket'];