REST API-文件(即图像)处理-最佳做法


193

我们正在使用REST API开发服务器,该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)。通常,您不能(也不想)获取/放置实体的每个变量,例如users / 4 / name。您可以使用GET获取名称,并使用users / 4在PUT上更改名称。如果id后面有东西,通常是另一个集合,例如users / 4 / reviews

3.使用Base64

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

良好:与第一个解决方案相同,它是尽可能多的RESTful服务。

缺点:再次,测试和调试要差很多(主体可能具有兆字节的数据),客户端和服务器的大小和处理时间都增加了


我真的很想使用解决方案编号。2,但是它有其缺点...任何人都可以给我更好的洞察力,即“什么是最好的”解决方案?

我的目标是使RESTful服务包含尽可能多的标准,而我想使其尽可能简单。


:您也可能会发现这个有用stackoverflow.com/questions/4083702/...
MARKON

5
我知道这个话题很老,但是最近我们遇到了这个问题。最好的方法类似于您的2。我们将文件直接上传到API,然后将这些文件附加到模型中。在这种情况下,您可以在表单之前,之后或同一页面上创建上传图像,这并不重要。好讨论!
Tiago Matos

2
@TiagoMatos-是的,确切的是,我在最近接受的一个答案中描述了它
libik

6
感谢您提出这个问题。
Zuhayer Tahir

1
“这个地址/ users / 4 / carPhoto也可以被视为一个集合” –不,它看起来不像一个集合,也不一定会被视为一个集合。与不是集合而是单个资源的资源有关系是完全可以的。
B12Toaster

Answers:


150

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

经过三年的全职软件开发(也侧重于软件体系结构,项目管理和微服务体系结构),我无疑选择了第二种方法(但有一个通用端点)作为最佳方法。

如果您有一个特殊的图像端点,则可以为您处理这些图像提供更多的功能。

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

我们的典型流程是,一旦选择了图像,它就会开始在后台上传(通常是在/ images端点上进行POST),并在上传后返回ID。这确实是用户友好的,因为用户选择一个图像然后通常继续进行其他一些字段(即地址,名称等),因此,当他单击“发送”按钮时,该图像通常已经上传。他没有等待,而是看着屏幕上显示“正在上传...”。

获取图像也是如此。特别是由于手机和有限的移动数据,您不想发送原始图像,不想发送经过调整大小的图像,因此它们不会占用太多带宽(并且为了使移动应用程序更快,您通常不希望要完全调整它的大小,您需要图像完全适合您的视图)。因此,好的应用程序正在使用诸如cloudinary之类的东西(或者我们确实有自己的图像服务器来调整大小)。

另外,如果数据不是私有的,那么您仅将URL发送回应用程序/前端,然后直接从云存储下载它,这为服务器节省了带宽和处理时间,为您节省了很多时间。在我们更大的应用程序中,每个月下载大量的TB,您不想直接在专注于CRUD操作的每个REST API服务器上进行处理。您想在一处处理(我们的Imageserver,有缓存等),或者让云服务处理所有这些。


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

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

另一个也很容易-您拥有CRON,即每个星期都删除了一个星期以上的所有未分配图像。


如果由于互联网连接而导致请求失败,如果[选择图像后,它开始在后台开始上传(通常在/ images端点上为POST),并在上传后返回ID),将会发生什么情况?当用户继续进行其他一些字段(例如地址,姓名,...)时,您是否会提示用户?我敢打赌,您仍然会等到用户点击“发送”按钮,然后重试您的请求,使他们在观看屏幕上显示“正在上传...”时等待。
Adromil Balais

5
@AdromilBalais-RESTful API是无状态的,因此不执行任何操作(服务器不跟踪使用者的状态)。服务的使用者(即网页或移动设备)负责处理失败的请求,因此,使用者必须决定在此请求失败后是否立即调用相同的请求,或者该怎么做(即显示“图像上传失败-想要重试” “)
libik

2
很有启发性的答案。谢谢回答。
Zuhayer Tahir

这并不能真正解决最初的问题。这只是说“使用云服务”
Martin Muzatk​​o

3
@MartinMuzatk​​o-是的,它选择第二个选项,并告诉您应该如何使用它以及为什么。如果您的意思是“但是这不是一个完美的选择,它允许您在一个请求中无提示地发送所有内容”,是的,很遗憾,没有这样的解决方案。
libik

102

要做出几个决定

  1. 第一个关于资源路径

    • 单独将图像建模为资源:

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

      • 在根路径(/ image)中:

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

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

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

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

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

这将是我的决策轨道:

  • 除非有充分的理由,否则我通常会优先考虑设计而不是性能。它使系统更易于维护,并且可以被集成商更容易地理解。
  • 所以我的第一个想法是选择图像资源的Base64表示形式,因为它可以让您保留所有JSON。如果选择此选项,则可以根据需要对资源路径进行建模。
    • 如果用户和图像之间的关系是1到1,则我希望将图像建模为属性,特别是如果两个数据集同时更新。在任何其他情况下,您都可以自由选择将图像建模为属性,通过PUT或PATCH更新图像,或作为单独的资源。
  • 如果您选择多部分有效负载,那么我将不得不将图像建模为资源本身,因此其他资源(在我们的情况下为用户资源)不会受到对图像使用二进制表示形式的决定的影响。

然后是一个问题:选择base64和multipart会对性能产生影响吗?。我们可以认为多部分格式的数据交换应该更有效。但是本文显示了这两种表示形式在大小方面几乎没有什么不同。

我选择Base64:

  • 一致的设计决策
  • 对性能的影响可忽略不计
  • 由于浏览器了解数据URI(以base64编码的图像),因此如果客户端是浏览器,则无需进行转换。
  • 对于将其作为属性还是独立资源,我不会投票,这取决于您的问题域(我不知道)和您的个人偏好。

3
我们不能使用其他序列化协议(例如protobuf等)对数据进行编码吗?基本上,我试图了解是否还有其他更简单的方法来解决base64编码附带的大小和处理时间增加的问题。
安迪·杜弗雷斯

1
非常引人入胜的答案。感谢您的逐步方法。它使我更好地理解了您的观点。
Zuhayer Tahir

13

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

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

1
我没有“客户端将如何使用API​​”的任何直接控制权...问题是“死”图片没有修补到某些资源上……
libik 2015年

4
通常,当您选择第二个选项时,最好先上传媒体元素并将媒体标识符返回给客户端,然后客户端可以发送包括媒体标识符的实体数据,这些方法可以避免实体损坏或信息不匹配。
凯勒曼·里维罗

2

没有简单的解决方案。每种方式各有利弊。但规范的方法是使用第一个选项:multipart/form-data。正如W3推荐指南所说

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

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

使用multipart/form-data你贴与REST / HTTP理念。您可以在此处查看类似问题的答案。

如果混合使用其他选项,则可以选择使用multipart / form-data,但不必将每个值分开发送,而是可以发送一个名为有效负载的值,其中包含json有效负载。(我使用ASP.NET WebAPI 2尝试了这种方法,效果很好)。


2
W3推荐指南在这里是无关紧要的,因为它与HTML 4规范有关。
约翰·

1
非常正确...。“非ASCII数据”需要多部分吗?在二十一世纪?在UTF-8世界中?当然,今天这是一个荒谬的建议。我什至感到惊讶的是,HTML已经存在了4天,但有时Internet基础结构世界的发展非常缓慢。
雷·托尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.