Skip to main content

路由

路由

¥Routes

路由方法将配置应用的端点。你可以通过两种方式使用 Fastify 声明路由:简写方法和完整声明。

¥The route methods will configure the endpoints of your application. You have two ways to declare a route with Fastify: the shorthand method and the full declaration.

完整声明

¥Full declaration

fastify.route(options)

路由选项

¥Routes options

  • method:目前支持 GETHEADTRACEDELETEOPTIONSPATCHPUTPOST。要接受更多方法,必须使用 addHttpMethod。它也可以是一系列方法。

    ¥method: currently it supports GET, HEAD, TRACE, DELETE, OPTIONS, PATCH, PUT and POST. To accept more methods, the addHttpMethod must be used. It could also be an array of methods.

  • url:与此路由匹配的 URL 路径(别名:path)。

    ¥url: the path of the URL to match this route (alias: path).

  • schema:包含请求和响应模式的对象。它们需要采用 JSON 结构 格式,请查看 此处 了解更多信息。

    ¥schema: an object containing the schemas for the request and response. They need to be in JSON Schema format, check here for more info.

    • body:验证请求主体是否为 POST、PUT、PATCH、TRACE、SEARCH、PROPFIND、PROPPATCH 或 LOCK 方法。

      ¥body: validates the body of the request if it is a POST, PUT, PATCH, TRACE, SEARCH, PROPFIND, PROPPATCH or LOCK method.

    • querystringquery:验证查询字符串。这可以是一个完整的 JSON Schema 对象,具有 object 的属性 type 和参数的 properties 对象,或者仅仅是 properties 对象中包含的值,如下所示。

      ¥querystring or query: validates the querystring. This can be a complete JSON Schema object, with the property type of object and properties object of parameters, or simply the values of what would be contained in the properties object as shown below.

    • params:验证参数。

      ¥params: validates the params.

    • response:过滤并生成响应的模式,设置模式可以使我们的吞吐量提高 10-20%。

      ¥response: filter and generate a schema for the response, setting a schema allows us to have 10-20% more throughput.

  • exposeHeadRoute:为任何 GET 路由创建同级 HEAD 路由。默认为 exposeHeadRoutes 实例选项的值。如果你想要自定义 HEAD 处理程序而不禁用此选项,请确保在 GET 路由之前定义它。

    ¥exposeHeadRoute: creates a sibling HEAD route for any GET routes. Defaults to the value of exposeHeadRoutes instance option. If you want a custom HEAD handler without disabling this option, make sure to define it before the GET route.

  • attachValidation:如果存在模式验证错误,则将 validationError 附加到请求,而不是将错误发送到错误处理程序。默认的 错误格式 是 Ajv。

    ¥attachValidation: attach validationError to request, if there is a schema validation error, instead of sending the error to the error handler. The default error format is the Ajv one.

  • onRequest(request, reply, done):在收到请求后立即调用的 function,它也可以是一个函数数组。

    ¥onRequest(request, reply, done): a function called as soon as a request is received, it could also be an array of functions.

  • preParsing(request, reply, done):在解析请求之前调用的 function,它也可以是一个函数数组。

    ¥preParsing(request, reply, done): a function called before parsing the request, it could also be an array of functions.

  • preValidation(request, reply, done):在共享 preValidation 钩子之后调用的 function,如果你需要在路由级别执行身份验证,则很有用,它也可以是一个函数数组。

    ¥preValidation(request, reply, done): a function called after the shared preValidation hooks, useful if you need to perform authentication at route level for example, it could also be an array of functions.

  • preHandler(request, reply, done):在请求处理程序之前调用的 function,它也可以是一个函数数组。

    ¥preHandler(request, reply, done): a function called just before the request handler, it could also be an array of functions.

  • preSerialization(request, reply, payload, done):在序列化之前调用的 function,它也可以是一个函数数组。

    ¥preSerialization(request, reply, payload, done): a function called just before the serialization, it could also be an array of functions.

  • onSend(request, reply, payload, done):在发送响应之前调用的 function,它也可以是一个函数数组。

    ¥onSend(request, reply, payload, done): a function called right before a response is sent, it could also be an array of functions.

  • onResponse(request, reply, done):在发送响应后调用的 function,因此你将无法向客户端发送更多数据。它也可以是函数数组。

    ¥onResponse(request, reply, done): a function called when a response has been sent, so you will not be able to send more data to the client. It could also be an array of functions.

  • onTimeout(request, reply, done):在请求超时且 HTTP 套接字已挂断时调用的 function

    ¥onTimeout(request, reply, done): a function called when a request is timed out and the HTTP socket has been hung up.

  • onError(request, reply, error, done):当路由处理程序抛出错误或将错误发送给客户端时,调用 function

    ¥onError(request, reply, error, done): a function called when an Error is thrown or sent to the client by the route handler.

  • handler(request, reply):将处理此请求的函数。当调用处理程序时,Fastify 服务器 将绑定到 this。注意:使用箭头函数将破坏 this 的绑定。

    ¥handler(request, reply): the function that will handle this request. The Fastify server will be bound to this when the handler is called. Note: using an arrow function will break the binding of this.

  • errorHandler(error, request, reply):请求范围的自定义错误处理程序。覆盖默认错误全局处理程序以及 setErrorHandler 设置的任何内容,用于对路由的请求。要访问默认处理程序,你可以访问 instance.errorHandler。请注意,只有当插件尚未覆盖它时,它才会指向 fastify 的默认 errorHandler

    ¥errorHandler(error, request, reply): a custom error handler for the scope of the request. Overrides the default error global handler, and anything set by setErrorHandler, for requests to the route. To access the default handler, you can access instance.errorHandler. Note that this will point to fastify's default errorHandler only if a plugin hasn't overridden it already.

  • childLoggerFactory(logger, binding, opts, rawReq):一个自定义工厂函数,将被调用来为每个请求生成一个子日志器实例。请参阅 childLoggerFactory 了解更多信息。覆盖默认日志器工厂以及 setChildLoggerFactory 设置的任何内容,用于对路由的请求。要访问默认工厂,你可以访问 instance.childLoggerFactory。请注意,只有当插件尚未覆盖它时,它才会指向 Fastify 的默认 childLoggerFactory

    ¥childLoggerFactory(logger, binding, opts, rawReq): a custom factory function that will be called to produce a child logger instance for every request. See childLoggerFactory for more info. Overrides the default logger factory, and anything set by setChildLoggerFactory, for requests to the route. To access the default factory, you can access instance.childLoggerFactory. Note that this will point to Fastify's default childLoggerFactory only if a plugin hasn't overridden it already.

  • validatorCompiler({ schema, method, url, httpPart }):构建请求验证模式的函数。参见 验证和序列化 文档。

    ¥validatorCompiler({ schema, method, url, httpPart }): function that builds schemas for request validations. See the Validation and Serialization documentation.

  • serializerCompiler({ { schema, method, url, httpStatus, contentType } }):构建响应序列化模式的函数。参见 验证和序列化 文档。

    ¥serializerCompiler({ { schema, method, url, httpStatus, contentType } }): function that builds schemas for response serialization. See the Validation and Serialization documentation.

  • schemaErrorFormatter(errors, dataVar):格式化来自验证编译器的错误的函数。参见 验证和序列化 文档。覆盖全局架构错误格式化程序处理程序以及 setSchemaErrorFormatter 设置的任何内容,用于对路由的请求。

    ¥schemaErrorFormatter(errors, dataVar): function that formats the errors from the validation compiler. See the Validation and Serialization documentation. Overrides the global schema error formatter handler, and anything set by setSchemaErrorFormatter, for requests to the route.

  • bodyLimit:阻止默认 JSON 正文解析器解析大于此字节数的请求正文。必须是整数。你也可以在首次使用 fastify(options) 创建 Fastify 实例时全局设置此选项。默认为 1048576 (1 MiB)。

    ¥bodyLimit: prevents the default JSON body parser from parsing request bodies larger than this number of bytes. Must be an integer. You may also set this option globally when first creating the Fastify instance with fastify(options). Defaults to 1048576 (1 MiB).

  • logLevel:设置此路由的日志级别。见下文。

    ¥logLevel: set log level for this route. See below.

  • logSerializers:设置序列化器来记录此路由。

    ¥logSerializers: set serializers to log for this route.

  • config:用于存储自定义配置的对象。

    ¥config: object used to store custom configuration.

  • version:定义端点版本的 semver 兼容字符串。示例

    ¥version: a semver compatible string that defined the version of the endpoint. Example.

  • constraints:根据请求属性或值定义路由限制,使用 find-my-way 约束实现自定义匹配。包括内置的 versionhost 约束,并支持自定义约束策略。

    ¥constraints: defines route restrictions based on request properties or values, enabling customized matching using find-my-way constraints. Includes built-in version and host constraints, with support for custom constraint strategies.

  • prefixTrailingSlash:用于确定如何处理将 / 作为带有前缀的路由传递的字符串。

    ¥prefixTrailingSlash: string used to determine how to handle passing / as a route with a prefix.

    • both(默认):将注册 /prefix/prefix/

      ¥both (default): Will register both /prefix and /prefix/.

    • slash:将仅注册 /prefix/

      ¥slash: Will register only /prefix/.

    • no-slash:将仅注册 /prefix

      ¥no-slash: Will register only /prefix.

    注意:此选项不会覆盖 Server 配置中的 ignoreTrailingSlash

    ¥Note: this option does not override ignoreTrailingSlash in Server configuration.

  • request请求 中定义。

    ¥request is defined in Request.

  • reply响应 中定义。

    ¥reply is defined in Reply.

