當我們在開發 Mosir 的中期,選擇了由社群發展出的一套基於 HTTP multipart/form-data 的方案,即 GraphQL Multipart Request Specification。
其核心概念是將傳統的 application/json Content Type 轉換為 multipart/form-data。一個典型的 multipart/form-data 請求會包含以下幾個部分:
- operations 部分: 包含常規的 GraphQL Query 或 Mutation,其中檔案變數會以 null 或特定的佔位符號表示。
- map 部分: 定義 operations 中佔位符號與 multipart 請求中實際檔案部分的對應關係。
- 一個或多個檔案 part: 每個 part 都包含一個實際的二進位檔案。
透過這種方式,Client 就能在單一 HTTP 請求中,同時傳送 GraphQL 操作的 JSON 資料和二進位檔案,伺服器解析後再將檔案注入到 GraphQL Mutation 的上下文中進行處理。
儘管 GraphQL Multipart Request Specification 解決了在 GraphQL 環境下上傳檔案的需求,但它也繼承了 multipart/form-data 的固有缺點,特別是在處理複雜情境時:
- 缺乏斷點續傳 (Resumable Uploads): multipart/form-data 屬於「一次性」的傳輸模式。若檔案在上傳過程中中斷(不論是網路不穩、伺服器重啟或客戶端意外關閉),整個上傳都必須從頭開始。這對於上傳大型檔案(數十 MB 甚至 GB 等級)而言,會導致極差的使用者體驗和大量的資源浪費。
- Proxy 和處理的複雜性:
- Proxy 處理壓力:如果架構中存在反向代理 (Reverse Proxy) 或 API Gateway,當大型檔案透過 multipart/form-data 方式上傳時,代理層需要接收並快取整個檔案請求,然後再轉發給後端服務。這可能對代理層造成巨大的記憶體和頻寬壓力,甚至可能因為代理的快取設定或連線逾時而導致上傳失敗。
- 擴充性考量:透過代理將大量檔案直接轉發到 GraphQL 服務,可能會使 GraphQL 服務承擔過多與其核心職責無關的 I/O 操作,進而影響其處理 GraphQL 查詢與變更的效率。這也讓檔案上傳的水平擴充變得更為困難。