Skip to Content

路由

路由方法配置应用程序的端点。可以通过简写方法或完整声明来定义路由。

完整声明

fastify.route(options)

路由选项

  • method: 当前支持的请求方法包括 GET, HEAD, TRACE, DELETE, OPTIONS, PATCH, PUTPOST。若要接受更多方法, 需使用 addHttpMethod。 它也可以是一个包含多个方法的数组。

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

  • schema: 包含请求和响应模式的对象。它们需要遵循 JSON Schema  格式,更多详情请参阅 此处

  • body:如果请求方法为 POST、PUT、PATCH、TRACE、SEARCH、PROPFIND、PROPPATCH 或 LOCK,则验证请求体。

    • querystringquery:验证查询字符串。这可以是一个完整的 JSON Schema 对象,具有类型属性为 object 和参数对象的 properties 属性,也可以只是包含在 properties 对象中的值,如下所示。
    • params:验证参数。
    • response:过滤并生成响应模式,设置模式可以使我们获得 10-20% 的额外吞吐量。
  • exposeHeadRoute:为任何 GET 路由创建一个同级 HEAD 路由。默认值为实例选项 exposeHeadRoutes 的值。如果您希望在不禁用此选项的情况下使用自定义的 HEAD 处理程序,请确保在 GET 路由之前定义它。

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

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

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

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

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

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

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

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

  • onResponse(request, reply, done): 当响应已经发送后调用的 函数,此时无法向客户端发送更多数据。也可以是一个函数数组。

  • onTimeout(request, reply, done): 请求超时且 HTTP 套接字断开连接时调用的 函数

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

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

  • errorHandler(error, request, reply): 请求范围内的自定义错误处理程序。覆盖默认全局错误处理程序,并且任何由 setErrorHandler 设置的处理程序,对于路由请求也是如此。要访问默认处理程序,请使用 instance.errorHandler 访问。注意:这将指向 fastify 的默认 errorHandler 只有在没有插件覆盖的情况下。

  • childLoggerFactory(logger, binding, opts, rawReq): 用于每个请求生成子日志实例的自定义工厂函数。有关更多信息,请参见 childLoggerFactory。覆盖默认的日志工厂,并且任何由 setChildLoggerFactory 设置的工厂,对于路由请求也是如此。要访问默认工厂,请使用

覆盖默认的日志工厂,并且任何由 setChildLoggerFactory 设置的内容,对于路由的请求。要访问默认工厂,可以使用 instance.childLoggerFactory 访问。请注意,这将指向 Fastify 的默认 childLoggerFactory,前提是插件尚未覆盖它。

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

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

  • schemaErrorFormatter(errors, dataVar): 格式化验证编译器中的错误信息的函数。请参阅 验证和序列化 文档。覆盖全局模式错误格式化处理器,并且任何由 setSchemaErrorFormatter 设置的内容,对于路由的请求。

  • bodyLimit: 防止默认 JSON 请求体解析器解析超过此字节数的请求体。必须是整数。您也可以在创建 Fastify 实例时使用 fastify(options) 全局设置此选项,默认值为 1048576(1 MiB)。

  • logLevel: 设置该路由的日志级别。请参阅下方说明。

  • logSerializers: 为此路由设置日志序列化器。

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

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

  • constraints: 根据请求属性或值定义路由限制,使用 find-my-way  约束实现自定义匹配。包括

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

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

    • both(默认):将同时注册 /prefix/prefix/
    • slash:仅注册 /prefix/
    • no-slash:仅注册 /prefix

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

  • requestRequest 中定义。

  • replyReply 中定义。

ℹ️ 注意:关于 onRequest, preParsing, preValidation, preHandler, preSerialization, onSend, 和 onResponse 的文档在 Hooks 中详细说明。要在请求由 handler 处理之前发送响应,请参阅 从钩子响应请求

示例:

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

简写声明

上述路由声明更类似于 Hapi,但如果你喜欢使用 Express/Restify 的方式,我们也支持:

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)

示例:

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) 将相同的处理器添加到所有支持的方法中。

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

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