注意:onRequestpreParsingpreValidationpreHandlerpreSerializationonSendonResponse 的文档在 钩子 中有更详细的描述。此外,要在 handler 处理请求之前发送响应,请参考 响应来自钩子的请求

¥Notice: The documentation of onRequest, preParsing, preValidation, preHandler, preSerialization, onSend, and onResponse are described in more detail in Hooks. Additionally, to send a response before the request is handled by the handler please refer to Respond to a request from a hook.

示例:

¥Example:

fastify.route({
method: 'GET',
url: '/',
schema: {
querystring: {
type: 'object',
properties: {
name: { type: 'string' },
excitement: { type: 'integer' }
}
},
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
},
handler: function (request, reply) {
reply.send({ hello: 'world' })
}
})

速记声明

¥Shorthand declaration

上面的路由声明更像 Hapi,但如果你更喜欢 Express/Restify 方法,我们也支持它:

¥The above route declaration is more Hapi-like, but if you prefer an Express/Restify approach, we support it as well:

fastify.get(path, [options], handler)

fastify.head(path, [options], handler)

fastify.post(path, [options], handler)

fastify.put(path, [options], handler)

fastify.delete(path, [options], handler)

fastify.options(path, [options], handler)

fastify.patch(path, [options], handler)

示例:

¥Example:

const opts = {
schema: {
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
}
}
fastify.get('/', opts, (request, reply) => {
reply.send({ hello: 'world' })
})

fastify.all(path, [options], handler) 将向所有支持的方法添加相同的处理程序。

¥fastify.all(path, [options], handler) will add the same handler to all the supported methods.

处理程序也可以通过 options 对象提供:

¥The handler may also be supplied via the options object:

const opts = {
schema: {
response: {
200: {
type: 'object',
properties: {
hello: { type: 'string' }
}
}
}
},
handler: function (request, reply) {
reply.send({ hello: 'world' })
}
}
fastify.get('/', opts)

注意:如果在 options 和快捷方式方法的第三个参数中都指定了处理程序,则会抛出重复的 handler 错误。

¥Note: if the handler is specified in both the options and as the third parameter to the shortcut method then throws a duplicate handler error.

网址建设

¥Url building

Fastify 支持静态和动态 URL。

¥Fastify supports both static and dynamic URLs.

要注册参数路径,请在参数名称前使用冒号。对于通配符,请使用星号。请记住,静态路由始终在参数和通配符之前进行检查。

¥To register a parametric path, use the colon before the parameter name. For wildcard, use the star. Remember that static routes are always checked before parametric and wildcard.

// parametric
fastify.get('/example/:userId', function (request, reply) {
// curl ${app-url}/example/12345
// userId === '12345'
const { userId } = request.params;
// your code here
})
fastify.get('/example/:userId/:secretToken', function (request, reply) {
// curl ${app-url}/example/12345/abc.zHi
// userId === '12345'
// secretToken === 'abc.zHi'
const { userId, secretToken } = request.params;
// your code here
})

