json REST API - 文件(即图像)处理 - 最佳实践
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/33279153/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
REST API - file (ie images) processing - best practices
提问by libik
We are developing server with REST API, which accepts and responses with JSON. The problem is, if you need to upload images from client to server.
我们正在开发带有 REST API 的服务器,它接受和响应 JSON。问题是,如果您需要将图像从客户端上传到服务器。
Note also that I am talking about use-case, where entity (user) can have files (carPhoto, licensePhoto) and also have other properties (name, email...), but when you create new user, you dont send these images, they are added after the registration process.
另请注意,我在谈论用例,其中实体(用户)可以拥有文件(carPhoto、licensePhoto)并具有其他属性(姓名、电子邮件...),但是当您创建新用户时,您不会发送这些图像,它们是在注册过程后添加的。
The solutions I am aware of, but each of them have some flaws
我知道的解决方案,但每个都有一些缺陷
1. Use multipart/form-data instead of JSON
1. 使用 multipart/form-data 而不是 JSON
good: POST and PUT requests are as RESTful as possible, they can contain text inputs together with file.
好:POST 和 PUT 请求尽可能 RESTful,它们可以包含文本输入和文件。
cons: It is not JSON anymore, which is much easier to test, debug etc. compare to multipart/form-data
缺点:它不再是 JSON,与 multipart/form-data 相比,它更容易测试、调试等
2. Allow to update separate files
2. 允许更新单独的文件
POST request for creating new user does not allow to add images (which is ok in our use-case how I said at beggining), uploading pictures is done by PUT request as multipart/form-data to for example /users/4/carPhoto
创建新用户的 POST 请求不允许添加图像(这在我们的用例中是可以的,我在开始时说的),上传图片是通过 PUT 请求作为 multipart/form-data 完成的,例如 /users/4/carPhoto
good: Everything (except the file uploading itself) remains in JSON, it is easy to test and debug (you can log complete JSON requests without being afraid of their length)
好:一切(除了文件上传本身)都保留在 JSON 中,易于测试和调试(您可以记录完整的 JSON 请求,而不必担心它们的长度)
cons: It is not intuitive, you cant POST or PUT all variables of entity at once and also this address /users/4/carPhotocan be considered more as a collection (standard use-case for REST API looks like this /users/4/shipments). Usually you cant (and dont want to) GET/PUT each variable of entity, for example users/4/name . You can get name with GET and change it with PUT at users/4. If there is something after the id, it is usually another collection, like users/4/reviews
缺点:它不直观,您不能一次 POST 或 PUT 实体的所有变量,而且这个地址/users/4/carPhoto可以更多地被视为一个集合(REST API 的标准用例看起来像这样/users/4/shipments)。通常你不能(也不想)GET/PUT 实体的每个变量,例如 users/4/name 。您可以使用 GET 获取名称并使用位于 users/4 的 PUT 更改它。如果id后面有东西,通常是另一个集合,比如users/4/reviews
3. Use Base64
3.使用Base64
Send it as JSON but encode files with Base64.
将其作为 JSON 发送,但使用 Base64 对文件进行编码。
good: Same as first solution, it is as RESTful service as possible.
good:与第一个解决方案相同,它尽可能地使用 RESTful 服务。
cons: Once again, testing and debugging is a lot worse (the body can have megabytes of data), there is increase in size and also in processing time in both - client and server
缺点:再次,测试和调试更糟糕(主体可能有数兆字节的数据),客户端和服务器的大小和处理时间都增加了
I would really like to use solution no. 2, but it has its cons... Anyone can give me a better insight of "what is best" solution?
我真的很想使用解决方案。2,但它有它的缺点......任何人都可以让我更好地了解“什么是最好的”解决方案?
My goal is to have RESTful services with as much standards included as possible, while I want to keep it as simple as possible.
我的目标是让 RESTful 服务包含尽可能多的标准,同时我想让它尽可能简单。
采纳答案by libik
OP here(I am answering this question after two years, the post made by Daniel Cerecedo was not bad at a time, but the web services are developing very fast)
OP here(我是两年后回答这个问题,Daniel Cerecedo 的帖子一次也不错,但是网络服务发展非常快)
After three years of full-time software development(with focus also on software architecture, project management and microservice architecture) I definitely choose the second way (but with one general endpoint) as the best one.
经过三年的全职软件开发(同时专注于软件架构、项目管理和微服务架构),我肯定会选择第二种方式(但有一个通用端点)作为最好的方式。
If you have a special endpoint for images, it gives you much more power over handling those images.
如果你有一个特殊的图像端点,它会给你更多处理这些图像的能力。
We have the same REST API (Node.js) for both - mobile apps (iOS/android) and frontend (using React). This is 2017, therefore you don't want to store images locally, you want to upload them to some cloud storage (Google cloud, s3, cloudinary, ...), therefore you want some general handling over them.
我们为移动应用程序(iOS/android)和前端(使用 React)提供相同的 REST API (Node.js)。这是 2017 年,因此您不想在本地存储图像,而是想将它们上传到某个云存储(Google 云、s3、cloudinary 等),因此您需要对它们进行一些常规处理。
Our typical flow is, that as soon as you select an image, it starts uploading on background (usually POST on /images endpoint), returning you the ID after uploading. This is really user-friendly, because user choose an image and then typically proceed with some other fields (i.e. address, name, ...), therefore when he hits "send" button, the image is usually already uploaded. He does not wait and watching the screen saying "uploading...".
我们的典型流程是,一旦您选择图像,它就会开始在后台上传(通常在 /images 端点上发布),上传后返回 ID。这真的是用户友好的,因为用户选择一个图像,然后通常会继续一些其他字段(即地址,姓名,...),因此当他点击“发送”按钮时,图像通常已经上传。他没有等待,看着屏幕说“正在上传……”。
The same goes for getting images. Especially thanks to mobile phones and limited mobile data, you don't want to send original images, you want to send resized images, so they do not take that much bandwidth (and to make your mobile apps faster, you often don't want to resize it at all, you want the image that fits perfectly into your view). For this reason, good apps are using something like cloudinary (or we do have our own image server for resizing).
获取图像也是如此。特别是由于手机和有限的移动数据,您不想发送原始图像,而是发送调整大小的图像,因此它们不会占用那么多带宽(并且为了使您的移动应用程序更快,您通常不希望要完全调整它的大小,您希望图像完全适合您的视图)。出于这个原因,好的应用程序会使用类似 cloudinary 的东西(或者我们有自己的图像服务器来调整大小)。
Also, if the data are not private, then you send back to app/frontend just URL and it downloads it from cloud storage directly, which is huge saving of bandwidth and processing time for your server. In our bigger apps there are a lot of terabytes downloaded every month, you don't want to handle that directly on each of your REST API server, which is focused on CRUD operation. You want to handle that at one place (our Imageserver, which have caching etc.) or let cloud services handle all of it.
此外,如果数据不是私有的,那么您只需将 URL 发送回应用程序/前端,它就会直接从云存储下载它,这大大节省了服务器的带宽和处理时间。在我们更大的应用程序中,每个月都有大量 TB 的下载量,您不想直接在每个 REST API 服务器上处理这些,因为它专注于 CRUD 操作。你想在一个地方处理它(我们的图像服务器,它有缓存等)或让云服务处理所有这些。
Cons : The only "cons" which you should think of is "not assigned images". User select images and continue with filling other fields, but then he says "nah" and turn off the app or tab, but meanwhile you successfully uploaded the image. This means you have uploaded an image which is not assigned anywhere.
缺点:您应该想到的唯一“缺点”是“未分配图像”。用户选择图像并继续填充其他字段,但随后他说“不”并关闭应用程序或选项卡,但同时您成功上传了图像。这意味着您上传了未在任何地方分配的图像。
There are several ways of handling this. The most easiest one is "I don't care", which is a relevant one, if this is not happening very often or you even have desire to store every image user send you (for any reason) and you don't want any deletion.
有几种处理方法。最简单的是“我不在乎”,这是一个相关的,如果这种情况不经常发生,或者您甚至希望存储用户发送给您的每个图像(出于任何原因)并且您不想要任何图像删除。
Another one is easy too - you have CRON and i.e. every week and you delete all unassigned images older than one week.
另一个也很容易——你有 CRON,即每周,你删除所有超过一周的未分配图像。
回答by Daniel Cerecedo
There are several decisions to make:
有几个决定要做:
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
第一个关于资源路径:
将图像建模为资源:
嵌套在用户(/user/:id/image)中:隐式建立用户和图片之间的关系
在根路径(/image)中:
客户负责建立图像与用户之间的关系,或;
如果用于创建图像的 POST 请求提供了安全上下文,则服务器可以隐式地建立经过身份验证的用户和图像之间的关系。
将图像嵌入为用户的一部分
第二个决定是关于如何表示图像资源:
- 作为 Base 64 编码的 JSON 有效负载
- 作为多部分有效载荷
This would be my decision track:
这将是我的决策轨迹:
- 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 表示图像资源,因为它可以让您保留所有 JSON。如果选择此选项,则可以根据需要对资源路径进行建模。
- 如果用户和图像之间的关系是 1 比 1,我更愿意将图像建模为一个属性,特别是如果两个数据集同时更新。在任何其他情况下,您可以自由选择将图像建模为属性,通过 PUT 或 PATCH 更新它,或者作为单独的资源。
- 如果您选择多部分有效负载,我会觉得有必要将图像建模为自己的资源,以便其他资源(在我们的示例中为用户资源)不会受到对图像使用二进制表示的决定的影响。
Then comes the question: Is there any performance impact about choosing base64 vs multipart?. We could think that exchanging data in multipart format should be more efficient. But this articleshows how little do both representations differ in terms of size.
那么问题来了:选择 base64 与 multipart 对性能有什么影响吗?. 我们可以认为以多部分格式交换数据应该更有效。但是这篇文章显示了两种表示在大小方面的差异很小。
My choice Base64:
我的选择 Base64:
- Consistent design decision
- Negligible performance impact
- As browsers understand data URIs (base64 encoded images), there is no need to transform these if the client is a browser
- I won't cast a vote on whether to have it as an attribute or standalone resource, it depends on your problem domain (which I don't know) and your personal preference.
- 一致的设计决策
- 性能影响可忽略不计
- 由于浏览器理解数据 URI(base64 编码的图像),如果客户端是浏览器,则无需转换这些
- 我不会对是将其作为属性还是独立资源进行投票,这取决于您的问题域(我不知道)和您的个人偏好。
回答by mmcclannahan
Your second solution is probably the most correct. You should use the HTTP spec and mimetypes the way they were intended and upload the file via multipart/form-data. As far as handling the relationships, I'd use this process (keeping in mind I know zero about your assumptions or system design):
您的第二个解决方案可能是最正确的。您应该按照预期的方式使用 HTTP 规范和 mimetypes,并通过multipart/form-data. 至于处理关系,我会使用这个过程(请记住,我对您的假设或系统设计的了解为零):
POSTto/usersto create the user entity.POSTthe image to/images, making sure to return aLocationheader to where the image can be retrieved per the HTTP spec.PATCHto/users/carPhotoand assign it the ID of the photo given in theLocationheader of step 2.
POST来/users创建用户实体。POST图像到/images,确保将Location标头返回到可以根据 HTTP 规范检索图像的位置。PATCH到/users/carPhoto并为其分配在给定照片的IDLocation步骤2的报头。
回答by Kellerman Rivero
There's no easy solution. Each way has their pros and cons . But the canonical way is using the first option: multipart/form-data. As W3 recommendation guidesays
没有简单的解决办法。每种方式都有其优缺点。但规范的方式是使用第一个选项: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.
内容类型“multipart/form-data”应用于提交包含文件、非 ASCII 数据和二进制数据的表单。
We aren't sending forms,really, but the implicit principle still applies. Using base64 as a binary representation, is incorrect because you're using the incorrect tool for accomplish your goal, in other hand, the second option forces your API clients to do more job in order to consume your API service. You should do the hard work in the server side in order to supply an easy-to-consume API. The first option is not easy to debug, but when you do it, it probably never changes.
我们不是在发送表单,真的,但隐含的原则仍然适用。使用 base64 作为二进制表示是不正确的,因为您使用了不正确的工具来实现您的目标,另一方面,第二个选项迫使您的 API 客户端做更多的工作以使用您的 API 服务。您应该在服务器端努力工作,以提供易于使用的 API。第一个选项不容易调试,但当你这样做时,它可能永远不会改变。
Using multipart/form-datayou're sticked with the REST/http philosophy. You can view an answer to similar question here.
使用multipart/form-data您坚持 REST/http 哲学。您可以在此处查看类似问题的答案。
Another option if mixing the alternatives, you can use multipart/form-data but instead of send every value separate, you can send a value named payload with the json payload inside it. (I tried this approach using ASP.NET WebAPI 2 and works fine).
如果混合备选方案,另一种选择是,您可以使用 multipart/form-data 但不是单独发送每个值,您可以发送一个名为 payload 的值,其中包含 json 有效载荷。(我使用 ASP.NET WebAPI 2 尝试了这种方法并且工作正常)。

