REST API – 文件(即图像)处理 – 最佳实践

我们正在使用REST API开发服务器,它接受和响应JSON。 问题是,如果你需要从客户端上传图片到服务器。

另外请注意,我正在谈论的用例,其中实体(用户)可以有文件(carPhoto,licensePhoto),也有其他属性(名称,电子邮件…),但是当你创build新的用户,你不会发送这些图像,在注册过程后添加。


我知道的解决scheme,但他们每个人都有一些缺陷

1.使用multipart / form-data而不是JSON

:POST和PUT请求尽可能RESTful,它们可以包含文本和文件一起input。

缺点 :不再是JSON,与multipart / form-data相比,testing,debugging等更容易

2.允许更新单独的文件

POST请求创build新用户不允许添加图像(这在我们的使用情况下是如何说,我开始说),上传图片是由PUT请求作为multipart / form-data例如/ users / 4 / carPhoto

:除了file upload以外,所有东西都保存在JSON中,很容易testing和debugging(你可以logging完整的JSON请求而不用担心它们的长度)

缺点 :这不是直观的,你不能一次POST或PUT实体的所有variables,也可以认为这个地址/users/4/carPhoto更多的集合(REST API的标准用例看起来像这样/users/4/shipments )。 通常你不能(并不想)GET / PUT实体的每个variables,例如用户/ 4 /名称。 您可以使用GET获取名称,并在用户/ 4处使用PUT更改它。 如果id后面有东西,通常是另一个集合,比如users / 4 / reviews

3.使用Base64

将它作为JSON发送,但使用Base64编码文件。

:与第一种解决scheme相同,它是尽可能的RESTful服务。

缺点 :testing和debugging再次糟糕得多(主体可能有兆字节的数据),在客户端和服务器上都有增加的大小和处理时间


我真的很想用解决scheme没有。 2,但它有其缺点…任何人都可以给我一个更好的“什么是最好的”解决scheme的见解?

我的目标是尽可能多地包含尽可能多的RESTful服务,而我希望尽可能简单。

有几个决定

  1. 首先关于资源path

    • 将图像build模为自己的资源:

      • 嵌套在用户(/ user /:id / image)中:用户和图像之间的关系是隐含的

      • 在根path(/ image)中:

        • 客户负责build立图像与用户之间的关系,或者;

        • 如果正在使用POST请求提供安全上下文来创build映像,则服务器可以隐式地build立经过身份validation的用户和映像之间的关系。

    • 将图像embedded为用户的一部分

  2. 第二个决定是关于如何表示图像资源

    • 由于Base 64编码JSON有效载荷
    • 作为多部分有效载荷

这将是我的决定轨迹:

  • 除非有强有力的例子,否则我通常喜欢devise而不是性能。 它使系统更易于维护,并且可以被集成商更容易地理解。
  • 所以我的第一个想法是去图像资源的Base64表示,因为它可以让你保持所有的JSON。 如果您select此选项,则可以根据需要为资源pathbuild模。
    • 如果用户和图像之间的关系是1到1,那么如果两个数据集同时更新,我希望将图像build模为一个属性。 在任何其他情况下,您可以自由select将图像build模为属性,通过PUT或PATCH更新图像,或作为单独的资源。
  • 如果select多部分有效载荷,我觉得不得不将自己的图像build模为一个资源,这样其他资源(在我们的例子中为用户资源)就不会受到为图像使用二进制表示的决定的影响。

那么问题selectbase64 vs multipart会有什么性能影响吗? 。 我们可以认为以多部分格式交换数据应该更有效率。 但是这篇文章展示了这两种表示在大小方面的差异很小。

我的selectBase64:

  • 一致的devise决策
  • 性能影响微乎其微
  • 当浏览器了解数据URI(base64编码图像)时,如果客户机是浏览器,则不需要转换这些数据URI
  • 我不会投票决定是把它作为一个属性还是独立的资源,这取决于你的问题领域(我不知道)和你的个人喜好。