// wildcard
fastify.get('/example/*', function (request, reply) {})

也支持正则表达式路由,但请注意你必须转义斜杠。请注意,RegExp 在性能方面也非常昂贵!

¥Regular expression routes are supported as well, but be aware that you have to escape slashes. Take note that RegExp is also very expensive in terms of performance!

// parametric with regexp
fastify.get('/example/:file(^\\d+).png', function (request, reply) {
// curl ${app-url}/example/12345.png
// file === '12345'
const { file } = request.params;
// your code here
})

可以在同一对斜线("/")中定义多个参数。例如:

¥It is possible to define more than one parameter within the same couple of slash ("/"). Such as:

fastify.get('/example/near/:lat-:lng/radius/:r', function (request, reply) {
// curl ${app-url}/example/near/15°N-30°E/radius/20
// lat === "15°N"
// lng === "30°E"
// r ==="20"
const { lat, lng, r } = request.params;
// your code here
})

请记住在这种情况下使用破折号 ("*") 作为参数分隔符。

¥Remember in this case to use the dash ("-") as parameters separator.

最后,RegExp 可以有多个参数:

¥Finally, it is possible to have multiple parameters with RegExp:

fastify.get('/example/at/:hour(^\\d{2})h:minute(^\\d{2})m', function (request, reply) {
// curl ${app-url}/example/at/08h24m
// hour === "08"
// minute === "24"
const { hour, minute } = request.params;
// your code here
})

在这种情况下,可以使用正则表达式不匹配的任何字符作为参数分隔符。

¥In this case as parameter separator it is possible to use whatever character is not matched by the regular expression.

如果在参数名称末尾添加问号 ("?"),则可以将最后一个参数设为可选。

¥The last parameter can be made optional if you add a question mark ("?") to the end of the parameters name.

fastify.get('/example/posts/:id?', function (request, reply) {
const { id } = request.params;
// your code here
})

在这种情况下,你可以请求 /example/posts/example/posts/1。如果未指定,可选参数将是未定义的。

¥In this case you can request /example/posts as well as /example/posts/1. The optional param will be undefined if not specified.

具有多个参数的路由可能会对性能产生负面影响,因此尽可能首选单参数方法,尤其是在应用热路径上的路由上。如果你对我们如何处理路由感兴趣,请查看 find-my-way

¥Having a route with multiple parameters may negatively affect performance, so prefer a single parameter approach whenever possible, especially on routes that are on the hot path of your application. If you are interested in how we handle the routing, check out find-my-way.

如果你想要包含冒号的路径而不声明参数,请使用双冒号。例如:

¥If you want a path containing a colon without declaring a parameter, use a double colon. For example:

fastify.post('/name::verb') // will be interpreted as /name:verb

异步等待

¥Async Await

你是 async/await 用户吗?我们为你服务!

¥Are you an async/await user? We have you covered!

fastify.get('/', options, async function (request, reply) {
const data = await getData()
const processed = await processData(data)
return processed
})

如你所见,我们没有调用 reply.send 将数据发送回用户。你只需要归还尸体就可以了!

¥As you can see, we are not calling reply.send to send back the data to the user. You just need to return the body and you are done!

如果需要,你还可以使用 reply.send 将数据发送回用户。在这种情况下,不要忘记在你的 async 处理程序中使用 return replyawait reply,否则你将在某些情况下引入竞争条件。

¥If you need it you can also send back the data to the user with reply.send. In this case do not forget to return reply or await reply in your async handler or you will introduce a race condition in certain situations.

fastify.get('/', options, async function (request, reply) {
const data = await getData()
const processed = await processData(data)
return reply.send(processed)
})

如果路由封装了基于回调的 API,该 API 将在 promise 链之外调用 reply.send(),则可以 await reply

