iT邦幫忙

2022 iThome 鐵人賽

DAY 23
0
Modern Web

Fastify 101系列 第 23

[Fastify] Day23 - Upload File

  • 分享至 

  • xImage
  •  

大家好,我是 Yubin

在網頁的應用上,常常會需要上傳的功能,上傳文件上傳圖片等。
本篇文章來介紹在 Fastify App 開發上,使用 Fastify-Multipart 來上傳檔案的方式。


@Fastify/multipart

我們知道 Fastify 核心中定義的 Content-Type Parser 只有 text 跟 JSON 兩種,其他 Parser 要使用 Plugin 的方式註冊使用。

@Fastify/multipart 這個 Plugin 可以增加 Fastify App 對於 multipart 的支援,由 Fastify 官方團隊所維護。

如果沒有擴充對於 multipart 的支援,直接對伺服器上傳檔案的話,在 Fastify 生命週期中,進到 route 前,就會被 Fastify 回傳 415 的錯誤。

https://ithelp.ithome.com.tw/upload/images/20221008/20151148S6PlW7ADh6.jpg

如圖,Fastify 回應了 415 Unsupported Media Type 的回應,因為沒有相應的 Parser。

安裝 @Fastify/multipart

透過 npm 就可以簡單安裝:

npm i @fastify/multipart

接著透過 server.register() 進行註冊:

import fastify, { FastifyInstance } from 'fastify'
import fastifyMultipart from '@fastify/multipart'

const server: FastifyInstance = fastify()

server.register(fastifyMultipart)

註冊完 plugin,我們的 app 就有處理 multipart 的能力了。

Define Upload Route

試著寫一個 POST /uploads 的 Endpoint 來測試上傳檔案的功能:

server.post('/uploads', async (request, reply) => {
    try {
      const data = await request.file()
      if (data) {
        const filename = data.filename
        // handle file
        return reply
          .status(201)
          .send({ message: `Successfully upload file: ${filename}` })
      } else {
        return reply.status(400).send({ message: `Failed to upload file` })
      }
    } catch (error) {
      return reply.status(500).send({ error })
    }
})

因為要帶 Payload,所以用 POST 而不是 GET

透過 request.file() 拿到 MultipartFile 物件。

處理完沒錯誤就回應 201 (或 200,看 API 設計) 給 Client。

Upload File Using Postman

通常是前端網頁會透過 JavaScript 的方式把要上傳的檔案帶到後端,為了方便開發測試,這裡使用 Postman。

https://ithelp.ithome.com.tw/upload/images/20221008/20151148v3KaaPD3c0.jpg

可以看到,不會再得到 415 的回應了,我們的 Request 順利進到 route 中,並回應正確的狀態瑪。

Save File in Local

把上傳的檔案儲存到伺服器的某個位置是個常見的方式。(先不論好不好XD)

這邊示範的是,使用 fscreateWriteStream 方法,來把收到的檔案寫進特定目錄。

以下程式以上傳到專案的 uplodads/ 目錄為例,請先建立好該目錄。

import fs from 'fs'
import { pipeline } from 'stream'
import util from 'util'

const pump = util.promisify(pipeline)

server.post('/uploads', async (request, reply) => {
    try {
      const data = await request.file()
      if (data) {
        const filename = data.filename
        await pump(data.file, fs.createWriteStream(`uploads/${filename}`))

        return reply
          .status(201)
          .send({ message: `Successfully upload file: ${filename}` })
      } else {
        return reply.status(400).send({ message: `Failed to upload file` })
      }
    } catch (error) {
      return reply.status(500).send({ error })
    }
})

再次透過 Postman 發送 Request,可以在 uploads 目錄找到該檔案。

https://ithelp.ithome.com.tw/upload/images/20221008/20151148pGCPOroehA.jpg

提醒

上傳檔案這個 Feature 雖然常見,但有許多要注意的地方。

比如說應該要對上傳的內容限制他的檔案大小,否則有人不小心或故意上傳了超大的東西給你,可能會造成伺服器的負擔 (硬碟或記憶體等)。

以下是 @fastify/multipart 文件中關於限制上傳的範例程式:

server.register(fastifyMultipart, {
  limits: {
    fieldNameSize: 100, // Max field name size in bytes
    fieldSize: 100,     // Max field value size in bytes
    fields: 10,         // Max number of non-file fields
    fileSize: 1000000,  // For multipart forms, the max file size in bytes
    files: 1,           // Max number of file fields
    headerPairs: 2000   // Max number of header key=>value pairs
  }
})

詳細資訊請參考官方文件


另外有些使用情境是檔案收進來之後,會經過處理,處理完就不需要那個檔案。
在處理的過程中,有沒有要以檔案的形式落地,如果落地了要不要保存或刪除。

這些跟 IO 有關的操作成本都是很高的,必須謹慎面對。

=> 不要看個範例,程式有功能就開始亂搞。


雖然本文是以上傳到本地端的檔案系統為範例,但更常見的可能會是把檔案保存到 NFS 或 Object Storage 中,避免妨礙了 Scale Out 的機制。

以上介紹 @fastify/multipart 這個 Plugin 的使用及實際演示。
完整範例可以參考 GitHub


上一篇
[Fastify] Day22 - Testcontainers
下一篇
[Fastify] Day24 - Upload File to Object Storage (MinIO)
系列文
Fastify 10130
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言