在 RESTful Web 服務中下載和上傳檔案 - mscharhag

banq發表於2021-08-12

通常我們使用標準的資料交換格式,如 JSON 或 XML 與 REST web 服務。然而,許多 REST 服務至少有一些操作很難僅用 JSON 或 XML 來完成。例如上傳產品圖片、使用上傳的 CSV 檔案匯入資料或生成可下載的 PDF 報告。
在這篇文章中,我們關注那些通常被歸類為檔案下載和上傳的操作。這有點不穩定,因為傳送簡單的 JSON 文件也可以看作是 (JSON) 檔案上傳操作。
一個常見的錯誤是關注操作所需的特定檔案格式。相反,我們應該考慮我們想要表達的操作。檔案格式僅決定用於操作的媒體型別
例如,假設我們要設計一個 API,讓使用者將頭像圖片上傳到他們的使用者帳戶。
在這裡,出於各種原因,將頭像影像與使用者帳戶資源分開通常是一個好主意:
  • 頭像影像不太可能改變,因此它可能是快取的一個很好的候選者。另一方面,使用者帳戶資源可能包含諸如上次登入日期之類的經常更改的內容。
  • 並非所有訪問使用者帳戶的客戶端都可能對頭像影像感興趣。因此,可以節省頻寬。
  • 對於客戶端來說,通常最好單獨載入影像(想想使用<img>標籤的 Web 應用程式)

可以透過以下方式訪問使用者帳戶資源:

/users/<使用者 ID>


我們可以想出一個代表頭像影像的簡單子資源:

/users/<user-id>/avatar

上傳頭像是一個簡單的替換操作,可以透過 PUT 表示:

PUT /users/<user-id>/avatar
Content-Type: image/jpeg

<image data>

如果使用者想要刪除他的頭像,我們可以使用簡單的 DELETE 操作:

DELETE /users/<user-id>/avatar

當然,客戶需要一種顯示頭像的方法。因此,我們可以使用 GET 提供下載操作:

GET /users/<user-id>/avatar

返回:

HTTP/1.1 200 Ok
Content-Type: image/jpeg

<image data>


在這個簡單的例子中,我們使用了一個帶有常見更新、刪除、獲取操作的新子資源。唯一的區別是我們使用影像媒體型別而不是 JSON 或 XML。
 

讓我們看一個不同的例子。
假設我們提供了一個 API 來管理產品資料。我們希望透過一個選項來擴充套件此 API,以從上傳的 CSV 檔案中匯入產品。我們應該考慮一種表達產品匯入操作的方法,而不是考慮檔案上傳。
可能最簡單的方法是將 POST 請求傳送到單獨的資源:

POST /product-import
Content-Type: text/csv

<csv data>

或者,我們也可以將其視為產品的批次操作。正如我們在另一篇關於REST 批次操作的文章中瞭解到的,PATCH 方法是一種表達對集合的批次操作的可能方式。在這種情況下,TEXT/CSV 描述了對產品集合的期望更改。

PATCH /products 
Content-Type: text/csv 

action,id,name,price 
create,,Cool Gadget,3.99 
create,,Nice cap,9.50 
delete,42,,

此示例建立兩個新產品並刪除 id 為42的產品。
處理檔案上傳可能需要相當長的時間。所以考慮將其設計為非同步 REST 操作
 

混合檔案和後設資料
在某些情況下,我們可能需要將額外的後設資料附加到檔案中。例如,假設我們有一個 API,使用者可以在其中上傳假日照片。除了實際的影像資料,照片還可能包含描述、拍攝地點等。
在這裡,我會(再次)推薦使用兩個單獨的操作,原因與上一節中關於頭像影像的原因類似。即使這裡的情況有點不同(資料直接連結到影像),它通常也是更簡單的方法。
在這種情況下,我們可以首先透過傳送實際影像來建立照片資源:

POST /photos
Content-Type: image/jpeg

<image data>

響應:

HTTP/1.1 201 Created
Location: /photos/123


之後,我們可以將額外的後設資料附加到照片中:

PUT /photos/123/metadata
Content-Type: application/json

{
    "description": "Nice shot of a beach in hawaii",
    "location": "hawaii",
    "filename": "hawaii-beach.jpg"
}

當然,我們也可以反過來設計,在影像之前傳送後設資料。
 

在 JSON 或 XML 中嵌入 Base64 編碼的檔案
如果無法在單獨的請求中拆分檔案內容和後設資料,我們可以使用Base64 編碼將檔案嵌入到 JSON/XML 文件中。使用 Base64 編碼,我們可以將二進位制格式轉換為文字表示,該文字表示可以整合到其他基於文字的格式中,例如 JSON 或 XML。
示例請求可能如下所示:

POST /photos 
Content-Type: application/json 

{ 
    "width": "1280", 
    "height": "920", 
    "filename": "funny-cat.jpg", 
    "image": "TmljZSBleGFt...cGxlIHRleHQ= " 
}


 

將媒體型別與多部分請求混合
在單個請求/響應中傳輸影像資料和後設資料的另一種可能方法是多部分媒體型別
多部分媒體型別需要一個邊界引數,用作不同正文部分之間的分隔符。以下請求由兩個正文部分組成。第一個包含影像,而第二個部分包含後設資料。
例如

POST /photos
Content-Type: multipart/mixed; boundary=foobar

--foobar
Content-Type: image/jpeg

<image data>
--foobar
Content-Type: application/json

{
    "width": "1280",
    "height": "920",
    "filename": "funny-cat.jpg"
}
--foobar--

不幸的是,多部分請求/響應通常很難處理。例如,並非每個 REST 客戶端都能夠構建這些請求,並且很難在單元測試中驗證響應。
 

相關文章