How to process image data from post request in node without express/multer?

Viewed 586

I'm trying to submit a POST request with an image to an azure functions endpoint. The endpoint is going to upload the image to azure blob storage. Everything is hooked up, but I'm not sure how to process the form encoded image data to be uploaded to the blob storage account. I'd like to avoid adding express and am looking for alternatives to multer. I've tried consulting the documentation, but it also uses express/multer.

Below is what I have so far for the azure function. It's currently able to upload it to the azure storage account, but the data is not correct since I cannot display when I try to download it or view it.

export const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
    const storageConnectionString = config.storageConnectionString;

    // Create the BlobServiceClient object which will be used to create a container client
    const blobServiceClient = BlobServiceClient.fromConnectionString(storageConnectionString);

    // Create a unique name for the container
    const containerName = config.storageContainerName;

    // Get a reference to a container
    const containerClient = blobServiceClient.getContainerClient(containerName);

    const blobName = context.bindingData.imageName;

    // Get a block blob client
    const blockBlobClient = containerClient.getBlockBlobClient(blobName);

    if (req.method === "POST")
    {
      const content = req.body;
      const blobHeaders: BlobHTTPHeaders = {
        blobContentType: "image/jpeg",
      }
      const options: BlockBlobUploadOptions = {blobHTTPHeaders: blobHeaders};
      const uploadBlobResponse = await blockBlobClient.upload(content, content.length, options);
    }

};

Here is my request from postman removing the token and the path to image.

curl --location --request POST 'http://localhost:7071/api/images/jaimeLannister.jpg' \
--header 'Cookie: BL_SiteLanguageID=1; __RequestVerificationToken=TOKEN' \
--form 'image=@/C:/jaimeLannister.jpg'

Any help would be appreciated!

2 Answers

As wvilSnobu said, you could use parse-multipart from npm which seems to be a more lightweight alternative to formidable.

Refer to the code as below:

module.exports = async function (context, req) {
    context.log('JavaScript HTTP trigger function processed a request.');

    const streamifier = require('streamifier');
    const multipart = require('parse-multipart');
    const azureStorage = require('azure-storage');

    var bodyBuffer = Buffer.from(req.body);
    var boundary = multipart.getBoundary(req.headers['content-type']);
    var parts = multipart.Parse(bodyBuffer, boundary);

    var filedata = parts[0].data;         // Image buffer data
    var filename = parts[0].filename; 

    var a=azureStorage.createBlobService('xxxx','xxxxxxxxxxxx');
    try {
        var b= a.createBlockBlobFromStream('container', filename, streamifier.createReadStream(new Buffer (filedata)), filedata.length,(err, result)=>{
            if(err){
                console.log("Image upload failed", err);
            }
        });
    } catch (error) {
        console.error(error);
    }
};

I send image using Postman:

enter image description here

And after uploading image to portal, the blob content is application/octet-stream and I can download it and view it successfully.

enter image description here

For more details, you could refer to this article.

Update:

The azure-storage library and "new Buffer" are both out of date. Refer to the code using @azure/storage-blob.

import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { config } from "../cosmos/config";
import { BlobServiceClient, BlockBlobUploadStreamOptions } from "@azure/storage-blob";
const streamifier = require("streamifier");
const multipart = require("parse-multipart");

export const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
    const storageConnectionString = config.storageConnectionString;

    // Create the BlobServiceClient object which will be used to create a container client
    const blobServiceClient = BlobServiceClient.fromConnectionString(storageConnectionString);

    // Create a unique name for the container
    const containerName = config.storageContainerName;

    // Get a reference to a container
    const containerClient = blobServiceClient.getContainerClient(containerName);

    // Gets the file name from the url, could be a bug if you want the filename from the file contents
    const blobName = context.bindingData.imageName;

    // Get a block blob client
    const blockBlobClient = containerClient.getBlockBlobClient(blobName);

    if (req.method === "POST")
    {
      const bodyBuffer = Buffer.from(req.body);
      const boundary = multipart.getBoundary(req.headers['content-type']);
      const parts = multipart.Parse(bodyBuffer, boundary);
      const filedata = parts[0].data;
      const filename = parts[0].filename;
      const options: BlockBlobUploadStreamOptions = {};
      try
      {
        const result = await blockBlobClient.uploadStream(streamifier.createReadStream(Buffer.from(filedata)), filedata.length);
        context.res = { status: 200 };
        return;
      }
      catch(err)
      {
        console.log(err);
      }
    }

    context.res = { status: 302, headers: { "location": blockBlobClient.url }, body: null};

};

Thanks to @Joey Cai and @evilSnobu's answers I was able to get it to work using parse-multipart and streamifier.

import { AzureFunction, Context, HttpRequest } from "@azure/functions";
import { config } from "../cosmos/config";
import { BlobServiceClient, BlockBlobUploadStreamOptions } from "@azure/storage-blob";
const streamifier = require("streamifier");
const multipart = require("parse-multipart");

export const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {
    const storageConnectionString = config.storageConnectionString;

    // Create the BlobServiceClient object which will be used to create a container client
    const blobServiceClient = BlobServiceClient.fromConnectionString(storageConnectionString);

    // Create a unique name for the container
    const containerName = config.storageContainerName;

    // Get a reference to a container
    const containerClient = blobServiceClient.getContainerClient(containerName);

    // Gets the file name from the url, could be a bug if you want the filename from the file contents
    const blobName = context.bindingData.imageName;

    // Get a block blob client
    const blockBlobClient = containerClient.getBlockBlobClient(blobName);

    if (req.method === "POST")
    {
      const bodyBuffer = Buffer.from(req.body);
      const boundary = multipart.getBoundary(req.headers['content-type']);
      const parts = multipart.Parse(bodyBuffer, boundary);
      const filedata = parts[0].data;
      const filename = parts[0].filename;
      const options: BlockBlobUploadStreamOptions = {};
      try
      {
        const result = await blockBlobClient.uploadStream(streamifier.createReadStream(Buffer.from(filedata)), filedata.length);
        context.res = { status: 200 };
        return;
      }
      catch(err)
      {
        console.log(err);
      }
    }

    context.res = { status: 302, headers: { "location": blockBlobClient.url }, body: null};

};

Related