¥If the route is wrapping a callback-based API that will call reply.send() outside of the promise chain, it is possible to await reply:

fastify.get('/', options, async function (request, reply) {
setImmediate(() => {
reply.send({ hello: 'world' })
})
await reply
})

返回响应也有效:

¥Returning reply also works:

fastify.get('/', options, async function (request, reply) {
setImmediate(() => {
reply.send({ hello: 'world' })
})
return reply
})

警告:

¥Warning:

  • 当同时使用 return valuereply.send(value) 时,第一个发生的优先,第二个值将被丢弃,并且还会发出警告日志,因为你尝试发送两次响应。

    ¥When using both return value and reply.send(value) at the same time, the first one that happens takes precedence, the second value will be discarded, and a warn log will also be emitted because you tried to send a response twice.

  • 可以在 promise 之外调用 reply.send(),但需要特别注意。有关更多详细信息,请阅读 promise-resolution

    ¥Calling reply.send() outside of the promise is possible but requires special attention. For more details read promise-resolution.

  • 你无法返回 undefined。有关更多详细信息,请阅读 promise-resolution

    ¥You cannot return undefined. For more details read promise-resolution.

Promise 决议

¥Promise resolution

如果你的处理程序是 async 函数或返回 promise,你应该了解支持回调和 promise 控制流所必需的特殊行为。当处理程序的 promise 得到解决时,除非你在处理程序中明确等待或返回 reply,否则将自动发送回复及其值。

¥If your handler is an async function or returns a promise, you should be aware of the special behavior that is necessary to support the callback and promise control-flow. When the handler's promise is resolved, the reply will be automatically sent with its value unless you explicitly await or return reply in your handler.

  1. 如果你想要使用 async/await 或 promise,但使用 reply.send 的值进行响应:

    ¥If you want to use async/await or promises but respond with a value with reply.send:

    • 执行 return reply / await reply

      ¥Do return reply / await reply.

    • 不要忘记调用 reply.send

      ¥Do not forget to call reply.send.

  2. 如果你想要使用 async/await 或 promise:

    ¥If you want to use async/await or promises:

    • 不要使用 reply.send

      ¥Do not use reply.send.

    • 请返回你想要发送的值。

      ¥Do return the value that you want to send.

这样,我们可以同时支持 callback-styleasync-await,同时以最小的权衡取舍。尽管有如此多的自由,我们强烈建议仅使用一种样式,因为错误处理应该在应用中以一致的方式进行处理。

¥In this way, we can support both callback-style and async-await, with the minimum trade-off. Despite so much freedom we highly recommend going with only one style because error handling should be handled in a consistent way within your application.

注意:每个异步函数都会返回一个 promise。

¥Notice: Every async function returns a promise by itself.

路由前缀

¥Route Prefixing

有时你需要维护同一 API 的两个或多个不同版本;一种经典方法是在所有路由前面加上 API 版本号,例如 /v1/user。Fastify 为你提供了一种快速、智能的方式来创建同一 API 的不同版本,而无需手动更改所有路由名称、路由前缀。让我们看看它是如何工作的:

¥Sometimes you need to maintain two or more different versions of the same API; a classic approach is to prefix all the routes with the API version number, /v1/user for example. Fastify offers you a fast and smart way to create different versions of the same API without changing all the route names by hand, route prefixing. Let's see how it works:

// server.js
const fastify = require('fastify')()

fastify.register(require('./routes/v1/users'), { prefix: '/v1' })
fastify.register(require('./routes/v2/users'), { prefix: '/v2' })

fastify.listen({ port: 3000 })
// routes/v1/users.js
module.exports = function (fastify, opts, done) {
fastify.get('/user', handler_v1)
done()
}
// routes/v2/users.js
module.exports = function (fastify, opts, done) {
fastify.get('/user', handler_v2)
done()
}

Fastify 不会因为你对两个不同的路由使用相同的名称而抱怨,因为在编译时它会自动处理前缀(这也意味着性能根本不会受到影响!)。

¥Fastify will not complain because you are using the same name for two different routes, because at compilation time it will handle the prefix automatically (this also means that the performance will not be affected at all!).

