iT邦幫忙

2022 iThome 鐵人賽

DAY 12
0
Modern Web

Fastify 101系列 第 12

[Fastify] Day12 - FastifyReply

  • 分享至 

  • xImage
  •  

大家好,我是 Yubin

在定義 route handler 的時候,第一個參數傳入的是 FastifyRequest,第二個參數是 FastifyReply,藉由 FastifyReply 我們可以控制要出去的 response 的長相。

本篇文章來講 FastifyReply 這個核心物件。


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'

.code(statusCode)

設定 HTTP Status Code 狀態碼,如果沒有設定,也沒有拋出錯誤,預設會回 200 (OK)。

.status(statusCode)

設定 HTTP Status Code 狀態碼,相當於 reply.code(statusCode) 的別名。

(個人常用此寫法)

.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' })
})

.server

拿到 fastify server 物件。

.header(key, value)

設定 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 兩個值都會發送。

https://ithelp.ithome.com.tw/upload/images/20220927/20151148WZUlDRZYsT.jpg

但瀏覽器 (或其他 Client 工具) 只會參考到最新的 key 的值。

https://ithelp.ithome.com.tw/upload/images/20220927/20151148JuGgSPVnfc.jpg

.headers(object)

想要一次設定多個 key-value Pair 的 headers 嗎?

可以使用 .headers() 傳入一個物件。

reply.headers({
  'x-foo': 'foo',
  'x-bar': 'bar'
})

.getHeader(key)

取得某個 header 的值,拿不到則為 undefined

reply.header('x-foo', 'foo') // setHeader: key, value
reply.getHeader('x-foo') // 'foo'
reply.getHeader('x-bar') // undefined

.getHeaders()

以物件的方式拿到當前 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' }

.removeHeader(key)

刪除 headers 中某個 key 值。

reply.header('x-foo', 'foo')
reply.removeHeader('x-foo')
reply.getHeader('x-foo') // undefined

.hasHeader(key)

回傳 header 中是否有某個 key,回傳型態為 boolean

.trailer(key, function)

設定 response 的 trailer。

.hasTrailer(key)

回傳 trailer 中是否有某個 key。

.removeTrailer(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([code ,] dest)

發送 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')

.callNotFound()

呼叫 Not Found hander。

reply.callNotFound()

.getResponseTime()

得到從 request 進來,到 response 出去的總時間。

單位為毫秒 (milliseconds)。

注意,只有在 onResponse hook 中使用這個方法才有意義,否則一律回傳 0

統計/監控每個 API 的回應時間非常有用。

.type(contentType)

設定 Response header 的 content type。

這個 function 是 reply.header('Content-Type', 'the/type') 的縮寫版本。

reply.type('text/html')

如果 Content-Type 被設為 JSON type,若沒指定 charset,則會將 charset 設定為 utf-8

.raw

拿到 NodeJS http 模組中的 ServerResponse 物件。
如果直接操作該物件,會有跟 fastify 運作邏輯相衝的風險 (不建議直接操作)。

.sent

回傳該 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
})

.hijack()

中斷正常 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)

.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-Typetext/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-Typeapplication/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 物件。

最後的 Payload

在經過序列化 (Serialization) 後,Payload 的型態必須為以下這幾種,否則會拋出錯誤。

  • string
  • Buffer
  • stream
  • undefined
  • null

Async-Await and Promises

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 官方文件


上一篇
[Fastify] Day11 - FastifyRequest
下一篇
[Fastify] Day13 - Decorator
系列文
Fastify 10130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言