如何使用fetch和FormData发送二进制数据(blob)?

23
以下代码按预期工作。在 Google Chrome 上打开页面 "https://wiki.epfl.ch/",并在开发者控制台上执行此代码。注意:页面 "https://wiki.epfl.ch/test.php" 不存在,因此加载失败,但这不是问题。

以下代码正常运行。在Google Chrome中打开页面"https://wiki.epfl.ch/",并在开发者控制台上执行此代码。请注意:"https://wiki.epfl.ch/test.php"页面不存在,因此无法加载,但这不是问题。

response = await fetch("https://wiki.epfl.ch/lapa-studio/documents/DTS/laser%20tutorial.pdf");
response.text().then(function(content) { 
  formData = new FormData();
  console.log(content.length);
  console.log(content);
  formData.append("content", content);

  fetch("https://wiki.epfl.ch/test.php", {method: 'POST', body: formData});
})

它记录:

content.length: 57234
content: %PDF-1.3
%���������
4 0 obj
<< /Length 5 0 R /Filter /FlateDecode >>
stream
x��K��F�����;¢�
...

前往“开发人员网络”选项卡,选择“test.php”页面,导航至“请求的有效负载:”,您可以看到此内容:

------WebKitFormBoundaryOJOOGb7N43BxCRlv
Content-Disposition: form-data; name="content"

%PDF-1.3
%���������
4 0 obj
<< /Length 5 0 R /Filter /FlateDecode >>
stream
...
------WebKitFormBoundaryOJOOGb7N43BxCRlv

问题在于请求文件是二进制文件(PDF),并且文字被“破坏”了。它报告的大小为57234字节,而实际文件大小(使用wget命令获取)为60248字节。
问题是:如何获取和发送二进制数据,而不被修改?
我尝试将response.text()替换为response.blob(),代码如下:
response = await fetch("https://wiki.epfl.ch/lapa-studio/documents/DTS/laser%20tutorial.pdf");
response.blob().then(function(content) { 
  console.log(content.size);
  console.log(content);
  formData = new FormData();
  formData.append("content", content);

  fetch("https://wiki.epfl.ch/test.php", {method: 'POST', body: formData});
})

现在我得到了这个日志,文件大小是正确的:

content.size:  60248
content:  Blob(60248) {size: 60248, type: "application/pdf"}

然而,前往开发者网络选项卡,选择“test.php”页面,导航到“请求有效载荷:”,它显示发送了一个空载荷:

------WebKitFormBoundaryYoibuD14Ah2cNGAd
Content-Disposition: form-data; name="content"; filename="blob"
Content-Type: application/pdf


------WebKitFormBoundaryYoibuD14Ah2cNGAd--

注意:我正在开发的网页不在wiki.epfl.ch上。 我提供这个例子是为了让用户尝试它(并避免“跨域资源共享”问题)。我的“test.php”页面是用php编写的,$_POST ['content']在使用response.text()时返回内容,但在使用response.blob()时返回空白。 因此,即使开发人员网络选项卡中的“请求有效载荷:”未显示二进制数据,该代码片段仍然无法正常工作。
注意:我正在开发的网页不在wiki.epfl.ch上。 我提供这个例子是为了让用户尝试它(并避免“跨域资源共享”问题)。我的“test.php”页面是用php编写的,$_POST ['content']在使用response.text()时返回内容,但在使用response.blob()时返回空白。 因此,即使开发人员网络选项卡中的“请求有效载荷:”未显示二进制数据,该代码片段仍然无法正常工作。

也许问题在于response.blob()返回了一个Blob对象。Body.blob()与问题有什么关联? - guest271314
我已经更新了问题,增加了更多细节。 - David Portabella
1
第二种方法(使用blob())确实会发送数据。我猜测Chrome开发工具不显示该值,因为它将是乱码的二进制数据,但如果您在Fiddler中观察请求,您可以看到数据正在发送。您有办法检查是否正确接收了数据吗?据我所知,https://wiki.epfl.ch/test实际上并没有显示任何文件接收的确认信息(它是否打算用于此目的?)。 - JLRishe
1
请参考:https://bugs.chromium.org/p/chromium/issues/detail?id=457484,这与您所面临的问题非常相关。 - TheChetan
1
我认为在Chrome中从未显示过二进制形式的块。 - dandavis
显示剩余2条评论
3个回答

27
如果您想发送二进制文件,请勿使用 .text() 方法,因为它会返回使用 UTF-8 解码的文件,这不是您想要的。相反,请使用 .blob() 方法,该方法不会尝试解码文件,并直接将其作为第二个 fetch()body 参数使用,因为它可以是 Blob 类型:
const response = await fetch("https://wiki.epfl.ch/lapa-studio/documents/DTS/laser%20tutorial.pdf");
const content = await response.blob();
console.log(content.size);
fetch("https://wiki.epfl.ch/test.php", { method: 'POST', body: content });

如需解析此上传格式,请参见此答案

如果您想将其作为multipart/form-data格式的附件上传,仍然可以使用FormData API,但这样做并不是将二进制数据发送到PHP脚本的必要步骤。只是为了完整性,以下是您应该使用的方式:

const response = await fetch("https://wiki.epfl.ch/lapa-studio/documents/DTS/laser%20tutorial.pdf");
const content = await response.blob();
console.log(content.size);
const formData = new FormData();
formData.append("content", content);
fetch("https://wiki.epfl.ch/test.php", { method: 'POST', body: formData });

这也是一个非常好的答案,尽管我没有指定需要使用multipart/form-data来传递其他值。 - David Portabella

8
尝试一下将Blob转换为DataURL字符串,这样您就可以发送二进制数据而不被修改。
response = await fetch("https://wiki.epfl.ch/lapa-studio/documents/DTS/laser%20tutorial.pdf");
response.blob().then(function (content) {

    let reader = new FileReader();

    reader.addEventListener("loadend", function () {

      formData = new FormData();
      formData.append("content", reader.result);
      fetch("https://wiki.epfl.ch/test.php", { method: 'POST', body: formData });
      reader.removeEventListener("loadend");

    });

    reader.readAsDataURL(content);

 });

6
使用 FileReader API 缓存 Blob 在这里完全是不必要的。formData.append() 直接将 content 作为 value 接受,而无需将其缓冲到 reader.result 中。 - Patrick Roberts
要了解如何在没有FileReader的情况下使用FormData,请参见我的答案的第二部分。 - Patrick Roberts
这个答案实际上并没有回答如何将二进制数据作为有效载荷发送;它将其包装在一个多部分表单部分中,并进行编码后发送。 - undefined

0

请尝试使用这段代码,希望能对您有所帮助。

 pdfcall(){
var xmlhttp = new XMLHttpRequest(),
method = 'GET',
 xmlhttp.open(method, url, true);
  xmlhttp.responseType = 'blob';
  xmlhttp.onload = (e: any) => {
   console.log(xmlhttp);
if (xmlhttp.status === 200) {
  let blob = new Blob([xmlhttp.response], {type: 'application/pdf'});
  this.pdfSrc = URL.createObjectURL(blob);
  }
};
 xmlhttp.onerror = (e: any) =>{
  console.error(e,'eerr')
    }
  xmlhttp.send();
}

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