ℹ️ 注意:在 options 和快捷方法的第三个参数中同时指定处理器会抛出重复的 handler 错误。

URL构建

Fastify 支持静态和动态URL。

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

// 参数化 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 }) // 通配符 fastify.get('/example/*', function (request, reply) {})

正则表达式路由是受支持的,但斜杠必须转义。 请注意,正则表达式的性能开销非常大!

// 带正则表达式的参数化路径 fastify.get('/example/:file(^\\d+).png', function (request, reply) { // curl ${app-url}/example/12345.png // file === '12345' const { file } = request.params; // your code here })

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

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; // 在这里编写代码 })

请注意,在这种情况下,必须使用破折号(”-“)作为参数分隔符。

最后,可以使用正则表达式定义多个参数:

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; // 在这里编写代码 })

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

最后一个参数可以通过在参数名末尾添加问号(”?”)来使其变为可选。

fastify.get('/example/posts/:id?', function (request, reply) { const { id } = request.params; // 在这里编写代码 })

在这种情况下,/example/posts/example/posts/1 都是有效的。如果未指定,则可选参数将为 undefined

具有多个参数的路由可能会对性能产生负面影响。 建议使用单个参数的方法,特别是在应用程序热点路径上的路由上。有关更多详细信息,请参阅 find-my-way 

要在路径中包含冒号而不声明参数时,可以使用双冒号。 例如:

fastify.post('/name::verb') // 将被解释为 /name:verb

异步/等待

你是 async/await 用户吗?我们为你提供支持!

fastify.get('/', options, async function (request, reply) { const data = await getData() const processed = await processData(data) return processed }) `` 如上所示,不需要调用 `reply.send` 将数据发送回用户。只需直接返回响应体即可! 如果需要的话,你也可以使用 `reply.send` 发送数据。在这种情况下,请不要忘记在异步处理程序中返回 `return reply` 或者 `await reply` 以避免竞态条件。 ```js 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`: ```js fastify.get('/', options, async function (request, reply) { setImmediate(() => { reply.send({ hello: 'world' }) }) await reply })

返回回复也有效:

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

⚠ 警告:

  • 当同时使用 return valuereply.send(value) 时,前者优先级更高,后者会被丢弃,并且会发出一个警告日志。
  • 在 promise 链之外调用 reply.send() 是可能的,但需要特别注意。请参阅 promise-resolution
  • 不可以返回 undefined。请参阅 promise-resolution

Promise 解决

如果处理器是一个 async 函数或返回一个承诺,请注意支持回调和承诺控制流的特殊行为。当处理器的承诺解析时,除非您在处理器中显式地等待或返回 reply,否则回复将自动发送其值。

  1. 如果使用 async/await 或承诺但用 reply.send 响应:
    • return reply / await reply
    • 不做:不要忘记调用 reply.send
  2. 如果使用 async/await 或承诺:
    • 不做:不要使用 reply.send
    • :返回要发送的值。

这种方法支持 callback-styleasync-await,并且折衷最小。然而,建议在应用程序中仅使用一种风格以保持一致的错误处理方式。

ℹ️ 注意:每个异步函数本身都会返回一个承诺。

路由前缀

有时需要维护同一 API 的多个版本。一种常见的方法是使用 API 版本号作为路由的前缀,例如 /v1/user。Fastify 提供了一种快速且智能的方式来创建相同 API 的不同版本,而无需手动更改所有路由名称,这种方法称为 路由前缀。以下是其工作原理:

// 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 不会因为使用相同的名称来定义两个不同的路由而报错,因为它在编译时自动处理前缀。这确保了性能不受影响。

现在客户端可以访问以下路由:

  • /v1/user
  • /v2/user

这种方法可以多次使用,并且适用于嵌套的 register 方法。路由参数也得到了支持。

要为所有路由设置一个公共前缀,请将其放在插件内部:

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' }) // 全局路由前缀 await fastify.listen({ port: 3000 })

路由前缀和 fastify-plugin

如果使用fastify-plugin来封装路由,此选项将无法工作。为了使其生效,请在插件中再封装一个插件:

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

前缀插件中的 / 路由处理

/ 路径的行为取决于前缀是否以 / 结尾。例如,对于前缀 /something/,添加一个 / 路径仅匹配 /something/。而对于前缀 /something,添加一个 / 路径会同时匹配 /something/something/

要更改此行为,请参见上面的 prefixTrailingSlash 路由选项。

自定义日志级别

在 Fastify 中为路由设置不同的日志级别,可以通过将 logLevel 选项传递给插件或具有所需值的路由来实现: value 

请注意,在插件级别设置 logLevel 还会影响 setNotFoundHandlersetErrorHandler

// 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 })

或者直接传递给路由:

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

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

自定义日志序列化器

在某些情况下,记录大型对象可能会浪费资源。定义自定义的 serializers 并在适当上下文中使用它们。

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

序列化器可以被上下文继承:

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' }) // 显示:{ user: 'My serializer father - call father serializer', key: 'another key' } reply.send({}) }) } fastify.listen({ port: 3000 })

配置

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

// 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 })

限制条件

Fastify 支持根据如 Host 标头或其他任何值的属性来匹配特定请求,从而对路由进行约束。这可以通过 find-my-way 约束实现。 约束在路由选项的 constraints 属性中指定。Fastify 内置了两种约束:versionhost。可以添加自定义约束策略来检查请求的其他部分,以决定是否执行该路由。

版本约束

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

如果设置了版本号,则 Fastify 将要求请求中设置一个 Accept-Version 头,对于相同的路径,Fastify 会优先选择带版本号的路由而不是不带版本号的路由。目前尚不支持高级版本范围和预发布版本。

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

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' // 它也可以是 '1.2.0' 或 '1.2.x' } }, (err, res) => { // { hello: 'world' } })

⚠ 警告: 在响应中设置一个 Vary 头,值为用于版本化的头(例如 'Accept-Version'),以防止缓存中毒攻击。 这也可以在代理或 CDN 中进行配置。

const append = require('vary').append fastify.addHook('onSend', (req, reply, payload, done) => { if (req.headers['accept-version']) { // 或使用的自定义头 let value = reply.getHeader('Vary') || '' const header = Array.isArray(value) ? value.join(', ') : String(value) if ((value = append(header, 'Accept-Version'))) { // 或使用的自定义头 reply.header('Vary', value) } } done() })

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

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

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

主机限制

在路由选项的 constraints 中提供一个 host 键,以将该路由限制为请求 Host 标头中的特定值。可以通过字符串指定精确匹配或通过正则表达式指定任意主机匹配。

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 }) fastify.inject({ method: 'GET', url: '/', headers: { 'Host': 'auth.fastify.dev' } }, (err, res) => { // 返回 'hello world from auth.fastify.dev' })

正则表达式 host 约束也可以指定,允许限制匹配通配符子域(或其他任何模式)的主机:

fastify.route({ method: 'GET', url: '/', constraints: { host: /.*\.fastify\.dev/ }, // 匹配 fastify.dev 的任意子域 handler: function (request, reply) { reply.send('hello world from ' + request.headers.host) } })

自定义异步约束

可以提供自定义约束,并从其他来源(如数据库)获取 constraint 条件。将异步自定义约束作为最后的手段,因为它们会影响路由器性能。

function databaseOperation(field, done) { done(null, field) } const secret = { // 路由处理程序中引用策略名称的名称 name: 'secret', // 存储工厂用于在 find-my-way 路由树中存储路由 storage: function () { let handlers = {} return { get: (type) => { return handlers[type] || null }, set: (type, store) => { handlers[type] = store } } }, // 从每个传入请求获取约束值的函数 deriveConstraint: (req, ctx, done) => { databaseOperation(req.headers['secret'], done) }, // 可选标志,标记没有约束的处理器是否可以匹配具有此约束值的请求 mustMatchWhenDerived: true }

⚠ 警告: 使用异步约束时,请避免在回调中返回错误。如果无法避免错误,则提供自定义 frameworkErrors 处理程序来管理它们,否则路由选择可能会中断或暴露敏感信息。

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("提供的标头无效") } else { res.send(err) } } })