大家好,我是 Yubin
在定義 route handler 的時候,第一個參數傳入的是 FastifyRequest,第二個參數是 FastifyReply,藉由 FastifyReply 我們可以控制要出去的 response 的長相。
本篇文章來講 FastifyReply 這個核心物件。
一個 route handler 的定義,可以參考如下範例。
import fastify, { FastifyInstance } from 'fastify'
const server: FastifyInstance = fastify()
server.get('/', (request, reply) => {
// some code
reply
.status(200)
.header('Content-Type', 'application/json; charset=utf-8')
.send({ hello: 'world' })
})
上述程式中,
運用了 reply 物件的 .status()
來設定 Response 的 HTTP Status Code,這邊設定為 200 (OK)。
利用 .header()
設定 Content-Type
這個 header 欄位的值為 application/json; charset=utf-8
。
透過 .send()
把物件 { hello: 'world' }
轉為 JSON String 後送出,當作 Response Payload。
reply 物件的 Type 為 FastifyReply,有許多欄位可以供開發者使用。
import { FastifyInstance } from 'fastify'
設定 HTTP Status Code 狀態碼,如果沒有設定,也沒有拋出錯誤,預設會回 200
(OK)。
設定 HTTP Status Code 狀態碼,相當於 reply.code(statusCode)
的別名。
(個人常用此寫法)
拿到 Status Code 的值。
也可以用於 Status Code 的指定 (setter),相當於 reply.code()
的別名。
server.get('/', (request, reply) => {
// some code
reply.statusCode = 200
reply
.header('Content-Type', 'application/json; charset=utf-8')
.send({ hello: 'world' })
})
拿到 fastify server 物件。
設定 Response Header。
要注意的是,header 的值必須經過 URL encode (如使用 encodeURI 等方法)。
若有不合法的字元會拋出 500 TypeError 的回應。
set-cookie
當以 set-cookie
為 key 增加 header 時,會若有不同的值,則每個值都會被當作 cookie 來發送,而不是取代掉前者。
server.get('/', (request, reply) => {
// some code
reply.header('set-cookie', 'foo')
reply.header('set-cookie', 'bar')
reply.status(200).send({ hello: 'world' })
})
如圖, foo
, bar
兩個值都會發送。
但瀏覽器 (或其他 Client 工具) 只會參考到最新的 key 的值。
想要一次設定多個 key-value Pair 的 headers 嗎?
可以使用 .headers()
傳入一個物件。
reply.headers({
'x-foo': 'foo',
'x-bar': 'bar'
})
取得某個 header 的值,拿不到則為 undefined
。
reply.header('x-foo', 'foo') // setHeader: key, value
reply.getHeader('x-foo') // 'foo'
reply.getHeader('x-bar') // undefined
以物件的方式拿到當前 response 的 headers。
reply.header('x-foo', 'foo')
reply.header('x-bar', 'bar')
reply.raw.setHeader('x-foo', 'foo2')
reply.getHeaders() // { 'x-foo': 'foo', 'x-bar': 'bar' }
刪除 headers 中某個 key 值。
reply.header('x-foo', 'foo')
reply.removeHeader('x-foo')
reply.getHeader('x-foo') // undefined
回傳 header 中是否有某個 key,回傳型態為 boolean
。
設定 response 的 trailer。
回傳 trailer 中是否有某個 key。
刪除 trailer 中的某個 key。
reply.trailer('server-timing', function() {
return 'db;dur=53, app;dur=47.2'
})
reply.removeTrailer('server-timing')
reply.getTrailer('server-timing') // undefined
發送 Redirect response 到某個指定的 Url,狀態碼若不給定,預設為 302
(Moved Temporarily)。
注意,目的 Url 必須經過 URL encode,若有非法字元會回傳 500 TypeError 的回應。
範例,狀態碼為預設的 302
redirect 到 /home
reply.redirect('/home')
範例,狀態碼 303
(See Other),redirect 到 /home
reply.redirect(303, '/home')
範例,也可以搭配 reply.code()
來設定狀態碼,redirect 到 /home
reply.code(303).redirect('/home')
呼叫 Not Found hander。
reply.callNotFound()
得到從 request 進來,到 response 出去的總時間。
單位為毫秒 (milliseconds)。
注意,只有在 onResponse
hook 中使用這個方法才有意義,否則一律回傳 0
。
統計/監控每個 API 的回應時間非常有用。
設定 Response header 的 content type。
這個 function 是 reply.header('Content-Type', 'the/type')
的縮寫版本。
reply.type('text/html')
如果 Content-Type
被設為 JSON type,若沒指定 charset,則會將 charset 設定為 utf-8
。
拿到 NodeJS http 模組中的 ServerResponse
物件。
如果直接操作該物件,會有跟 fastify 運作邏輯相衝的風險 (不建議直接操作)。
回傳該 response 是否已經透過 reply.send()
送出,型態為 boolean
。
若 reply.hijack()
被呼叫,也會得到 true
。
server.get('/', (request, reply) => {
// some code
console.log(reply.sent) // false
reply.status(200).send({ hello: 'world' })
console.log(reply.sent) // true
})
中斷正常 request 的生命週期,並手動發出回應。
server.get('/', (request, reply) => {
// some code
reply.hijack()
reply.raw.end('Hijack!')
reply.status(200).send({ hello: 'world' }) // this will be skipped
})
以上程式會送出 Hijack!
字串。
.hijack()
方法會跳過接下來生命週期中的剩餘的 hook function。
.send(data)
會將 payload 送出。
傳入的 data 可以為不同的形態
Objects
若傳入的是一個物件,則預設會透過 fast-json-stringify 轉為 JSON String 後送出。
fastify.get('/json', options, function (request, reply) {
reply.send({ hello: 'world' })
})
Strings
在傳入的是 String 的情況下,如果沒有指定 Content-Type
的值,則會預設為 text/plain; charset=utf-8
。
若有指定 Content-Type
則會利用該型態自訂的序列化方法來序列化。
若設定 Content-Type
為 text/plain; charset=utf-8
則會用 JSON 的序列化方法來處理。
server.get('/', (request, reply) => {
reply.send('plain string') // Response Content-Type: text/plain; charset=utf-8
})
Streams
.send()
也可以處理 stream。
若要送一個 stream 出去,header 會被設為 application/octet-stream
。(不用手動設定)
import fs from 'fs'
server.get('/streams', function (request, reply) {
const stream = fs.createReadStream('some-file', 'utf8')
reply.send(stream)
})
Buffers
.send()
也可以處理 buffer,預設的 Content-Type
為 application/octet-stream
。
import fs from 'fs'
server.get('/', (request, reply) => {
// some code
fs.readFile(some-file, (error, buffer) => {
reply.status(200).send(error || buffer) // Response Content-Type: application/octet-stream
})
})
Error
也可以把 Error 物件丟給 .send()
。
Fastify 會產生一個結構如下的物件。
{
error: String // the HTTP error message
code: String // the Fastify error code
message: String // the user error message
statusCode: Number // the HTTP status code
}
注意,如果傳入 .send()
的 Error 物件有 statusCode < 400 的情況,Fastify 會自動將該回應設為 500
。
可以用 @fastify/sensible plugin 來產生 Error 物件。
在經過序列化 (Serialization) 後,Payload 的型態必須為以下這幾種,否則會拋出錯誤。
Fastify 原生可以處理 Promise 和 async/await 的寫法。
import { promisify } from 'util'
const delay = promisify(setTimeout)
server.get('/promises', (request, reply) => {
return delay(200).then(() => {
return { hello: 'world' }
})
})
server.get('/async-await', async (request, reply) => {
await delay(200)
return { hello: 'world' }
})
注意,以上寫法並不用使用 reply.send()
。
若拋出錯誤,預設會回應 500
的狀態碼。
本篇介紹的 FastifyReply 基本概念及常用欄位,想看更詳細的說明可以參考 Fastify 官方文件。