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

时间:2015-10-22 10:44:38

标签: json rest file-upload restful-architecture

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

另请注意,我说的是用例,其中实体(用户)可以拥有文件(carPhoto,licensePhoto),还有其他属性(名称,电子邮件...),但是当您创建新用户时,您不会发送这些图像后,它们会在注册过程后添加。

我所知道的解决方案,但每个解决方案都有一些缺陷

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

:POST和PUT请求尽可能具有RESTful,它们可以包含文本输入和文件。

缺点:它不再是JSON,与multipart / form-data相比,它更容易测试,调试等

2。允许更新单独的文件

用于创建新用户的POST请求不允许添加图像(在我们的用例中我在开始时的说法是可以的),上传图片是通过PUT请求作为multipart / form-data完成的,例如/ users / 4 / carPhoto

:一切(除了文件上传本身)都保留在JSON中,它很容易测试和调试(您可以记录完整的JSON请求而不用担心它们的长度)

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

第3。使用Base64

以JSON格式发送,但使用Base64编码文件。

good :与第一个解决方案相同,它尽可能地提供RESTful服务。

缺点:再一次,测试和调试更糟糕(正文可以有兆字节的数据),大小和处理时间都在增加 - 客户端和服务器

我真的很想使用解决方案。 2,但它有它的缺点...任何人都可以给我一个更好的洞察力"什么是最好的"溶液

我的目标是尽可能多地使用RESTful服务,而我希望尽可能简化它。

4 个答案:

答案 0 :(得分:87)

OP在这里(两年后我回答这个问题,Daniel Cerecedo的帖子一次都不错,但网络服务发展得非常快)

经过三年的全职软件开发(重点关注软件架构,项目管理和微服务架构)我绝对选择第二种方式(但有一个通用端点)作为最佳方式。< / p>

如果您有一个特殊的图像端点,它会比处理这些图像更省电。

我们有两个相同的REST API(Node.js) - 移动应用程序(iOS / android)和前端(使用React)。这是2017年,因此您不希望在本地存储图像,您希望将它们上传到云端存储(Google云端,s3,cloudinary,...),因此您需要对它们进行一些常规处理。

我们的典型流程是,一旦您选择了图像,它就会开始在背景上上传(通常是POST在/ images端点上),上传后会返回ID。这实际上是用户友好的,因为用户选择图像然后通常继续其他一些字段(即地址,名称......),因此当他点击“发送”按钮时,图像通常已经上传。他不等着看屏幕说“上传......”。

获取图像同样如此。特别是由于移动电话和有限的移动数据,你不想发送原始图像,你想发送调整大小的图像,所以他们没有带那么多带宽(并且为了让你的移动应用更快,你通常不想要要完全调整大小,您希望图像完全适合您的视图)。出于这个原因,好的应用程序正在使用像cloudinary(或者我们有自己的图像服务器来调整大小)。

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

缺点:您应该想到的唯一“缺点”是“未分配图像”。用户选择图像并继续填写其他字段,但随后他说“不”并关闭应用程序或标签,但同时您成功上传了图像。这意味着您已上传未在任何地方分配的图像。

有几种方法可以解决这个问题。最简单的是“我不在乎”,这是一个相关的,如果这种情况不经常发生,或者你甚至希望存储用户发送给你的每一个图像(出于任何原因),你不想要任何删除。

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

答案 1 :(得分:76)

要做出几项决定

  1. 第一个关于资源路径

    • 将图像建模为资源:

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

      • 在根路径(/ image)中:

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

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

    • 将图片作为用户的一部分嵌入

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

    • 作为Base 64编码的JSON有效负载
    • 作为多部分有效载荷
  3. 这将是我的决定轨道:

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

    接下来是问题:选择base64与多部分有什么性能影响吗?。我们可以认为以多部分格式交换数据应该更有效率。但this article显示两种表示在大小方面的差异很小。

    我的选择Base64:

    • 一致的设计决定
    • 性能影响可忽略
    • 当浏览器理解数据URI(base64编码图像)时,如果客户端是浏览器,则无需转换它们
    • 我不会就是否将其作为属性或独立资源投票,这取决于您的问题域(我不知道)和您的个人偏好。

答案 2 :(得分:7)

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

  1. POST/users创建用户实体。
  2. POST将图片发送至/images,确保将Location标题返回到可根据HTTP规范检索图片的位置。
  3. PATCH/users/carPhoto并为其指定第2步Location标题中提供的照片ID。

答案 3 :(得分:2)

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

  

内容类型&#34; multipart / form-data&#34;应该用于提交包含文件,非ASCII数据和二进制数据的表单。

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

使用multipart/form-data您坚持使用REST / http理念。您可以查看类似问题here的答案。

如果混合备选方案的另一个选项,您可以使用multipart / form-data但不是将每个值分开发送,您可以发送一个名为payload的值,其中包含json有效负载。 (我尝试使用ASP.NET WebAPI 2这种方法并且工作正常)。