OP在这里 (我在两年后回答这个问题,Daniel Cerecedo发表的post一次也不错,但是web服务发展非常快)

经过三年的全职软件开发 (同时也关注软件架构,项目pipe理和微服务架构),我绝对select第二种方式(但只有一个通用端点)作为最佳方式。

如果您对图像有特殊的端点,则可以为您处理图像提供更多的function。

对于移动应用程序(iOS / Android)和前端(使用React),我们都有相同的REST API(Node.js)。 这是2017年,所以你不想要存储图片本地化,你想上传他们来云存储(谷歌云,S3,cloudinary,…),因此你想要一些一般的处理。

我们典型的stream程是,只要你select图像,它开始上传在后台(通常POST /图像端点),上传后返回您的ID。 这是真正用户友好的,因为用户select图像,然后通常进行一些其他领域(即地址,名称…),因此,当他点击“发送”button,图像通常已经上传。 他不会等着看屏幕说“上传…”。

获取图像也是一样。 特别感谢手机和有限的移动数据,你不想发送原始图像,你想发送resize的图像,所以他们不占用太多的数据(并使您的移动应用程序更快,你通常不想调整它的大小,你想要的图像完全符合你的观点)。 出于这个原因,好的应用程序正在使用像cloudinary(或我们有我们自己的图像服务器resize)。

另外,如果数据不是私人的,那么你只需将URL发送回app / frontend就可以直接从云存储中下载,这对你的服务器来说是一个巨大的带宽节省和处理时间。 在我们更大的应用程序中,每个月都会下载大量的TB,您不希望直接在您的每个REST API服务器上处理这个服务器,这个服务器专注于CRUD操作。 你想在一个地方处理它(我们的Imageserver,它有caching等),或者让云服务处理所有这些。


缺点:你应该考虑的唯一“缺点”是“未分配图像”。 用户select图像,并继续填写其他领域,但他说“不”,关掉应用程序或标签,但同时你成功上传的图像。 这意味着你已经上传了没有分配到任何地方的图片。

有几种方法来处理这个问题。 最简单的一个是“我不在乎”,这是相关的,如果这种情况不经常发生,或者您甚至希望存储用户向您发送的每个图像(出于任何原因),并且您不希望任何删除。

另一个也很容易 – 你有CRON,即每周,你删除所有未分配的图像超过一个星期。

你的第二个解决scheme可能是最正确的。 您应该按照预期方式使用HTTP规范和mimetypes,并通过multipart/form-data上传文件。 至于处理关系,我会用这个过程(记住我对你的假设或系统devise知之甚less):

  1. POST/users创build用户实体。
  2. 将映像发布到/images ,确保将一个Location标题返回到可以根据HTTP规范检索图像的Location
  3. /users/carPhoto/users/carPhoto并为其分配步骤2的Location标题中给出的照片的ID。

没有简单的解决scheme。 每种方式都有其优点和缺点。 但是规范的方法是使用第一个选项: multipart/form-data 。 正如W3推荐指南所说

内容types“multipart / form-data”应该用于提交包含文件,非ASCII数据和二进制数据的表单。

我们并没有发送表格,但隐含的原则仍然适用。 使用base64作为二进制表示是不正确的,因为您使用不正确的工具来实现您的目标,另一方面,第二个选项强制您的API客户端执行更多工作,以便使用您的API服务。 您应该在服务器端努力工作,以便提供易于使用的API。 第一个选项不容易debugging,但是当你这样做的时候,它可能永远不会改变。

使用multipart/form-data你坚持REST / HTTP的哲学。 你可以在这里查看类似问题的答案。

另一个select是如果混合替代品,你可以使用multipart / form-data,而不是单独发送每个值,你可以发送一个名为payload的值,里面有json的有效载荷。 (我尝试了这种方法使用ASP.NET WebAPI 2,并正常工作)。