Fastify

    通过钩子方法,你可以与 Fastify 的生命周期直接进行交互。有用于请求/响应的钩子,也有应用级钩子:

    注意:使用 async/await 或返回一个 Promise 时,done 回调不可用。在这种情况下,仍然使用 done 可能会导致难以预料的行为,例如,处理函数的重复调用。

    Reply 是 Fastify 核心的对象。
    done 是调用下一阶段的函数。

    生命周期一文清晰地展示了各个钩子执行的位置。
    钩子可被封装,因此可以运用在特定的路由上。更多信息请看一节。

    在请求/响应中,有八个可用的钩子 (按执行顺序排序)

    或使用 async/await

    1. fastify.addHook('onRequest', async (request, reply) => {
    2. // 其他代码
    3. await asyncMethod()
    4. })

    注意:在 onRequest 钩子中,request.body 的值总是 null,这是因为 body 的解析发生在 钩子之前。

    preParsing

    preParsing 钩子让你能在解析请求之前转换它们。它的参数除了和其他钩子一样的请求与响应对象外,还多了一个当前请求 payload 的 stream。

    需要通过 return 或回调函数返回值的话,必须返回一个 stream。

    例如,你可以解压请求的 body:

    1. fastify.addHook('preParsing', (request, reply, payload, done) => {
    2. // 其他代码
    3. done(null, newPayload)
    4. })

    或使用 async/await

    1. fastify.addHook('preParsing', async (request, reply, payload) => {
    2. // 其他代码
    3. await asyncMethod()
    4. return newPayload
    5. })

    注意:在 钩子中,request.body 的值总是 null,这是因为 body 的解析发生在 preValidation 钩子之前。

    注意:你应当给返回的 stream 添加 receivedEncodedLength 属性。这是为了通过比对请求头的 Content-Length,来精确匹配请求的 payload。理想情况下,每收到一块数据都应该更新该属性。

    注意:早先的写法 function(request, reply, done)function(request, reply) 仍被支持,但不推荐使用。

    preValidation

    使用 preValidation 钩子时,你可以在校验前修改 payload。示例如下:

    1. fastify.addHook('preValidation', (request, reply, done) => {
    2. req.body = { ...req.body, importantKey: 'randomString' }
    3. done()
    4. })

    或使用 async/await

    1. fastify.addHook('preValidation', async (request, reply) => {
    2. const importantKey = await generateRandomString()
    3. req.body = { ...req.body, importantKey }
    4. })

    preHandler

    1. fastify.addHook('preHandler', (request, reply, done) => {
    2. // 其他代码
    3. done()
    4. })
    1. fastify.addHook('preHandler', async (request, reply) => {
    2. // 其他代码
    3. await asyncMethod()
    4. })

    preSerialization

    preSerialization 钩子让你可以在 payload 被序列化之前改动 (或替换) 它。举个例子:

    1. fastify.addHook('preSerialization', (request, reply, payload, done) => {
    2. const err = null
    3. const newPayload = { wrapped: payload }
    4. done(err, newPayload)
    5. })

    或使用 async/await

    1. fastify.addHook('preSerialization', async (request, reply, payload) => {
    2. return { wrapped: payload }
    3. })

    注:payload 为 stringBufferstreamnull 时,该钩子不会被调用。

    1. fastify.addHook('onError', (request, reply, error, done) => {
    2. // 其他代码
    3. done()
    4. })

    或使用 async/await

    onError 钩子可用于自定义错误日志,或当发生错误时添加特定的 header。
    该钩子并不是为了变更错误而设计的,且调用 reply.send 会抛出一个异常。
    它只会在 customErrorHandler 向用户发送错误之后被执行 (要注意的是,默认的 customErrorHandler 总是会发送错误)。 注意:与其他钩子不同,onError 不支持向 done 函数传递错误。

    onSend

    使用 onSend 钩子可以改变 payload。例如:

    1. fastify.addHook('onSend', (request, reply, payload, done) => {
    2. const err = null;
    3. const newPayload = payload.replace('some-text', 'some-new-text')
    4. done(err, newPayload)
    5. })

    或使用 async/await

    1. fastify.addHook('onSend', async (request, reply, payload) => {
    2. const newPayload = payload.replace('some-text', 'some-new-text')
    3. return newPayload
    4. })

    你也可以通过将 payload 置为 null,发送一个空消息主体的响应:

    1. fastify.addHook('onSend', (request, reply, payload, done) => {
    2. reply.code(304)
    3. const newPayload = null
    4. done(null, newPayload)
    5. })

    注:你只能将 payload 修改为 stringBufferstreamnull

    onResponse

    1. fastify.addHook('onResponse', (request, reply, done) => {
    2. // 其他代码
    3. done()
    4. })

    或使用 async/await

    1. fastify.addHook('onResponse', async (request, reply) => {
    2. // 其他代码
    3. await asyncMethod()
    4. })

    onResponse 钩子在响应发出后被执行,因此在该钩子中你无法再向客户端发送数据了。但是你可以在此向外部服务发送数据,比如收集数据。

    onTimeout

    1. fastify.addHook('onTimeout', (request, reply, done) => {
    2. // 其他代码
    3. done()
    4. })

    Or async/await:

    1. // 其他代码
    2. await asyncMethod()

    onTimeout 用于监测请求超时,需要在 Fastify 实例上设置 connectionTimeout 属性。当请求超时,socket 挂起 (hang up) 时,该钩子执行。因此,在这个钩子里不能再向客户端发送数据了。

    在钩子中管理错误

    在钩子的执行过程中如果发生了错误,只需将错误传递给 done(),Fastify 就会自动关闭请求,并发送一个相应的错误码给用户。

    1. fastify.addHook('onRequest', (request, reply, done) => {
    2. done(new Error('Some error'))
    3. })

    如果你想自定义发送给用户的错误码,使用 reply.code() 即可:

    1. fastify.addHook('preHandler', (request, reply, done) => {
    2. reply.code(400)
    3. done(new Error('Some error'))
    4. })

    或者在 async/await 函数中抛出错误:

    1. fastify.addHook('onResponse', async (request, reply) => {
    2. throw new Error('Some error')
    3. })

    需要的话,你可以在路由函数执行前响应一个请求,例如进行身份验证。在钩子中响应暗示着钩子的调用链被 终止,剩余的钩子将不会执行。假如钩子使用回调的方式,意即不是 async 函数,也没有返回 Promise,那么只需要调用 reply.send(),并且避免触发回调便可。假如钩子是 async 函数,那么 reply.send() 必须 发生在函数返回或 promise resolve 之前,否则请求将会继续下去。当 reply.send() 在 promise 调用链之外被调用时,需要 return reply,不然请求将被执行两次。

    不应当混用回调与 async/Promise,否则钩子的调用链会被执行两次。

    如果你在 onRequestpreHandler 中发出响应,请使用 reply.send

    如果你想要使用流 (stream) 来响应请求,你应该避免使用 async 函数。必须使用 async 函数的话,请参考 test/hooks-async.js 中的示例来编写代码。

    1. fastify.addHook('onRequest', (request, reply, done) => {
    2. const stream = fs.createReadStream('some-file', 'utf8')
    3. reply.send(stream)
    4. })

    如果发出响应但没有 await 关键字,请确保总是 return reply

    1. fastify.addHook('preHandler', async (request, reply) => {
    2. setImmediate(() => { reply.send('hello') })
    3. // 让处理函数等待 promise 链之外发出的响应
    4. return reply
    5. })
    6. fastify.addHook('preHandler', async (request, reply) => {
    7. // fastify-static 插件异步地发送文件,因此需要 return reply
    8. reply.sendFile('myfile')
    9. return reply
    10. })

    你也可以在应用的生命周期里使用钩子方法。要格外注意的是,这些钩子并未被完全封装。钩子中的 this 得到了封装,但处理函数可以响应封装界线外的事件。

    onReady

    在服务器开始监听请求之前触发。在此你无法更改路由,或添加新的钩子。注册的 onReady 钩子函数串行执行,只有全部执行完毕时,服务器才会开始监听请求。钩子接受一个回调函数作为参数:done,在钩子函数完成后调用。钩子的 this 为 Fastify 实例。

    1. // 回调写法
    2. fastify.addHook('onReady', function (done) {
    3. // 其他代码
    4. const err = null;
    5. done(err)
    6. })
    7. // 或 async/await
    8. fastify.addHook('onReady', async function () {
    9. // 异步代码
    10. await loadCacheFromDatabase()
    11. })

    onClose

    使用 fastify.close() 停止服务器时被触发。当需要一个 “shutdown” 事件时有用,例如关闭一个数据库连接。
    该钩子的第一个参数是 Fastify 实例,第二个为 done 回调函数。

    1. fastify.addHook('onClose', (instance, done) => {
    2. // 其他代码
    3. done()
    4. })

    onRoute

    当注册一个新的路由时被触发。它的监听函数拥有一个唯一的参数:routeOptions 对象。该函数是同步的,其本身并不接受回调作为参数。

    1. fastify.addHook('onRoute', (routeOptions) => {
    2. // 其他代码
    3. routeOptions.method
    4. routeOptions.schema
    5. routeOptions.url // 路由的完整 URL,包括前缀
    6. routeOptions.path // `url` 的别名
    7. routeOptions.routePath // 无前缀的 URL
    8. routeOptions.bodyLimit
    9. routeOptions.logLevel
    10. routeOptions.logSerializers
    11. routeOptions.prefix
    12. })

    如果在编写插件时,需要自定义程序的路由,比如修改选项或添加新的路由层钩子,你可以在这里添加。

    1. fastify.addHook('onRoute', (routeOptions) => {
    2. function onPreSerialization(request, reply, payload, done) {
    3. // 其他代码
    4. done(null, payload)
    5. }
    6. // preSerialization 可以是数组或 undefined
    7. routeOptions.preSerialization = [...(routeOptions.preSerialization || []), onPreSerialization]
    8. })

    onRegister

    当注册一个新的插件,或创建了新的封装好的上下文后被触发。该钩子在注册的代码之前被执行。
    当你的插件需要知晓上下文何时创建完毕,并操作它们时,可以使用这一钩子。
    注意:被 fastify-plugin 所封装的插件不会触发该钩子。

    1. fastify.decorate('data', [])
    2. fastify.register(async (instance, opts) => {
    3. instance.data.push('hello')
    4. console.log(instance.data) // ['hello']
    5. instance.register(async (instance, opts) => {
    6. instance.data.push('world')
    7. console.log(instance.data) // ['hello', 'world']
    8. }), { prefix: '/hola' })
    9. }), { prefix: '/ciao' })
    10. fastify.register(async (instance, opts) => {
    11. console.log(instance.data) // []
    12. }), { prefix: '/hello' })
    13. fastify.addHook('onRegister', (instance, opts) => {
    14. // 从旧数组浅拷贝,
    15. // 生成一个新数组,
    16. // 使用户获得一个
    17. // 封装好的 `data` 的实例
    18. instance.data = instance.data.slice()
    19. // 新注册实例的选项
    20. console.log(opts.prefix)
    21. })

    除了,所有的钩子都是封装好的。这意味着你可以通过 register 来决定在何处运行它们,正如插件指南所述。如果你传递一个函数,那么该函数会获得 Fastify 的上下文,如此你便能使用 Fastify 的 API 了。

    1. fastify.addHook('onRequest', function (request, reply, done) {
    2. done()
    3. })

    要注意的是,每个钩子内的 Fastify 上下文都和注册路由时的插件一样,举例如下:

    1. fastify.addHook('onRequest', async function (req, reply) {
    2. if (req.raw.url === '/nested') {
    3. assert.strictEqual(this.foo, 'bar')
    4. } else {
    5. assert.strictEqual(this.foo, undefined)
    6. }
    7. })
    8. assert.strictEqual(this.foo, undefined)
    9. return { hello: 'world' }
    10. })
    11. fastify.register(async function plugin (fastify, opts) {
    12. fastify.decorate('foo', 'bar')
    13. fastify.get('/nested', async function (req, reply) {
    14. assert.strictEqual(this.foo, 'bar')
    15. return { hello: 'world' }
    16. })
    17. })

    提醒:使用的话,this 将不会是 Fastify,而是当前的作用域。

    你可以为单个路由声明一个或多个自定义的生命周期钩子 (onRequest、、preParsing、、preHandler、、onSend、 与 onError)。 如果你这么做,这些钩子总是会作为同一类钩子中的最后一个被执行。
    当你需要进行认证时,这会很有用,而 与 preValidation 钩子正是为此而生。 你也可以通过数组定义多个路由层钩子。

    1. fastify.addHook('onRequest', (request, reply, done) => {
    2. // 你的代码
    3. done()
    4. })
    5. fastify.addHook('onResponse', (request, reply, done) => {
    6. // 你的代码
    7. done()
    8. })
    9. fastify.addHook('preParsing', (request, reply, done) => {
    10. // 你的代码
    11. done()
    12. })
    13. fastify.addHook('preValidation', (request, reply, done) => {
    14. // 你的代码
    15. done()
    16. })
    17. fastify.addHook('preHandler', (request, reply, done) => {
    18. // 你的代码
    19. done()
    20. })
    21. fastify.addHook('preSerialization', (request, reply, payload, done) => {
    22. // 你的代码
    23. done(null, payload)
    24. })
    25. fastify.addHook('onSend', (request, reply, payload, done) => {
    26. // 你的代码
    27. done(null, payload)
    28. })
    29. fastify.addHook('onTimeout', (request, reply, done) => {
    30. // 你的代码
    31. done()
    32. })
    33. fastify.addHook('onError', (request, reply, error, done) => {
    34. // 你的代码
    35. done()
    36. })
    37. fastify.route({
    38. method: 'GET',
    39. url: '/',
    40. schema: { ... },
    41. onRequest: function (request, reply, done) {
    42. // 该钩子总是在共享的 `onRequest` 钩子后被执行
    43. done()
    44. },
    45. onResponse: function (request, reply, done) {
    46. // 该钩子总是在共享的 `onResponse` 钩子后被执行
    47. done()
    48. },
    49. preParsing: function (request, reply, done) {
    50. // 该钩子总是在共享的 `preParsing` 钩子后被执行
    51. done()
    52. },
    53. preValidation: function (request, reply, done) {
    54. // 该钩子总是在共享的 `preValidation` 钩子后被执行
    55. done()
    56. },
    57. preHandler: function (request, reply, done) {
    58. // 该钩子总是在共享的 `preHandler` 钩子后被执行
    59. done()
    60. },
    61. // // 使用数组的例子。所有钩子都支持这一语法。
    62. //
    63. // preHandler: [function (request, reply, done) {
    64. // // 该钩子总是在共享的 `preHandler` 钩子后被执行
    65. // done()
    66. // }],
    67. preSerialization: (request, reply, payload, done) => {
    68. // 该钩子总是在共享的 `preSerialization` 钩子后被执行
    69. done(null, payload)
    70. },
    71. onSend: (request, reply, payload, done) => {
    72. // 该钩子总是在共享的 `onSend` 钩子后被执行
    73. done(null, payload)
    74. },
    75. onTimeout: (request, reply, done) => {
    76. // 该钩子总是在共享的 `onTimeout` 钩子后被执行
    77. done()
    78. },
    79. onError: (request, reply, error, done) => {
    80. // 该钩子总是在共享的 `onError` 钩子后被执行
    81. done()
    82. },
    83. handler: function (request, reply) {
    84. reply.send({ hello: 'world' })
    85. }