现在你的客户将可以访问以下路由:

¥Now your clients will have access to the following routes:

  • /v1/user

  • /v2/user

你可以根据需要多次执行此操作,它也适用于嵌套 register,并且还支持路由参数。

¥You can do this as many times as you want, it also works for nested register, and route parameters are supported as well.

如果你想对所有路由使用前缀,你可以将它们放入插件中:

¥In case you want to use prefix for all of your routes, you can put them inside a plugin:

const fastify = require('fastify')()

const route = {
method: 'POST',
url: '/login',
handler: () => {},
schema: {},
}

fastify.register(function (app, _, done) {
app.get('/users', () => {})
app.route(route)

done()
}, { prefix: '/v1' }) // global route prefix

await fastify.listen({ port: 3000 })

路由前缀和 fastify-plugin

¥Route Prefixing and fastify-plugin

请注意,如果你使用 fastify-plugin 封装你的路由,此选项将不起作用。你仍然可以通过将插件封装在插件中来使其工作,例如。 G。:

¥Be aware that if you use fastify-plugin for wrapping your routes, this option will not work. You can still make it work by wrapping a plugin in a plugin, e. g.:

const fp = require('fastify-plugin')
const routes = require('./lib/routes')

module.exports = fp(async function (app, opts) {
app.register(routes, {
prefix: '/v1',
})
}, {
name: 'my-routes'
})

处理前缀插件内的 / 路由

¥Handling of / route inside prefixed plugins

/ 路由的行为取决于前缀是否以 / 结尾。例如,如果我们考虑前缀 /something/,则添加 / 路由将仅匹配 /something/。如果我们考虑前缀 /something,添加 / 路由将匹配 /something/something/

¥The / route has different behavior depending on if the prefix ends with / or not. As an example, if we consider a prefix /something/, adding a / route will only match /something/. If we consider a prefix /something, adding a / route will match both /something and /something/.

有关更多信息,请参阅上面的 prefixTrailingSlash 路由选项以更改此行为。

¥See the prefixTrailingSlash route option above to change this behavior.

自定义日志级别

¥Custom Log Level

你的路由中可能需要不同的日志级别;Fastify 以非常简单的方式实现了这一点。

¥You might need different log levels in your routes; Fastify achieves this in a very straightforward way.

你只需要将选项 logLevel 传递给插件选项或带有你需要的 value 的路由选项。

¥You just need to pass the option logLevel to the plugin option or the route option with the value that you need.

请注意,如果你在插件级别设置 logLevelsetNotFoundHandlersetErrorHandler 也会受到影响。

¥Be aware that if you set the logLevel at plugin level, also the setNotFoundHandler and setErrorHandler will be affected.

// server.js
const fastify = require('fastify')({ logger: true })

fastify.register(require('./routes/user'), { logLevel: 'warn' })
fastify.register(require('./routes/events'), { logLevel: 'debug' })

fastify.listen({ port: 3000 })

或者你可以直接将其传递给路由:

¥Or you can directly pass it to a route:

fastify.get('/', { logLevel: 'warn' }, (request, reply) => {
reply.send({ hello: 'world' })
})

请记住,自定义日志级别仅适用于路由,而不适用于可通过 fastify.log 访问的全局 Fastify Logger

¥Remember that the custom log level is applied only to the routes, and not to the global Fastify Logger, accessible with fastify.log

自定义日志序列化器

¥Custom Log Serializer

在某些情况下,你可能需要记录大型对象,但这可能会浪费某些路由的资源。在这种情况下,你可以定义自定义 serializers 并将它们附加到正确的上下文中!

¥In some contexts, you may need to log a large object but it could be a waste of resources for some routes. In this case, you can define custom serializers and attach them in the right context!

const fastify = require('fastify')({ logger: true })

fastify.register(require('./routes/user'), {
logSerializers: {
user: (value) => `My serializer one - ${value.name}`
}
})
fastify.register(require('./routes/events'), {
logSerializers: {
user: (value) => `My serializer two - ${value.name} ${value.surname}`
}
})

fastify.listen({ port: 3000 })

