多部分HTTP响应

39

目标是让一个 Node.js / hapi API 服务器用两个东西响应浏览器的 AJAX 请求:

  • 媒体文件(例如图片)
  • 带有文件元数据的 JSON 对象

这两个东西之所以是分开的,仅因为二进制数据无法轻松地存储在 JSON 中。否则,这将是一个单一的资源。尽管如此,最好将它们发送到单个响应中。

我们使用 multipart/form-data 在单个请求中上传它们。在这种情况下,浏览器提供了内置机制来序列化请求体,大多数服务器端框架也知道如何解析它。但是,如何在相反的方向上对响应进行相同的操作呢?也就是说,服务器应该如何序列化主体以将其传输给客户端?

据我所知,multipart/mixed 可能是一个有用的内容类型。但是很少有人谈论这个问题。大多数人似乎都倾向于提供两个单独的 GET 路由,每个路由返回一个部分。我不喜欢这样做,因为它会引起竞态条件等问题。我错过了什么吗?

还请参阅我在 hapijs/discuss#563 中的问题。


1
因为媒体文件不能轻易地存储在JSON中。你尝试过将图像作为"data URI"在JSON响应中提供吗? - guest271314
将图像转换为base64,将json转换为base64,然后使用“.”分隔符将它们连接成一个字符串,这样做是否适合您?您可以将其作为字符串发送,然后在前端解码。 - Rex
1
这在某种程度上就是我所说的“容易”的部分。我可以对媒体文件进行base64编码,但这不仅会增加额外的处理,还会使文件大小增加约33%。我想我只是惊讶于这个问题在一个方向上得到了清晰的解决,而在另一个方向上则没有那么清晰。 - Seth Holladay
你可以将响应作为 multipart/form-data 提供,并使用 Response.formData() - guest271314
很酷,我不知道response.formData()。这将在这里非常有用。现在我必须想出如何在服务器上构建响应。我基本上需要pez的反向。 - Seth Holladay
如果元数据非常小,可以考虑将其放在自定义标头中,然后使图像对不知情的消费者立即可见。 - dsz
2个回答

15
您可以将响应作为 multipart/form-data 提供,并使用 Response.formData() 在客户端读取响应。
fetch("/path/to/server", {method:"POST", body:formData})
.then(response => response.formData())
.then(fd => {
  for (let [key, prop] of fd) {
    console.log(key, prop)
  }
})

let fd = new FormData();
fd.append("json", JSON.stringify({
  file: "image"
}));
fetch("data:image/gif;base64,R0lGODlhEAAQAPIAAP///wAAAMLCwkJCQgAAAGJiYoKCgpKSkiH+GkNyZWF0ZWQgd2l0aCBhamF4bG9hZC5pbmZvACH5BAAKAAAAIf8LTkVUU0NBUEUyLjADAQAAACwAAAAAEAAQAAADMwi63P4wyklrE2MIOggZnAdOmGYJRbExwroUmcG2LmDEwnHQLVsYOd2mBzkYDAdKa+dIAAAh+QQACgABACwAAAAAEAAQAAADNAi63P5OjCEgG4QMu7DmikRxQlFUYDEZIGBMRVsaqHwctXXf7WEYB4Ag1xjihkMZsiUkKhIAIfkEAAoAAgAsAAAAABAAEAAAAzYIujIjK8pByJDMlFYvBoVjHA70GU7xSUJhmKtwHPAKzLO9HMaoKwJZ7Rf8AYPDDzKpZBqfvwQAIfkEAAoAAwAsAAAAABAAEAAAAzMIumIlK8oyhpHsnFZfhYumCYUhDAQxRIdhHBGqRoKw0R8DYlJd8z0fMDgsGo/IpHI5TAAAIfkEAAoABAAsAAAAABAAEAAAAzIIunInK0rnZBTwGPNMgQwmdsNgXGJUlIWEuR5oWUIpz8pAEAMe6TwfwyYsGo/IpFKSAAAh+QQACgAFACwAAAAAEAAQAAADMwi6IMKQORfjdOe82p4wGccc4CEuQradylesojEMBgsUc2G7sDX3lQGBMLAJibufbSlKAAAh+QQACgAGACwAAAAAEAAQAAADMgi63P7wCRHZnFVdmgHu2nFwlWCI3WGc3TSWhUFGxTAUkGCbtgENBMJAEJsxgMLWzpEAACH5BAAKAAcALAAAAAAQABAAAAMyCLrc/jDKSatlQtScKdceCAjDII7HcQ4EMTCpyrCuUBjCYRgHVtqlAiB1YhiCnlsRkAAAOwAAAAAAAAAAAA==")
  .then(response => response.blob())
  .then(blob => {
    fd.append("file", blob);
    new Response(fd)
      .formData()
      .then(formData => {
        for (let [key, data] of formData) {
          console.log(key, data)
        }
      })
  })


