Fastify 鉤子方法

2020-02-06 15:39 更新

鉤子方法

鉤子 (hooks) 讓你能夠監(jiān)聽?wèi)?yīng)用或請求/響應(yīng)生命周期之上的特定事件。使用 fastify.addHook 可以注冊鉤子。你必須在事件被觸發(fā)之前注冊相應(yīng)的鉤子,否則,事件將得不到處理。

通過鉤子方法,你可以與 Fastify 的生命周期直接進(jìn)行交互。有用于請求/響應(yīng)的鉤子,也有應(yīng)用級鉤子:

注意:使用 async/await 或返回一個 Promise 時,done 回調(diào)不可用。在這種情況下,仍然使用 done 可能會導(dǎo)致難以預(yù)料的行為,例如,處理函數(shù)的重復(fù)調(diào)用。

請求/響應(yīng)鉤子

Request 與 Reply 是 Fastify 核心的對象。done 是調(diào)用生命周期下一階段的函數(shù)。

生命周期一文清晰地展示了各個鉤子執(zhí)行的位置。鉤子可被封裝,因此可以運用在特定的路由上。更多信息請看作用域一節(jié)。

在請求/響應(yīng)中,有八個可用的鉤子 (按執(zhí)行順序排序):

onRequest

fastify.addHook('onRequest', (request, reply, done) => {
  // 其他代碼
  done()
})

或使用 async/await:

fastify.addHook('onRequest', async (request, reply) => {
  // 其他代碼
  await asyncMethod()
  return
})

注意:在 onRequest 鉤子中,request.body 的值總是 null,這是因為 body 的解析發(fā)生在 preValidation 鉤子之前。

preParsing

fastify.addHook('preParsing', (request, reply, done) => {
  // 其他代碼
  done()
})

或使用 async/await:

fastify.addHook('preParsing', async (request, reply) => {
  // 其他代碼
  await asyncMethod()
  return
})

注意:在 preParsing 鉤子中,request.body 的值總是 null,這是因為 body 的解析發(fā)生在 preValidation 鉤子之前。

preValidation

fastify.addHook('preValidation', (request, reply, done) => {
  // 其他代碼
  done()
})

或使用 async/await:

fastify.addHook('preValidation', async (request, reply) => {
  // 其他代碼
  await asyncMethod()
  return
})

preHandler

fastify.addHook('preHandler', (request, reply, done) => {
  // 其他代碼
  done()
})

或使用 async/await:

fastify.addHook('preHandler', async (request, reply) => {
  // 其他代碼
  await asyncMethod()
  return
})

preSerialization

preSerialization 鉤子讓你可以在 payload 被序列化之前改動 (或替換) 它。舉個例子:

fastify.addHook('preSerialization', (request, reply, payload, done) => {
  const err = null;
  const newPayload = { wrapped: payload }
  done(err, newPayload)
})

或使用 async/await

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

注:payload 為 string、Buffer、stream 或 null 時,該鉤子不會被調(diào)用。

onError

fastify.addHook('onError', (request, reply, error, done) => {
  // 其他代碼
  done()
})

或使用 async/await:

fastify.addHook('onError', async (request, reply, error) => {
  // 當(dāng)自定義錯誤日志時有用處
  // 你不應(yīng)該使用這個鉤子去更新錯誤
})

onError 鉤子可用于自定義錯誤日志,或當(dāng)發(fā)生錯誤時添加特定的 header。該鉤子并不是為了變更錯誤而設(shè)計的,且調(diào)用 reply.send 會拋出一個異常。它只會在 customErrorHandler 向用戶發(fā)送錯誤之后被執(zhí)行 (要注意的是,默認(rèn)的 customErrorHandler 總是會發(fā)送錯誤)。 注意:與其他鉤子不同,onError 不支持向 done 函數(shù)傳遞錯誤。

onSend

使用 onSend 鉤子可以改變 payload。例如:

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

或使用 async/await:

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

你也可以通過將 payload 置為 null,發(fā)送一個空消息主體的響應(yīng):