你可以通过上下文继承序列化器:

¥You can inherit serializers by context:

const fastify = Fastify({
logger: {
level: 'info',
serializers: {
user (req) {
return {
method: req.method,
url: req.url,
headers: req.headers,
host: req.host,
remoteAddress: req.ip,
remotePort: req.socket.remotePort
}
}
}
}
})

fastify.register(context1, {
logSerializers: {
user: value => `My serializer father - ${value}`
}
})

async function context1 (fastify, opts) {
fastify.get('/', (req, reply) => {
req.log.info({ user: 'call father serializer', key: 'another key' })
// shows: { user: 'My serializer father - call father serializer', key: 'another key' }
reply.send({})
})
}

fastify.listen({ port: 3000 })

配置

¥Config

注册一个新的处理程序,你可以将配置对象传递给它并在处理程序中检索它。

¥Registering a new handler, you can pass a configuration object to it and retrieve it in the handler.

// server.js
const fastify = require('fastify')()

function handler (req, reply) {
reply.send(reply.routeOptions.config.output)
}

fastify.get('/en', { config: { output: 'hello world!' } }, handler)
fastify.get('/it', { config: { output: 'ciao mondo!' } }, handler)

fastify.listen({ port: 3000 })

约束条件

¥Constraints

Fastify 支持根据请求的某些属性(如 Host 标头)或通过 find-my-way 约束的任何其他值)限制路由以仅匹配某些请求。约束在路由选项的 constraints 属性中指定。Fastify 有两个可供使用的内置约束:version 约束和 host 约束,你可以添加自己的自定义约束策略来检查请求的其他部分,以决定是否应为请求执行路由。

¥Fastify supports constraining routes to match only certain requests based on some property of the request, like the Host header, or any other value via find-my-way constraints. Constraints are specified in the constraints property of the route options. Fastify has two built-in constraints ready for use: the version constraint and the host constraint, and you can add your own custom constraint strategies to inspect other parts of a request to decide if a route should be executed for a request.

版本限制

¥Version Constraints

你可以在 constraints 选项中为路由提供 version 键。版本化路由允许你为相同的 HTTP 路由路径声明多个处理程序,然后根据每个请求的 Accept-Version 标头进行匹配。Accept-Version 标头值应遵循 semver 规范,并且应使用精确的 semver 版本声明路由以进行匹配。

¥You can provide a version key in the constraints option to a route. Versioned routes allow you to declare multiple handlers for the same HTTP route path, which will then be matched according to each request's Accept-Version header. The Accept-Version header value should follow the semver specification, and routes should be declared with exact semver versions for matching.

如果路由设置了版本,Fastify 将要求设置请求 Accept-Version 标头,并且对于同一路径,将优先使用版本化路由而不是非版本化路由。目前不支持高级版本范围和预发行版。

¥Fastify will require a request Accept-Version header to be set if the route has a version set, and will prefer a versioned route to a non-versioned route for the same path. Advanced version ranges and pre-releases currently are not supported.

请注意,使用此功能会导致路由整体性能下降。

¥Be aware that using this feature will cause a degradation of the overall performances of the router.

fastify.route({
method: 'GET',
url: '/',
constraints: { version: '1.2.0' },
handler: function (request, reply) {
reply.send({ hello: 'world' })
}
})

fastify.inject({
method: 'GET',
url: '/',
headers: {
'Accept-Version': '1.x' // it could also be '1.2.0' or '1.2.x'
}
}, (err, res) => {
// { hello: 'world' }
})

⚠ 安全须知

¥⚠ Security Notice

请记住在你的响应中设置 Vary 标头,并使用你用于定义版本控制的值(例如:'Accept-Version'),以防止缓存中毒攻击。你还可以将其配置为代理/CDN 的一部分。

¥Remember to set a Vary header in your responses with the value you are using for defining the versioning (e.g.: 'Accept-Version'), to prevent cache poisoning attacks. You can also configure this as part of your Proxy/CDN.

