iT邦幫忙

2022 iThome 鐵人賽

DAY 9
0
Modern Web

Fastify 101系列 第 9

[Fastify] Day09 - Hook

  • 分享至 

  • xImage
  •  

大家好,我是 Yubin

這篇文章會講 Fastify 的 Hook,可以監聽特定的事件,在事件觸發前呼叫自己想要的處理函式。


本文中的 server 指的是 FastifyInstance,如下程式片段。

import fastify, { FastifyInstance } from 'fastify'

const server: FastifyInstance = fastify()

Hook 藉由 server.addHook() 註冊,可以監聽應用程式的事件或 request/response 生命週期中的事件。

Application Hooks

應用程式事件。

有 onReady、onClose、onRoute、onRegister 四種。


  • onReady

觸發 onReady hook 有兩個情況。
第一是伺服器開始聽 request 的時候。
第二是 server 的 .ready() 被呼叫的時候觸發。

Hook 的註冊有兩種風格可以用,一種是 callback、一種是 async/await。

callback style

// callback style
server.addHook('onReady', (done) => {
  // Some code
  done()
})

done() 不能用在 async/await 的寫法上,done 的 Type 是 HookHandlerDoneFunction
代表這個 hook 的動作結束。

async/await style

server.addHook('onReady', async () => {
  // Some async code
  await loadCache()
})

如果 hook handler 中需要進行 async function 的呼叫,非常方便使用。


  • onClose

當 server 的 .close() 被呼叫時觸發。

當需要在關閉伺服器前做一些事情的時候很好用,例如寫 log 或關閉資料庫連線。

// callback style
server.addHook('onClose', (instance, done) => {
  // some code
  done()
})

// or async/await style
server.addHook('onClose', async (instance) => {
  // some async code
  await closeDatabaseConnections()
})

  • onRoute

當有一個新的 route 被註冊時觸發。

會傳入一個 RouteOptions 物件。

server.addHook('onRoute', (routeOptions) => {
  // Some code
  routeOptions.method
  routeOptions.schema
  routeOptions.url
  routeOptions.path
  routeOptions.routePath
  routeOptions.bodyLimit
  routeOptions.logLevel
  routeOptions.prefix
})

備註:官網的程式範例有誤,RouteOptions 裡面已經沒有 logSerializers 欄位。


  • onRegister

當有一個新的 plugin 被註冊 觸發。

但若該 plugin 是被 fastify-plugin 所封裝的,不會觸發此 hook。

Request/Reply Hooks

Request 和 Reply 都是 Fastify 核心的物件。

done() 在前面提到的 Application Hook 中代表著該 hook 的結束,對於 Request/Reply Hooks 來說,除了代表 hook 的結束,也代表該 request/response 進入下一個生命週期。


  • onRequest

request 進來的第一個 hook。

// callback style
server.addHook('onRequest', (request, reply, done) => {
  // some code
  done()
})

// async/await style
server.addHook('onRequest', async (request, reply) => {
  // some async code
  await asyncMethod()
})

值得注意的是,在這個 hook 中,request.body 永遠都是 undefined,因為在觸發這個 hook 的時候,Request 的 body 還沒被 parsing。


  • preParsing

preParsing 這個 hook,可以在解析 Request Payload 成 request.body 前,對 Request 的 Payload 進行操作。

server.addHook('preParsing', (request, reply, payload, done) => {
  // some code
  done(null, newPayload)
})

這裡的 done(null, newPayload),第一個參數是 Error,第二個參數是修改過後的 Request Payload。

payload 的 Type 是 RequestPayload

一樣也可以用 async/await 的寫法。

server.addHook('preParsing', async (request, reply, payload) => {
  // some async code
  return newPayload
})

onRequest hook 一樣,preParsing hook 也是在實際解析 Request Payload 成 request.body 前進行,所以 request.body 必定為 undefined

如果有對 payload 進行修改,則必須一並調整新的 RequestPayload 中的 receivedEncodedLength 屬性,避免後面的操作發現 header 中的 Content-Length 欄位值與實際的 Request Payload 長度不符。


  • preValidation

preValidation hook 在執行 body 的 validation 之前被呼叫,可以在進行驗證工作前修改 request.body

// callback style
server.addHook('preValidation', (request, reply, done) => {
  // some code
  const oldBody = request.body
  request.body = { oldBody, importantKey: 'randomString' }
  done()
})

