再牛逼的梦想也抵不住傻逼似的坚持!   设为首页 - 加入收藏
您的当前位置:小鱼资料库 > 计算机 > fastify教程 > 正文

Fastify 路由

来源:网络 编辑:澜薄 时间:2022-06-17

路由

完整定义

fastify.route(options)

路由选项

  • method:支持的 HTTP 请求方法。目前支持 'DELETE'、'GET'、'HEAD'、'PATCH'、'POST'、'PUT' 以及 'OPTIONS'。它还可以是一个 HTTP 方法的数组。
  • url:路由匹配的 url 路径 (别名:path)。
  • schema:用于验证请求与回复的 schema 对象。 必须符合 JSON Schema 格式。请看这里了解更多信息。body:当为 POST 或 PUT 方法时,校验请求主体。querystring 或 query:校验 querystring。可以是一个完整的 JSON Schema 对象,它包括了值为 object 的 type 属性以及包含参数的 properties 对象,也可以仅仅是 properties 对象中的值 (见下文示例)。params:校验 url 参数。response:过滤并生成用于响应的 schema,能帮助提升 10-20% 的吞吐量。
  • attachValidation:当 schema 校验出错时,将一个 validationError 对象添加到请求中,否则错误将被发送给错误处理函数。
  • onRequest(request, reply, done): 每当接收到一个请求时触发的函数。可以是一个函数数组。
  • preParsing(request, reply, done): 解析请求前调用的函数。可以是一个函数数组。
  • preValidation(request, reply, done):在共享的 preValidation 钩子之后执行的函数,在路由层进行认证等场景中会有用处。可以是一个函数数组。
  • preHandler(request, reply, done):处理请求之前调用的函数。可以是一个函数数组。
  • preSerialization(request, reply, payload, done):序列化之前调用的函数。可以是一个函数数组。
  • onSend(request, reply, payload, done): 响应即将发送前调用的函数。可以是一个函数数组。
  • onResponse(request, reply, done): 当响应发送后调用的函数。因此,在这个函数内部,不允许再向客户端发送数据。可以是一个函数数组。
  • handler(request, reply):处理请求的函数。
  • schemaCompiler(schema):生成校验 schema 的函数。请看这里
  • bodyLimit:一个以字节为单位的整形数,默认值为 1048576 (1 MiB),防止默认的 JSON 解析器解析超过此大小的请求主体。你也可以通过 fastify(options),在首次创建 Fastify 实例时全局设置该值。
  • logLevel:设置日志级别。详见下文。
  • logSerializers:设置当前路由的日志序列化器。
  • config:存放自定义配置的对象。
  • version:一个符合语义化版本控制规范 (semver) 的字符串。示例。 prefixTrailingSlash:一个字符串,决定如何处理带前缀的 / 路由。both (默认值):同时注册 /prefix 与 /prefix/。slash:只会注册 /prefix/。no-slash:只会注册 /prefix。request 的相关内容请看 请求一文。reply 请看回复一文。

示例:

fastify.route({
    method: 'GET',
    url: '/',
    schema: {
      querystring: {
        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 也是支持的:

  • 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) 会给所有支持的 HTTP 方法添加相同的处理函数。

处理函数还可以写到 options 对象里:

const opts = {
    schema: {
      response: {
        200: {
          type: 'object',
          properties: {
            hello: { type: 'string' }
          }
        }
      }
    },
    handler (request, reply) {
      reply.send({ hello: 'world' })
    }
  }
  fastify.get('/', opts)
注:假如同时在 options 和简写方法的第三个参数里指明了处理函数,将会抛出重复的 handler 错误。

Url 构建

Fastify 同时支持静态与动态的 url。要注册一个参数命名的路径,请在参数名前加上冒号。星号表示*通配符**。 *注意,静态路由总是在参数路由和通配符之前进行匹配。

// 参数路由
  fastify.get('/example/:userId', (request, reply) => {}))
  fastify.get('/example/:userId/:secretToken', (request, reply) => {}))
  
  // 通配符
  fastify.get('/example/*', (request, reply) => {}))

正则表达式路由亦被支持。但要注意,正则表达式会严重拖累性能!

// 正则表达的参数路由
  fastify.get('/example/:file(^\d+).png', (request, reply) => {}))

你还可以在同一组斜杠 ("/") 里定义多个参数。就像这样:

fastify.get('/example/near/:lat-:lng/radius/:r', (request, reply) => {}))

使用短横线 ("-") 来分隔参数。

最后,同时使用多参数和正则表达式也是允许的。

fastify.get('/example/at/:hour(^\d{2})h:minute(^\d{2})m', (request, reply) => {}))

在这个例子里,任何未被正则匹配的符号均可作为参数的分隔符。

多参数的路由会影响性能,所以应该尽量使用单参数,对于高频访问的路由来说更是如此。 如果你对路由的底层感兴趣,可以查看find-my-way

Async Await

你是 async/await 的使用者吗?我们为你考虑了一切!

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

