How can I send a binary data (blob) using fetch and FormData?

David Portabella picture David Portabella · Jan 25, 2018 · Viewed 12.2k times · Source

The following code works as expected. Open the page "https://wiki.epfl.ch/" on Google Chrome, and execute this code on the Developer console. Note: the page "https://wiki.epfl.ch/test.php" does not exists and so it fails to load, but that's not the issue.

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});
})

It logs:

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

Go to the Developer Network tab, choose the 'test.php' page, navigate to "Requested payload:" and you can see this content:

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

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

The issue is that the request file is a binary file (PDF), and the text gets "mangled". It reports a size of 57234 bytes, when the actual file size (as fetched with a wget command) is 60248 bytes.

The question is: How to get and send the binary data, without being modified?


I tried replacing response.text() by response.blob(), as follows:

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});
})

Now I get this log, with the correct file size:

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

However, going to the Developer Network tab, choose the 'test.php' page, navigate to "Requested payload:", it shows that it sends an empty payload:

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


------WebKitFormBoundaryYoibuD14Ah2cNGAd--

Note: The webpage I am developing is not at wiki.epfl.ch. I provide this example so that users can try it (and avoid the "Cross-Origin Resource Sharing" problem). My "test.php" page is in php and $_POST['content'] returns the content when using response.text(), but it returns empty when using response.blob(). So, even if it is the case that the Developer Network tab "Requested payload:" does not show binary data, this snipped is still not working.


The question is: How to get and send the binary data, without being modified?

Answer

Patrick Roberts picture Patrick Roberts · Feb 1, 2018

If you want to send a binary file, do not use the .text() method, as that returns the file decoded using UTF-8, which is not what you want. Instead, use the .blob() method, which does not attempt to decode the file, and use it directly as the body parameter of the second fetch() since it's allowed to be a 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 });

For parsing this upload format see this answer.

If you want to upload it as part of a multipart/form-data formatted attachment, you can still use the FormData API, but doing so isn't necessary to send the binary data to your PHP script. Just for completeness, here's how you'd do that instead:

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 });