const append = require('vary').append
fastify.addHook('onSend', (req, reply, payload, done) => {
if (req.headers['accept-version']) { // or the custom header you are using
let value = reply.getHeader('Vary') || ''
const header = Array.isArray(value) ? value.join(', ') : String(value)
if ((value = append(header, 'Accept-Version'))) { // or the custom header you are using
reply.header('Vary', value)
}
}
done()
})

如果你声明具有相同主版本或次版本号的多个版本,Fastify 将始终选择与 Accept-Version 标头值兼容最高的版本。

¥If you declare multiple versions with the same major or minor, Fastify will always choose the highest compatible with the Accept-Version header value.

如果请求没有 Accept-Version 标头,则将返回 404 错误。

¥If the request will not have the Accept-Version header, a 404 error will be returned.

可以定义自定义版本匹配逻辑。这可以在创建 Fastify 服务器实例时通过 constraints 配置来完成。

¥It is possible to define a custom version matching logic. This can be done through the constraints configuration when creating a Fastify server instance.

主机限制

¥Host Constraints

你可以在 constraints 路由选项中提供 host 键,以限制该路由仅与请求 Host 标头的某些值匹配。host 约束值可以指定为精确匹配的字符串或任意主机匹配的 RegExp。

¥You can provide a host key in the constraints route option for to limit that route to only be matched for certain values of the request Host header. host constraint values can be specified as strings for exact matches or RegExps for arbitrary host matching.

fastify.route({
method: 'GET',
url: '/',
constraints: { host: 'auth.fastify.dev' },
handler: function (request, reply) {
reply.send('hello world from auth.fastify.dev')
}
})

fastify.inject({
method: 'GET',
url: '/',
headers: {
'Host': 'example.com'
}
}, (err, res) => {
// 404 because the host doesn't match the constraint
})

fastify.inject({
method: 'GET',
url: '/',
headers: {
'Host': 'auth.fastify.dev'
}
}, (err, res) => {
// => 'hello world from auth.fastify.dev'
})

还可以指定 RegExp host 约束,允许限制主机匹配通配符子域(或任何其他模式):

¥RegExp host constraints can also be specified allowing constraining to hosts matching wildcard subdomains (or any other pattern):

fastify.route({
method: 'GET',
url: '/',
constraints: { host: /.*\.fastify\.dev/ }, // will match any subdomain of fastify.dev
handler: function (request, reply) {
reply.send('hello world from ' + request.headers.host)
}
})

异步自定义约束

¥Asynchronous Custom Constraints

可以提供自定义约束,并且可以从另一个来源(例如 database)获取 constraint 标准。使用异步自定义约束应该是最后的手段,因为它会影响路由性能。

¥Custom constraints can be provided and the constraint criteria can be fetched from another source such as database. The use of asynchronous custom constraints should be a last resort as it impacts router performance.

function databaseOperation(field, done) {
done(null, field)
}

const secret = {
// strategy name for referencing in the route handler `constraints` options
name: 'secret',
// storage factory for storing routes in the find-my-way route tree
storage: function () {
let handlers = {}
return {
get: (type) => { return handlers[type] || null },
set: (type, store) => { handlers[type] = store }
}
},
// function to get the value of the constraint from each incoming request
deriveConstraint: (req, ctx, done) => {
databaseOperation(req.headers['secret'], done)
},
// optional flag marking if handlers without constraints can match requests that have a value for this constraint
mustMatchWhenDerived: true
}

⚠ 安全须知

¥⚠ Security Notice

与异步约束一起使用时。强烈建议不要在回调中返回错误。如果无法避免错误,建议提供自定义 frameworkErrors 处理程序来处理它。否则,你的路由选择可能会破坏敏感信息或向攻击者暴露敏感信息。

¥When using with asynchronous constraint. It is highly recommend never return error inside the callback. If the error is not preventable, it is recommended to provide a custom frameworkErrors handler to deal with it. Otherwise, you route selection may break or expose sensitive information to attackers.

const Fastify = require('fastify')

const fastify = Fastify({
frameworkErrors: function (err, res, res) {
if (err instanceof Fastify.errorCodes.FST_ERR_ASYNC_CONSTRAINT) {
res.code(400)
return res.send("Invalid header provided")
} else {
res.send(err)
}
}
})