如你所见,我们不再使用 reply.send 向用户发送数据,只需返回消息主体就可以了!

当然,需要的话你还是可以使用 reply.send 发送数据。

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

假如在路由中,reply.send() 脱离了 promise 链,在一个基于回调的 API 中被调用,你可以使用 await reply:

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 value 与 reply.send(value),那么只会发送第一次,同时还会触发警告日志,因为你试图发送两次响应。
  • 不能返回 undefined。更多细节请看 promise 取舍

Promise 取舍

假如你的处理函数是一个 async 函数,或返回了一个 promise,请注意一种必须支持回调函数和 promise 控制流的特殊情况:如果 promise 被 resolve 为 undefined,请求会被挂起,并触发一个错误日志。

  1. 如果你想使用 async/await 或 promise,但通过 reply.send 返回值:别 return 任何值。别忘了 reply.send。
  2. 如果你想使用 async/await 或 promise:别使用 reply.send。别返回 undefined。

通过这一方法,我们便可以最小代价同时支持 回调函数风格 以及 async-await。尽管这么做十分自由,我们还是强烈建议仅使用其中的一种,因为应用的错误处理方式应当保持一致。

注意:每个 async 函数各自返回一个 promise 对象。

路由前缀

有时你需要维护同一 api 的多个不同版本。一般的做法是在所有的路由之前加上版本号,例如 /v1/user。 Fastify 提供了一个快捷且智能的方法来解决上述问题,无需手动更改全部路由。这就是路由前缀。让我们来看下吧:

// server.js
  const fastify = require('fastify')()
  
  fastify.register(require('./routes/v1/users'), { prefix: '/v1' })
  fastify.register(require('./routes/v2/users'), { prefix: '/v2' })
  
  fastify.listen(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 以及路由参数。 请注意,当使用了 fastify-plugin 时,这一选项是无效的。

处理带前缀的 / 路由

根据前缀是否以 / 结束,路径为 / 的路由的匹配模式有所不同。举例来说,前缀为 /something/ 的 / 路由只会匹配 something,而前缀为 /something 则会匹配 /something 和 /something/。

要改变这一行为,请见上文 prefixTrailingSlash 选项。

自定义日志级别

在 Fastify 中为路由里设置不同的日志级别是十分容易的。你只需在插件或路由的选项里设置 logLevel 为相应的即可。

要注意的是,如果在插件层面上设置了 logLevel,那么 setNotFoundHandler 和 setErrorHandler 也会受到影响。

// server.js
  const fastify = require('fastify')({ logger: true })
  
  fastify.register(require('./routes/user'), { logLevel: 'warn' })
  fastify.register(require('./routes/events'), { logLevel: 'debug' })
  
  fastify.listen(3000)

你也可以直接将其传给路由:

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

自定义的日志级别仅对路由生效,通过 fastify.log 访问的全局日志并不会受到影响。

自定义日志序列化器

在某些上下文里,你也许需要记录一个大型对象,但这在其他路由中是个负担。这时,你可以定义一些序列化器 (serializer),并将它们设置在正确的上下文之上!

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(3000)

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

const fastify = Fastify({ 
    logger: {
      level: 'info',
      serializers: {
        user (req) {
          return {
            method: req.method,
            url: req.url,
            headers: req.headers,
            hostname: req.hostname,
            remoteAddress: req.ip,
            remotePort: req.connection.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(3000)

配置

注册一个新的处理函数,你可以向其传递一个配置对象,并在其中使用它。

// server.js
  const fastify = require('fastify')()
  
  function handler (req, reply) {
    reply.send(reply.context.config.output)
  }
  
  fastify.get('/en', { config: { output: 'hello world!' } }, handler)
  fastify.get('/it', { config: { output: 'ciao mondo!' } }, handler)
  
  fastify.listen(3000)

版本

默认

需要的话,你可以提供一个版本选项,它允许你为同一个路由声明不同的版本。版本号请遵循 semver 规范。Fastify 会自动检测 Accept-Version header,并将请求分配给相应的路由 (当前尚不支持 semver 规范中的 advanced ranges 与 pre-releases 语法)。请注意,这一特性会降低路由的性能。

fastify.route({
    method: 'GET',
    url: '/',
    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' }
  })

如果你声明了多个拥有相同主版本或次版本号的版本,Fastify 总是会根据 Accept-Version header 的值选择最兼容的版本。假如请求未带有 Accept-Version header,那么将返回一个 404 错误。

自定义

新建实例时,可以通过设置 versioning 来自定义版本号逻辑。

上一篇:Fastify 服务器方法
下一篇:Fastify 日志

小鱼资料库 www.xiaoyuzl.com

Copyright © 2020-2022 XIAOYUZL. All rights reserved. 冀ICP备2020029262号-2

声明:本站分享的文章、资源等均由网友上传,版权归原作者所有,只用于搜集整理。如有侵权,请您与站长联系,我们将及时处理!

Top