关于json:REST API – 文件(即图像)处理 – 最佳实践

REST API - file (ie images) processing - best practices

我们正在使用RESTAPI开发服务器,RESTAPI接受JSON并响应它。问题是,如果需要将图像从客户机上载到服务器。

还要注意,我在讨论用例,其中实体(用户)可以有文件(carphoto、licensephoto),也可以有其他属性(name、email…),但是当您创建新用户时,您不发送这些图像,它们是在注册过程之后添加的。

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

1。使用多部分/表单数据而不是JSON

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

缺点:它不再是JSON了,与多部分/表单数据相比,它更易于测试、调试等。

2。允许更新单独的文件

创建新用户的post请求不允许添加图像(在我们的用例中,我在beggining中是这样说的),上传图片是通过将请求作为多部分/表单数据发送到例如/users/4/carphoto来完成的。

好:所有的东西(除了文件上传本身)都保存在JSON中,很容易测试和调试(您可以记录完整的JSON请求,而不用担心它们的长度)。

缺点:这是不直观的,你不能一次发布或放置实体的所有变量,而且这个地址/users/4/carPhoto可以被更多地视为一个集合(标准的RESTAPI用例看起来像这个/users/4/shipments)。通常您不能(也不想)获取/放置实体的每个变量,例如users/4/name。您可以使用get获取名称,使用put at users/4更改名称。如果ID后面有内容,则通常是另一个集合,如用户/4/评论

三。使用base64

以JSON的形式发送,但使用base64编码文件。

好的:和第一个解决方案一样,它是尽可能安静的服务。

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

我真的很想用2号解决方案,但它有缺点…任何人都能让我更好地了解"什么是最好的"解决方案?

我的目标是提供尽可能多的标准的RESTful服务,而我希望尽可能简单。


请看这里(两年后我回答这个问题,丹尼尔·塞雷西多的帖子一次不错,但是网络服务发展得很快)

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

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

对于移动应用程序(iOS/android)和前端应用程序(使用react),我们有相同的rest api(node.js)。这是2017年,因此您不希望在本地存储图像,您希望将其上传到云存储(Google Cloud、S3、Cloudinary等),因此您希望对其进行一些常规处理。

我们的典型流程是,一旦您选择了一个图像,它就开始在后台上载(通常是在/images端点上发布),并在上载后返回ID。这是非常用户友好的,因为用户选择一个图像,然后通常继续使用其他一些字段(例如地址、名称等),因此当他点击"发送"按钮时,图像通常已经上传。他不会等着看屏幕上说"上传…"。

获取图像也是如此。尤其是由于手机和有限的移动数据,你不想发送原始图像,你想发送调整后的图像,所以它们不需要太多的带宽(为了使你的移动应用程序更快,你通常根本不想调整大小,你想让图像完全适合你的视图)。因此,好的应用程序正在使用类似于Cloudinary的东西(或者我们有自己的图像服务器来调整大小)。

此外,如果数据不是私有的,那么您只需将其发送回app/frontend-just-url,它就可以直接从云存储中下载数据,这为您的服务器节省了大量的带宽和处理时间。在我们更大的应用程序中,每个月都会下载大量的terabytes,您不希望在每个RESTAPI服务器上直接处理这些问题,因为它们都专注于CRUD操作。您希望在一个地方(我们的ImageServer,它有缓存等)处理这些问题,或者让云服务处理所有这些问题。

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

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

另一个也很简单——你有cron,也就是说,每周删除所有超过一周的未分配图像。


有几个决定要做:

  • 第一个关于资源路径:

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

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

      • 在根路径(/image)中:

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

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

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

  • 第二个决定是如何表示图像资源:

    • AS Base 64编码的JSON负载
    • 作为多部分有效载荷
  • 这是我的决定轨迹:

    • 我通常喜欢设计而不是性能,除非有一个强有力的理由。它使系统更易于维护,更容易被集成商理解。
    • 所以我的第一个想法是使用图像资源的base64表示,因为它允许您保留所有JSON。如果选择此选项,则可以根据需要为资源路径建模。
      • 如果用户和图像之间的关系是1到1,我希望将图像建模为一个属性,特别是当两个数据集同时更新时。在任何其他情况下,您可以自由选择将图像建模为属性、通过放置或修补程序更新图像,或者作为单独的资源。
    • 如果您选择多部分有效负载,我将被迫将映像建模为自己的资源,这样其他资源(在我们的例子中是用户资源)就不会受到对映像使用二进制表示的决定的影响。

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

    我的选择基准64:

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


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

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

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

    The content type"multipart/form-data" should be used for submitting forms that contain files, non-ASCII data, and binary data.

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

    使用multipart/form-data时,您会坚持rest/http的理念。您可以在此处查看类似问题的答案。

    另一个选项是,如果混合使用其他选项,您可以使用多部分/表单数据,但您可以发送一个名为payload的值,其中包含JSON有效负载。(我使用ASP.NET WebAPI 2尝试了这种方法,但效果很好)。