// async/await style
server.addHook('preValidation', async (request, reply) => {
  const oldBody = request.body
  // some async code
  const key = await getRandomString()
  request.body = { oldBody, importantKey: key}
})

  • preHandler

preHandler hook 在進到 route handler 之前被呼叫。

// callback style
fastify.addHook('preHandler', (request, reply, done) => {
  // some code
  done()
})

// async/await style
fastify.addHook('preHandler', async (request, reply) => {
  // some async code
  await asyncMethod()
})

  • preSerialization

藉由 preSerialization hook,可以讓你在進行 Response 的序列化 (Serialization) 之前修改 Payload。

// callback style
server.addHook('preSerialization', (request, reply, payload, done) => {
  const err = null
  const newPayload = { wrapped: payload }
  done(err, newPayload)
})

// async/await style
server.addHook('preSerialization', async (request, reply, payload) => {
  return { wrapped: payload }
})

要注意的是,如果 Payload 是 string, Buffer, streamnull 的時候,不會觸發此 hook。


  • onError

onError hook 在自訂的 error handler 拋出錯誤時被呼叫。
當要對特別的 error handler 進行處理的時候很常用,例如寫 log 或增加特別的 header。

// callback style
server.addHook('onError', (request, reply, error, done) => {
  // some code
  done()
})

// async/await style
server.addHook('onError', async (request, reply, error) => {
  // Useful for custom error logging
  // You should not use this hook to update the error
})

這個 hook 不是用來改變 error 用的,呼叫 reply.send 會噴錯。
只有在執行 customErrorHandler 的過程出錯才會觸發此 hook。

跟其他 hook 不同的是,done function 不能傳入錯誤。(因為這邊是錯誤的終點了)


  • onSend

onSend hook 可以讓你在 Response 出去前更改 Payload。

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

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

也可以藉由把 Payload 設為 null 來回傳空的 Payload 出去。

server.addHook('onSend', (request, reply, payload, done) => {
  reply.code(304) // for 'Not Modified'
  const newPayload = null
  done(null, newPayload)
})

要注意的是,這邊的 Payload 已經被序列化 (Serialization) 過了,只能把 Payload 改為 string, Buffer, streamnull 等型態。


  • onResponse

onResponse hook 在 Response 發送出去被呼叫。

所以不能藉由這個 hook 來修改 Response 的內容 (阿就已經送出去了),但要寫 log 或做一些統計數據的用途很好用。

// callback style
server.addHook('onResponse', (request, reply, done) => {
  // some code
  done()
})

// async/await style
server.addHook('onResponse', async (request, reply) => {
  // some async code
  await asyncMethod()
})

  • onTimeout

在發生 timeout 時候觸發,如果要監控 Request timeout 的時候很好用。

// callback style
server.addHook('onTimeout', (request, reply, done) => {
  // some code
  done()
})

// async/await style
server.addHook('onTimeout', async (request, reply) => {
  // some async code
  await asyncMethod()
})

要注意的是,onTimeout hook 執行的時候 HTTP socket 已經掛斷,所以沒辦法再發送 Response 給對方。


Manage Errors from a hook

我們在寫 hook 的 handler 的時候也常常會有例外處理或拋出錯誤的需求。

可以藉由 done() 把錯誤往下傳。(除了 onError hook。)

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

除了拋出錯誤,也可以指定要回應的 Status Code。

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

Error 會在 Reply 的時候被處理。


Respond to a request from a hook

有時候我們想在 hook 的處理過程就回應 Respone 出去。

例如,在一個身分驗證的 hook 中,判斷出 Request 沒有符合認證,就可以回應 401 出去了,而不用走到 Route 定義的 Handler 那邊處理。

// callback style
server.addHook('onRequest', (request, reply, done) => {
  // some code
  reply.send('Early response')
})

// async/await style
server.addHook('preHandler', async (request, reply) => {
  // some async code
  await something()
  reply.send({ hello: 'world' })
  return reply // mandatory, so the request is not executed further
})

注意,使用 async function 的時候要回傳 reply 物件。


Hook 的註冊除了定義 Global 的伺服器行為,也可以定義 Route Level 的 hook。

對於特定 Route 有特別的處理非常實用。

以上是 Fastify hook 的介紹。


上一篇
[Fastify] Day08 - Fastify Server Methods
下一篇
[Fastify] Day10 - Lifecycle
系列文
Fastify 10130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言