ChatGPT解决这个技术问题 Extra ChatGPT

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

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

注意:我还在谈论一个用例,其中实体(用户)可以有多个文件(carPhoto、licensePhoto)并且还有其他属性(名称、电子邮件...),但是当您创建新用户时,您不需要'不要发送这些图像,它们是在注册过程之后添加的。

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

1. 使用 multipart/form-data 代替 JSON

good : 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 获取名称并在 users/4 使用 PUT 更改它。如果id后面有东西,一般是另一个集合,比如users/4/reviews

3.使用Base64

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

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

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

我真的很想使用解决方案。 2,但它有它的缺点......任何人都可以让我更好地了解“什么是最好的”解决方案?

我的目标是让 RESTful 服务包含尽可能多的标准,同时我想让它尽可能简单。

您可能还会发现这很有用:stackoverflow.com/questions/4083702/…
我知道这个话题很老,但我们最近遇到了这个问题。我们拥有的最佳方法类似于您的第 2 种方法。我们将文件直接上传到 API,然后将这些文件附加到模型中。在这种情况下,您可以在表单之前、之后或在同一页面上创建上传图像,这并不重要。好讨论!
@TiagoMatos - 是的,确切地说,我在最近接受的一个答案中描述了它
感谢您提出这个问题。
“这个地址 /users/4/carPhoto 也可以被认为是一个集合”——不,它看起来不像一个集合,也不一定被认为是一个集合。与不是集合而是单个资源的资源建立关系是完全可以的。

p
pvsfair

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

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

如果你有一个特殊的图像端点,它会给你更多的权力来处理这些图像。

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

我们的典型流程是,一旦您选择了一张图片,它就会开始在后台上传(通常是在 /images 端点上发布),上传后返回您的 ID。这对用户来说非常友好,因为用户选择了一张图片,然后通常会继续处理其他一些字段(即地址、姓名等),因此当他点击“发送”按钮时,通常已经上传了图片。他没有等待,看着屏幕说“正在上传……”。

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

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

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

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

另一个也很简单——你有 CRON 和即每周,你删除所有超过一周的未分配图像。


如果 [一旦您选择图像,它就会开始在后台上传(通常是在 /images 端点上发布),上传后返回您的 ID] 当请求由于互联网连接而失败时会发生什么?当用户继续处理其他一些字段(即地址、姓名……)时,您会提示他们吗?我敢打赌,您仍然会等到用户点击“发送”按钮并重试您的请求,让他们在观看屏幕上说“正在上传...”的同时等待。
@AdromilBalais - RESTful API 是无状态的,因此它什么都不做(服务器不跟踪消费者的状态)。服务的消费者(即网页或移动设备)负责处理失败的请求,因此消费者必须决定是否在此请求失败后立即调用相同的请求或做什么(即显示“图像上传失败 - 想再试一次")
非常翔实和启发性的答案。谢谢回答。
这并不能真正解决最初的问题。这只是说“使用云服务”
@MartinMuzatko - 它确实,它选择了第二个选项并告诉你应该如何使用它以及为什么。如果您的意思是“但这不是一个完美的选择,它允许您在一个请求中发送所有内容并且没有暗示” - 是的,不幸的是没有这样的解决方案。
D
Daniel Cerecedo

有几个决定要做:

第一个关于资源路径: 将图像单独建模为资源: 嵌套在用户(/user/:id/image)中:隐式建立用户与图像之间的关系 在根路径(/image)中:客户负责建立图像与用户之间的关系,或;如果用于创建图像的 POST 请求提供了安全上下文,则服务器可以隐式地在经过身份验证的用户和图像之间建立关系。将图像作为用户的一部分嵌入第二个决定是关于如何表示图像资源: 作为 Base 64 编码的 JSON 有效负载 作为多部分有效负载

这将是我的决策轨迹:

除非有充分的理由,否则我通常更喜欢设计而不是性能。它使系统更易于维护,集成商更容易理解。

所以我的第一个想法是使用图像资源的 Base64 表示,因为它可以让您保留所有 JSON。如果您选择此选项,您可以根据需要对资源路径进行建模。如果用户和图像之间的关系是 1 比 1,我倾向于将图像建模为属性,特别是如果两个数据集同时更新。在任何其他情况下,您可以自由选择将图像建模为属性,通过 PUT 或 PATCH 更新它,或者作为单独的资源。

如果用户和图像之间的关系是 1 比 1,我倾向于将图像建模为属性,特别是如果两个数据集同时更新。在任何其他情况下,您可以自由选择将图像建模为属性,通过 PUT 或 PATCH 更新它,或者作为单独的资源。

如果您选择多部分有效负载,我会觉得有必要将图像建模为自己的资源,以便其他资源(在我们的例子中为用户资源)不受使用图像二进制表示的决定的影响。

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

我的选择 Base64:

一致的设计决策

可忽略的性能影响

由于浏览器理解数据 URI(base64 编码图像),如果客户端是浏览器,则无需转换这些

我不会就是否将其作为属性或独立资源投票,这取决于您的问题域(我不知道)和您的个人偏好。


我们不能使用 protobuf 等其他序列化协议对数据进行编码吗?基本上我想了解是否有其他更简单的方法来解决 base64 编码带来的大小和处理时间增加。
非常引人入胜的答案。感谢您的逐步方法。这让我更好地理解了你的观点。
m
mmcclannahan

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

POST 到 /users 以创建用户实体。将图像发布到 /images,确保将 Location 标头返回到可以根据 HTTP 规范检索图像的位置。 PATCH 到 /users/carPhoto 并将其分配给步骤 2 的 Location 标头中给出的照片的 ID。


我对“客户端将如何使用 API”没有任何直接控制......这个问题是“死”图片没有修补到某些资源......
通常当您选择第二个选项时,最好先上传媒体元素并将媒体标识符返回给客户端,然后客户端可以发送包括媒体标识符在内的实体数据,这些方法避免了损坏的实体或不匹配的信息。
C
Community

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

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

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

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

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


W3 推荐指南在这里无关紧要,因为它是在 HTML 4 规范的上下文中。
非常正确....“非 ASCII 数据”需要多部分?在二十一世纪?在 UTF-8 世界中?当然,这对今天来说是一个荒谬的建议。我什至对 HTML 4 天的存在感到惊讶,但有时 Internet 基础设施世界的发展非常缓慢。
@RayToal 您能否详细说明 multipart/form-data 与 HTML 版本的关系。我的理解是 HTML 5 是支持多媒体的扩展。 HTML 5 规范中是否有任何新增内容来处理二进制数据?