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

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

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


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

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

: POST和PUT请求是尽可能rest的,它们可以包含文本输入和文件。

缺点:它不再是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。您可以在用户/4处使用get获取名称并使用PUT更改名称。如果在id后面有东西,它通常是另一个集合,比如users/4/reviews

3.使用Base64

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

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

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


我很想用解不。但它也有它的缺点……谁能给我一个更好的“什么是最好的”解决方案?

我的目标是使基于rest的服务包含尽可能多的标准,同时我希望使它尽可能简单。

229273 次浏览

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

  1. POST/users来创建用户实体。
  2. POST图像到/images,确保返回一个Location头到图像可以根据HTTP规范检索的地方。
  3. PATCH/users/carPhoto,并将第2步的Location头中给出的照片的ID赋给它。

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

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

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

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

如果混合使用其他选项,你可以使用multipart/form-data,但不是单独发送每个值,你可以发送一个名为payload的值,其中包含json payload。(我尝试使用ASP。NET WebAPI 2和工作良好)。

有几个决定要做:

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

    • 将图像建模为其自身的资源:

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

      • 根路径(/image):

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

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

        • 李< / ul > < / > 李< / ul > < / >
        • 嵌入图像作为用户的一部分

        • 李< / ul > < / >
        • 第二个决定是关于如何表示图像资源的:

          • 作为Base 64编码的JSON有效载荷
          • 作为一个多部分有效载荷
          • 李< / ul > < / >

这将是我的决定轨道:

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

    接下来的问题是:选择base64和multipart对性能有什么影响吗?。我们可以认为,以多部分格式交换数据应该更有效。但是这篇文章显示了这两种表示在大小方面的差别是多么小。

    我选择Base64:

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

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

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

如果您有一个图像的特殊端点,它将为您提供更大的处理这些图像的能力。

我们为移动应用(iOS/android)和前端应用(使用React)提供了相同的REST API (Node.js)。这是2017年,因此你不想在本地存储图像,你想把它们上传到一些云存储(谷歌cloud, s3, cloudinary,…),因此你想对它们进行一些一般性处理。

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

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

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


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

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

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