12

I have read tutorials on how to upload the image to a bucket and do post processing via background function. But my requirement is to upload the image, do post processing and return the result immediately via HTTP function. Please let me know if this is the correct way to do or not as I didn't get much material online on this. Here is how I went about it:

HTTP Cloud function:

exports.uploadImage = function (req, res){
 var file = req.body.file;
 uploadSomewhere(file)(); < post-processing code which is working fine >

UI form:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7/jquery.js"></script> 
<script src="http://malsup.github.com/jquery.form.js"></script>
<form id="myForm" action="<cloud_function_url>/uploadImage" method="post"> 
  <label for="file">Choose file to upload</label>
  <input type="file" id="file" name="file" multiple>
  <input type="submit" value="Submit" /> 
</form>

<script> 
 $(document).ready(function() { 
  $('#myForm').ajaxForm(function() { 
 }); 
}); 
</script>

The problem is, after I deployed the function, when I upload the image from the folder where function is present, image gets uploaded. But if I upload the image from any other location, it returns me error:

Error: Error in uploading image file .... - Error: ENOENT: no such file or directory, open '....'

Please let me know what am I doing wrong or if you need more information.

Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
anmolagrawal
  • 203
  • 1
  • 2
  • 12
  • Possible duplicate of [How to perform an HTTP file upload using express on Cloud Functions for Firebase](https://stackoverflow.com/questions/47242340/how-to-perform-an-http-file-upload-using-express-on-cloud-functions-for-firebase) – Doug Stevenson Dec 23 '17 at 00:59

2 Answers2

3

Here is a fully working function file

    const vision = require('@google-cloud/vision')({
        projectId: "pid",
        keyfileName: 'keyfile.json'
    });


    // The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
    const functions = require('firebase-functions');

    // The Firebase Admin SDK to access the Firebase Realtime Database.
    const admin = require('firebase-admin');
    admin.initializeApp();
    // Create the Firebase reference to store our image data
    const db = admin.database();
    const { Storage } = require('@google-cloud/storage');
    // Your Google Cloud Platform project ID                                        
    const projectId = 'pid';

    // Creates a client                                                             
    const storage = new Storage({
        projectId: projectId,
    });
    /**
     * Parses a 'multipart/form-data' upload request
     *
     * @param {Object} req Cloud Function request context.
     * @param {Object} res Cloud Function response context.
     */
    const path = require('path');
    const os = require('os');
    const fs = require('fs');

    // Node.js doesn't have a built-in multipart/form-data parsing library.
    // Instead, we can use the 'busboy' library from NPM to parse these requests.
    const Busboy = require('busboy');

    exports.upload = functions.https.onRequest((req, res) => {
        if (req.method !== 'POST') {
            // Return a "method not allowed" error
            return res.status(405).end();
        }
        const busboy = new Busboy({ headers: req.headers });
        const tmpdir = os.tmpdir();

        // This object will accumulate all the fields, keyed by their name
        const fields = {};

        // This object will accumulate all the uploaded files, keyed by their name.
        const uploads = {};

        // This code will process each non-file field in the form.
        busboy.on('field', (fieldname, val) => {
            // TODO(developer): Process submitted field values here
            console.log(`Processed field ${fieldname}: ${val}.`);
            fields[fieldname] = val;
        });

        const fileWrites = [];

        // This code will process each file uploaded.
        busboy.on('file', (fieldname, file, filename) => {
            // Note: os.tmpdir() points to an in-memory file system on GCF
            // Thus, any files in it must fit in the instance's memory.
            console.log(`Processed file ${filename}`);

            const filepath = path.join(tmpdir, filename);
            uploads[fieldname] = filepath;

            const writeStream = fs.createWriteStream(filepath);
            file.pipe(writeStream);

            // File was processed by Busboy; wait for it to be written to disk.
            const promise = new Promise((resolve, reject) => {
                file.on('end', () => {
                    writeStream.end();
                });
                writeStream.on('finish', resolve);
                writeStream.on('error', reject);
            });
            fileWrites.push(promise);
        });

        // Triggered once all uploaded files are processed by Busboy.
        // We still need to wait for the disk writes (saves) to complete.
        busboy.on('finish', () => {
            Promise.all(fileWrites).then(() => {
                // TODO(developer): Process saved files here
                for (const name in uploads) {
                    const file = uploads[name];
                    async function upload2bucket() {
                        var bucketName = 'bname'
                        fileRes = await storage.bucket(bucketName).upload(file);
                        fs.unlinkSync(file);
                        console.log('fileRes',fileRes)
                        console.log(`Finish: Processed file ${file}`);
                        res.send(fileRes);
                    }
                    upload2bucket()
                }
            });
        });

        busboy.end(req.rawBody);

    });
Elia Weiss
  • 8,324
  • 13
  • 70
  • 110
2

There is sample code for uploading files directly to Cloud Functions here: https://cloud.google.com/functions/docs/writing/http#multipart_data_and_file_uploads, but be aware there is a file size limit when using this approach (10MB). If you have files larger than that, I recommend you use the Cloud Storage approach and use something like the Firebase Realtime Database to manage the state on the client.

So, you generate the signed upload URL using a Cloud Function, then use the RTDB to track progress for the client. Return the URL and a reference to the location in the DB where you're going to track progress. The client would watch this location for updates. The client then uploads the file to Cloud Storage and the second function is triggered. After post processing it updates the RTDB which pushes the update down to the client. From the client's perspective this is all synchronous, but it's actually a series of async operations with state coalescing in the database.

Here's the sample code for inline updates taken from the docs if you're OK with file sizes < 10MB:

/**
 * Parses a 'multipart/form-data' upload request
 *
 * @param {Object} req Cloud Function request context.
 * @param {Object} res Cloud Function response context.
 */
const path = require('path');
const os = require('os');
const fs = require('fs');

// Node.js doesn't have a built-in multipart/form-data parsing library.
// Instead, we can use the 'busboy' library from NPM to parse these requests.
const Busboy = require('busboy');

exports.uploadFile = (req, res) => {
  if (req.method === 'POST') {
    const busboy = new Busboy({ headers: req.headers });
    const tmpdir = os.tmpdir();

    // This object will accumulate all the fields, keyed by their name
    const fields = {};

    // This object will accumulate all the uploaded files, keyed by their name.
    const uploads = {};

    // This code will process each non-file field in the form.
    busboy.on('field', (fieldname, val) => {
      // TODO(developer): Process submitted field values here
      console.log(`Processed field ${fieldname}: ${val}.`);
      fields[fieldname] = val;
    });

    let fileWrites = [];

    // This code will process each file uploaded.
    busboy.on('file', (fieldname, file, filename) => {
      // Note: os.tmpdir() points to an in-memory file system on GCF
      // Thus, any files in it must fit in the instance's memory.
      console.log(`Processed file ${filename}`);
      const filepath = path.join(tmpdir, filename);
      uploads[fieldname] = filepath;

      const writeStream = fs.createWriteStream(filepath);
      file.pipe(writeStream);

      // File was processed by Busboy; wait for it to be written to disk.
      const promise = new Promise((resolve, reject) => {
        file.on('end', () => {
          writeStream.end();
        });
        writeStream.on('finish', resolve);
        writeStream.on('error', reject);
      });
      fileWrites.push(promise);
    });

    // Triggered once all uploaded files are processed by Busboy.
    // We still need to wait for the disk writes (saves) to complete.
    busboy.on('finish', () => {
      Promise.all(fileWrites)
        .then(() => {
          // TODO(developer): Process saved files here
          for (const name in uploads) {
            const file = uploads[name];
            fs.unlinkSync(file);
          }
          res.send();
        });
    });

    busboy.end(req.rawBody);
  } else {
    // Return a "method not allowed" error
    res.status(405).end();
  }
};
Jason Polites
  • 5,571
  • 3
  • 25
  • 24