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

lib*_*bik 140 rest json 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服务,同时我希望尽可能简化它.

lib*_*bik 116

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

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

如果您有图像的特殊端点,它将为您提供比处理图像更多的功能.

我们有两个相同的REST API(Node.js) - 移动应用程序(iOS/android)和前端(使用React).这是2017年,因此你不想存储图像localy,你想上传它们来云存储(谷歌云,s3,cloudinary,...),因此你想要一些一般处理它们.

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

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

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


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

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

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

  • @AdromilBalais-RESTful API是无状态的,因此它什么也不做(服务器不跟踪使用者的状态)。服务的使用者(即网页或移动设备)负责处理失败的请求,因此,使用者必须确定在此请求失败后是否立即调用相同的请求或该怎么办(即显示“图像上传失败-想要重试”) (3认同)
  • @MartinMuzatk​​o - 确实如此,它选择了第二个选项并告诉你应该如何使用它以及为什么.如果你的意思是"但这不是一个完美的选择,它允许你在一个请求中发送所有内容并且没有暗示" - 是的,遗憾的是没有这样的解决方案. (3认同)
  • 如果 [只要您选择图像,它就开始在后台上传(通常在 /images 端点上发布),上传后返回 ID] 当请求因互联网连接失败时会发生什么?您会在用户继续处理其他一些字段(即地址、姓名等)时提示他们吗?我敢打赌,您仍然会等到用户点击“发送”按钮并重试您的请求,让他们在看着屏幕说“正在上传...”时等待。 (2认同)
  • 很有启发性的答案。谢谢回答。 (2认同)
  • 这并没有真正解决最初的问题。这只是说“使用云服务” (2认同)
  • 开放所有文件上传路由可以用来攻击服务器对吗?这会导致服务器负载增加以及不必要的 aws S3 成本,对吗? (2认同)

Dan*_*edo 90

有几个决定要做:

  1. 第一个关于资源路径:

    • 将图像建模为资源:

      • 嵌套在用户(/ user /:id/image)中:隐式地创建用户和图像之间的关系

      • 在根路径(/ image)中:

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

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

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

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

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

这将是我的决定轨道:

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

接下来的问题:选择base64和multipart是否有任何性能影响?.我们可以认为以多部分格式交换数据应该更有效率.但是这篇文章表明两种表示在大小方面的差异很小.

我的选择Base64:

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

  • 我们不能使用protobuf等其他序列化协议对数据进行编码吗?基本上我试图了解是否有其他更简单的方法来解决base64编码带来的大小和处理时间的增加. (3认同)

mmc*_*han 12

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

  1. POST/users创建用户实体.
  2. POST图像到/images,确保Location按照HTTP规范将标题返回到可以检索图像的位置.
  3. PATCH/users/carPhoto并为其分配在给定照片的ID Location步骤2的报头.

  • 通常当您选择第二个选项时,首选上传媒体元素并将媒体标识符返回给客户端,然后客户端可以发送包括媒体标识符的实体数据,这些方法避免了破坏的实体o不匹配信息. (4认同)
  • 我对“客户端将如何使用 API”没有任何直接控制权......问题在于没有修补某些资源的“死”图片...... (2认同)

Kel*_*ero 6

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

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

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

使用multipart/form-data您坚持 REST/http 哲学。您可以在此处查看类似问题的答案

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

  • W3 推荐指南在这里无关紧要,因为它是在 HTML 4 规范的上下文中。 (2认同)