归档 – 并在 RESTful Web 服务中上传

被风吹过灼思 2021-09-03 11:29:07 浏览数 (2020)
反馈

通常我们使用标准的数据交换格式,如 JSON 或 XML 与 REST web 服务。然而,许多 REST 服务至少有一些操作很难仅用 JSON 或 XML 来完成。例如上传产品图片、使用上传的 CSV 文件导入数据或生成可下载的 PDF 报告。

在这篇文章中,我们关注那些通常被归类为文件下载和上传的操作。这有点不稳定,因为发送简单的 JSON 文档也可以看作是 (JSON) 文件上传操作。

想想你要表达的操作

一个常见的错误是关注操作所需的特定文件格式。相反,我们应该考虑我们想要表达的操作。文件格式仅决定用于操作的媒体类型。

例如,假设我们要设计一个 API,让用户将头像图片上传到他们的用户帐户。

在这里,出于各种原因,将头像图像与用户帐户资源分开通常是一个好主意:

  • 头像图像不太可能改变,因此它可能是缓存的一个很好的候选者。另一方面,用户帐户资源可能包含诸如上次登录日期之类的经常更改的内容。
  • 并非所有访问用户帐户的客户端都可能对头像图像感兴趣。因此,可以节省带宽。
  • 对于客户端来说,通常最好单独加载图像(想想使用 <img> 标签的 Web 应用程序)

可以通过以下方式访问用户帐户资源:

/users/<user-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>

或者,我们也可以将其视为产品的批量操作。​PATCH ​方法是一种表达对集合的批量操作的可能方式。在这种情况下,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

之后,我们可以将额外的元数据附加到照片中:

当然,我们也可以反过来设计,在图像之前发送元数据。

在 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 客户端都能够构建这些请求,并且很难在单元测试中验证响应。


0 人点赞