fastify.addHook('onSend', (request, reply, payload, done) => {
  reply.code(304)
  const newPayload = null
  done(null, newPayload)
})
將 payload 設(shè)為空字符串 '' 也可以發(fā)送空的消息主體。但要小心的是,這么做會造成 Content-Length header 的值為 0。而 payload 為 null 則不會設(shè)置 Content-Length header。

注:你只能將 payload 修改為 string、Buffer、stream 或 null。

onResponse


fastify.addHook('onResponse', (request, reply, done) => {
  // 其他代碼
  done()
})

或使用 async/await:

fastify.addHook('onResponse', async (request, reply) => {
  // 其他代碼
  await asyncMethod()
  return
})

onResponse 鉤子在響應(yīng)發(fā)出后被執(zhí)行,因此在該鉤子中你無法再向客戶端發(fā)送數(shù)據(jù)了。但是你可以在此向外部服務(wù)發(fā)送數(shù)據(jù),比如收集數(shù)據(jù)。

在鉤子中管理錯誤

在鉤子的執(zhí)行過程中如果發(fā)生了錯誤,只需將錯誤傳遞給 done(),F(xiàn)astify 就會自動關(guān)閉請求,并發(fā)送一個相應(yīng)的錯誤碼給用戶。

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

如果你想自定義發(fā)送給用戶的錯誤碼,使用 reply.code() 即可:

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

錯誤最終會在 Reply 中得到處理。

在鉤子中響應(yīng)請求

需要的話,你可以在路由控制器執(zhí)行前響應(yīng)一個請求,例如進(jìn)行身份驗證。如果你在 onRequest 或 preHandler 中發(fā)出響應(yīng),請使用 reply.send。如果是在中間件中,使用 res.end。

fastify.addHook('onRequest', (request, reply, done) => {
  reply.send('Early response')
})

// 也可使用 async 函數(shù)
fastify.addHook('preHandler', async (request, reply) => {
  reply.send({ hello: 'world' })
})

如果你想要使用流 (stream) 來響應(yīng)請求,你應(yīng)該避免使用 async 函數(shù)。必須使用 async 函數(shù)的話,請參考 test/hooks-async.js 中的示例來編寫代碼。

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

應(yīng)用鉤子

你也可以在應(yīng)用的生命周期里使用鉤子方法。要格外注意的是,這些鉤子并未被完全封裝。鉤子中的 this 得到了封裝,但處理函數(shù)可以響應(yīng)封裝界線外的事件。

onClose

使用 fastify.close() 停止服務(wù)器時被觸發(fā)。當(dāng)插件需要一個 "shutdown" 事件時有用,例如關(guān)閉一個數(shù)據(jù)庫連接。該鉤子的第一個參數(shù)是 Fastify 實例,第二個為 done 回調(diào)函數(shù)。

fastify.addHook('onClose', (instance, done) => {
  // 其他代碼
  done()
})

onRoute

當(dāng)注冊一個新的路由時被觸發(fā)。它的監(jiān)聽函數(shù)擁有一個唯一的參數(shù):routeOptions 對象。該函數(shù)是同步的,其本身并不接受回調(diào)作為參數(shù)。

fastify.addHook('onRoute', (routeOptions) => {
  // 其他代碼
  routeOptions.method
  routeOptions.schema
  routeOptions.url
  routeOptions.bodyLimit
  routeOptions.logLevel
  routeOptions.logSerializers
  routeOptions.prefix
})

如果在編寫插件時,需要自定義程序的路由,比如修改選項或添加新的路由層鉤子,你可以在這里添加。

fastify.addHook('onRoute', (routeOptions) => {
  function onPreSerialization(request, reply, payload, done) {
    // 其他代碼
    done(null, payload)
  }

  // preSerialization 可以是數(shù)組或 undefined
  routeOptions.preSerialization = [...(routeOptions.preSerialization || []), onPreSerialization]
})

onRegister

