Search code examples
phpfetch-apifs

Attempting to upload a file from local to a server via php


I'm attempting to upload a file via node from my local system to a php based remote server.

My initial attempt:

  const readStream = fs.createReadStream(filePath);
  
  fetch(API_ENDPOINT, {
    method: 'POST',
    body: readStream
  })
  .then(function(res) {
    console.log(res);
    return "here";
    // return res.json();
  }).then(function(json) {
    console.log(json);
  })

This returns a 200 that it hit the server but on the php side I'm checking to see if it's reading and this file write appears empty.

<?php
file_put_contents('fileExistsNew', $_FILES);
file_put_contents('fileExistsPost', $_POST);

?>

I try using formData and that also doesn't work for me it gives me an error that it's not a blob.

Failed to execute 'append' on 'FormData': parameter 2 is not of type 'Blob'


 const stats = fs.statSync(filePath);
  const fileSizeInBytes = stats.size;
  const fileStream = fs.createReadStream(filePath);
  formData.append('file', fileStream, { knownLength: fileSizeInBytes });

  const options = {
    method: 'POST',
    body: formData
  }

  fetch(API_ENDPOINT, {...options})
  .then(res => {
    if(res.ok) return res;
    throw res;
  });

How can I accomplish this?


Solution

  • Streaming Files

    PHP populates $_FILES when it receives a multi-part encoded request. Your first attempt isn't sending one of those. It is sending the raw file data (and hasn't included the content-type header).

    So let's start by tweaking that:

    import fs from 'fs';
    import fetch from 'node-fetch';
    
    const API_ENDPOINT = 'http://localhost:5000/';
    const filePath = './sample.png';
    
    const readStream = fs.createReadStream(filePath);
    
    fetch(API_ENDPOINT, {
        method: 'POST',
        body: readStream,
        headers: {
            'Content-Type': 'image/png',
        },
    })
        .then(res => res.json())
        .then(data => {
            console.log(data);
        });
    

    Then, on the PHP side, the data will be presented through standard input, so you can read it with PHP's file handling files.

    Here's an example which copies the file data to a file on disk and then reports some statistics about it.

    <?php
        $filename = "/tmp/outfile.png";
    
        $uploaded_file = fopen("php://input", "r");
        $out_file = fopen($filename, "w");
        while ($data = fread($uploaded_file, 1024))
            fwrite($out_file, $data);
        fclose($uploaded_file);
        fclose($out_file);
    
        $stats = stat($filename);
    
        header("Content-Type: application/json";
        echo json_encode($stats);
    ?>
    

    Note that this completely lacks any form of error handling (it doesn't even check the content-type header I set in the previous section).

    Using Form Data

    Form data objects can be used to generate a multipart request which PHP will use to populate $_FILES.

    it gives me an error that it's not a blob

    I don't know what library you are using to provide FormData to Node.js, but formdata-polyfill expects the stream to be returned from a method on the object you pass to append:

    import fs from 'fs';
    import fetch from 'node-fetch';
    import { FormData } from 'formdata-polyfill/esm.min.js';
    
    const API_ENDPOINT = 'http://localhost:5000/multipart.php';
    const filePath = './sample.png';
    
    const stats = fs.statSync(filePath);
    const fileSizeInBytes = stats.size;
    const body = new FormData();
    body.append('file', {
        size: fileSizeInBytes,
        name: 'example.png',
        stream() {
            return fs.createReadStream(filePath);
        },
        [Symbol.toStringTag]: 'File',
    });
    
    fetch(API_ENDPOINT, {
        method: 'POST',
        body,
    })
        .then(res => res.text())
        .then(data => {
            console.log(data);
        });