我们正在使用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/出货量)。通常你不能(也不想)GET/PUT实体的每个变量,例如users/4/name。您可以在用户/4处使用get获取名称并使用PUT更改名称。如果在id后面有东西,它通常是另一个集合,比如users/4/reviews

3.使用Base64

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

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

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


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

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


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

POST到/users创建用户实体。 将图像POST到/images,确保返回一个Location标头到可以根据HTTP规范检索图像的位置。 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和工作良好)。

你需要做出以下几个决定:

The first about resource path: Model the image as a resource on its own: Nested in user (/user/:id/image): the relationship between the user and the image is made implicitly In the root path (/image): The client is held responsible for establishing the relationship between the image and the user, or; If a security context is being provided with the POST request used to create an image, the server can implicitly establish a relationship between the authenticated user and the image. Embed the image as part of the user The second decision is about how to represent the image resource: As Base 64 encoded JSON payload As a multipart payload

这将是我的决定轨道:

I usually favor design over performance unless there is a strong case for it. It makes the system more maintainable and can be more easily understood by integrators. So my first thought is to go for a Base64 representation of the image resource because it lets you keep everything JSON. If you chose this option you can model the resource path as you like. If the relationship between user and image is 1 to 1 I'd favor to model the image as an attribute specially if both data sets are updated at the same time. In any other case you can freely choose to model the image either as an attribute, updating the it via PUT or PATCH, or as a separate resource. If you choose multipart payload I'd feel compelled to model the image as a resource on is own, so that other resources, in our case, the user resource, is not impacted by the decision of using a binary representation for the image.

接下来的问题是:选择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,即每周,你删除所有未分配的图像超过一个星期。