當(dāng)注冊一個新的插件,或創(chuàng)建了新的封裝好的上下文后被觸發(fā)。該鉤子在注冊的代碼之前被執(zhí)行。當(dāng)你的插件需要知曉上下文何時創(chuàng)建完畢,并操作它們時,可以使用這一鉤子。注意:被 fastify-plugin 所封裝的插件不會觸發(fā)該鉤子。

fastify.decorate('data', [])

fastify.register(async (instance, opts) => {
  instance.data.push('hello')
  console.log(instance.data) // ['hello']

  instance.register(async (instance, opts) => {
    instance.data.push('world')
    console.log(instance.data) // ['hello', 'world']
  }), { prefix: '/hola' })
}), { prefix: '/ciao' })

fastify.register(async (instance, opts) => {
  console.log(instance.data) // []
}), { prefix: '/hello' })

fastify.addHook('onRegister', (instance, opts) => {
  // 從舊數(shù)組淺拷貝,
  // 生成一個新數(shù)組,
  // 使用戶獲得一個
  // 封裝好的 `data` 的實例
  instance.data = instance.data.slice()

  // 新注冊實例的選項
  console.log(opts.prefix)
})

作用域

除了應(yīng)用鉤子,所有的鉤子都是封裝好的。這意味著你可以通過 register 來決定在何處運行它們,正如插件指南所述。如果你傳遞一個函數(shù),那么該函數(shù)會獲得 Fastify 的上下文,如此你便能使用 Fastify 的 API 了。

fastify.addHook('onRequest', function (request, reply, done) {
  const self = this // Fastify 上下文
  done()
})

注:使用箭頭函數(shù)會破壞 Fastify 實例對 this 的綁定。

路由層鉤子

你可以為單個路由聲明一個或多個自定義的 onRequest、onReponsepreParsing、 preValidationpreHandler 與 preSerialization 鉤子。 如果你這么做,這些鉤子總是會作為同一類鉤子中的最后一個被執(zhí)行。當(dāng)你需要進(jìn)行認(rèn)證時,這會很有用,而  preParsing 與 preValidation 鉤子正是為此而生。 你也可以通過數(shù)組定義多個路由層鉤子。

fastify.addHook('onRequest', (request, reply, done) => {
  // 你的代碼
  done()
})

fastify.addHook('onResponse', (request, reply, done) => {
  // 你的代碼
  done()
})

fastify.addHook('preParsing', (request, reply, done) => {
  // 你的代碼
  done()
})

fastify.addHook('preValidation', (request, reply, done) => {
  // 你的代碼
  done()
})

fastify.addHook('preHandler', (request, reply, done) => {
  // 你的代碼
  done()
})

fastify.addHook('preSerialization', (request, reply, payload, done) => {
  // 你的代碼
  done(null, payload)
})

fastify.route({
  method: 'GET',
  url: '/',
  schema: { ... },
  onRequest: function (request, reply, done) {
    // 該鉤子總是在共享的 `onRequest` 鉤子后被執(zhí)行
    done()
  },
  onResponse: function (request, reply, done) {
    // 該鉤子總是在共享的 `onResponse` 鉤子后被執(zhí)行
    done()
  },
  preParsing: function (request, reply, done) {
    // 該鉤子總是在共享的 `preParsing` 鉤子后被執(zhí)行
    done()
  },
  preValidation: function (request, reply, done) {
    // 該鉤子總是在共享的 `preValidation` 鉤子后被執(zhí)行
    done()
  },
  preHandler: function (request, reply, done) {
    // 該鉤子總是在共享的 `preHandler` 鉤子后被執(zhí)行
    done()
  },
  // // 使用數(shù)組的例子。所有鉤子都支持這一語法。
  //
  // preHandler: [function (request, reply, done) {
  //   // 該鉤子總是在共享的 `preHandler` 鉤子后被執(zhí)行
  //   done()
  // }],
  preSerialization: (request, reply, payload, done) => {
    // 該鉤子總是在共享的 `preSerialization` 鉤子后被執(zhí)行
    done(null, payload)
  },
  handler: function (request, reply) {
    reply.send({ hello: 'world' })
  }
})

注:兩個選項都接受一個函數(shù)數(shù)組作為參數(shù)。



以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號