1
非常好的例子,谢谢!这应该很好地适用于客户端部分。你知道如何在服务器上创建响应吗?我还需要这个才能使用它。 - Seth Holladay
@SethHolladay 请查看如何在Web Workers中上传文件,当FormData未定义。您应该也可以在ServiceWorker中使用Response.formData()从任意数据创建multipart/form-data,并使用创建的FormData作为参数响应一个新的Response(),如果您不想手动创建multipart/form-data - guest271314
@SethHolladay 请参阅https://github.com/whatwg/fetch/issues/392,https://github.com/whatwg/html/issues/3040。 - guest271314
我可能错了,但我认为这里不涉及服务工作者。我需要在Node.js服务器框架(类似于[Express](https://expressjs.com/))的路由处理程序中传输文件和元数据,这是我缺少的部分。然后我需要在前端接收响应并解析它,这应该可以使用您当前的示例完成。 - Seth Holladay
@SethHolladay 没有使用过Express或hapi服务器的经验。 ServiceWorker可以用作中介来为客户端提供响应,参见Chrome扩展程序:在访问之前阻止页面项目。可以具体提出一个问题,如何手动创建multipart/form-data,这应该提供了解决方案。相关规范 https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html,https://www.ietf.org/rfc/rfc2388.txt - guest271314
@SethHolladay https://dev59.com/E6bja4cB1Zd3GeqPgnQS如何手动创建多部分表单数据 - guest271314

4
如果您想要使用多部分格式,我认为在上传(POST / PUT)和检索(GET)期间使用完全相同的格式并没有任何本质上的问题。
当使用HTTP时,在两个方向上使用相同的传输格式确实很优雅。
然而,如果您想要在PUT / POST期间发送表单数据并在GET中返回JSON,则我会开始质疑这是否是正确的做法。
对于客户端来说,如果他们只想显示图像,则多部分会很麻烦。您考虑过只使用不同的端点;一个用于图像,一个用于其元数据吗?您有什么理由将它们合并到单个资源中?
或者,您也可以尝试嵌入图像中的信息。例如,JPEG允许使用EXIF添加自定义数据。至少您保留了直接打开图像的能力。
然而,我会总结说 multipart/mixed 适用于仅嵌入图像+json对象,但请注意:
  1. 它可能会对消费造成一些不便
  2. 它也有些不寻常
  3. 我非常确定多部分编码将要求您使用某些7位编码对图像进行编码,这将从本质上导致请求大小急剧增加。

3
将一个表单作为响应发送会感觉很奇怪。但是,呃,随便吧,对吧? :) 你知道有什么好的库可以用于构建响应吗?据我所知,hapi或其他框架都不知道如何序列化表单,只能解析它们。 - Seth Holladay
@MichaelPotter 我猜这个问题和答案已经有一段时间了,但我仍然有点怀疑你不能只做两个请求... - Evert
@Evert 我很欣赏怀疑论者。我正在生成一个可能需要相当长时间才能生成的pdf文件。在生成.pdf文件的同时,我还会生成将传递给电子签名解决方案的信息,告诉它在哪里签名以及其他信息。由于您持续的怀疑态度,我想到了一个主意,即调用两次,第一步生成pdf和json。第一步将缓存json,以便在第二次调用时返回。 - Be Kind To New Users
@MichaelPotter 不错! - Evert
@Dragas,你能详细说明一下你的问题吗?它不是很清楚。 - Evert
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接