我们正在使用 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 服务包含尽可能多的标准,同时我想让它尽可能简单。
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 和即每周,你删除所有超过一周的未分配图像。
有几个决定要做:
第一个关于资源路径: 将图像单独建模为资源: 嵌套在用户(/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 编码图像),如果客户端是浏览器,则无需转换这些
我不会就是否将其作为属性或独立资源投票,这取决于您的问题域(我不知道)和您的个人偏好。
您的第二个解决方案可能是最正确的。您应该按照预期的方式使用 HTTP 规范和 mimetypes,并通过 multipart/form-data
上传文件。至于处理关系,我会使用这个过程(请记住,我对您的假设或系统设计知之甚少):
POST 到 /users 以创建用户实体。将图像发布到 /images,确保将 Location 标头返回到可以根据 HTTP 规范检索图像的位置。 PATCH 到 /users/carPhoto 并将其分配给步骤 2 的 Location 标头中给出的照片的 ID。
没有简单的解决方案。每种方式都有其优缺点。但规范的方法是使用第一个选项: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 尝试了这种方法并且工作正常)。