Search code examples
typescriptaxiosazure-functionsform-data

Typescript, Azure Function, Axios - Posting FormData from File


I have an Azure Function that receives File uploads to the endpoint and am trying to repost that file to another endpoint microservice which handles the file using Axios. I am receiving an error to the effect of source.on is not a function when trying to add formData to the ocrServicePromise.

Any ideas on what's wrong?

Error is: [10/26/2020 2:06:27 PM] Executed 'Functions.ReceiveInboundFax' (Failed, Id=0a7bb949-9ff8-4762-8c21-ba2177ba96e2) [10/26/2020 2:06:27 PM] System.Private.CoreLib: Exception while executing function: Functions.ReceiveInboundFax. System.Private.CoreLib: Result: Failure [10/26/2020 2:06:27 PM] Exception: TypeError: source.on is not a function [10/26/2020 2:06:27 PM] Stack: TypeError: source.on is not a function

import { AzureFunction, Context, HttpRequest } from "@azure/functions"
import * as multipart from 'parse-multipart';
import axios from 'axios';
import { OcrStatusResponse } from "./classes/OcrStatusResponse";
import { ApolloClient } from 'apollo-client';
import { InMemoryCache } from 'apollo-cache-inmemory';
import gql from 'graphql-tag';
import fetch from 'node-fetch';
import { createHttpLink } from "apollo-link-http";

const httpTrigger: AzureFunction = async function (context: Context, req: HttpRequest): Promise<void> {

const body = req.rawBody;
// Retrieve the boundary id
const boundary = multipart.getBoundary(req.headers["content-type"]);
let logEntries = [];

if (boundary) {
    const files: File[] = multipart.Parse(Buffer.from(body), boundary);

    if (files && files.length > 0) {
                      
        var allUpdated = await Promise.all(files.map(async (file) => {

            //CallOcrService
            var ocrresult = await CallOcrService(context, file);                

            //Write to Hasura
            logEntries.push(await LogOcrRequest(context, ocrresult, file.name));

        }));
    }   
    
}    

context.res = {
    status: 200,            
    body: logEntries
}  

};

async function CallOcrService(context: Context,file : File) : Promise<OcrStatusResponse>{

const FormDataFile = require('form-data');
const formData = new FormDataFile();
formData.append("file", file)

const ocrServicePromise = await axios.post(process.env["Ocr_Service_Upload_Endpoint"],  formData, {
        headers: formData.getHeaders()
    })
    .then((response) => {
        const ocrSeriviceResponse = new OcrStatusResponse(response.data);

        ocrSeriviceResponse.status = response.data.state;
        
        return ocrSeriviceResponse;
    }, (error) => {
        context.log.error(error);

        throw new Error(error);
    }
);

return ocrServicePromise;
}

async function LogOcrRequest(context: Context, ocrResponse: OcrStatusResponse, filename: String){  

const headers = {
    "content-type": "application/json",
    "x-hasura-admin-secret": process.env["ATTACHMENT_API_ADMIN_SECRET"]
}

const client = new ApolloClient({
    link: createHttpLink({
    uri:
        process.env["ATTACHMENT_API_ENDPOINT"],
        headers:headers,        
        fetch:fetch
    }),
    cache: new InMemoryCache()
});

const ocr_deadline = dateAdd(new Date(), "minute", 20);

const mutationInsertObj = {
    date_time_received: new Date(),
    file_name_received: filename,
    ocr_job_id: ocrResponse.jobid,
    date_time_ocr_response_deadline : ocr_deadline,
    ocr_result_statuscheck_endpoint : ocrResponse.status_url,
    ocr_result : ocrResponse.status,
    ocr_document_endpoint: `${process.env["Ocr_Service_Upload_Document_Retrival_Endpoint"]}/${ocrResponse.jobid}/${ocrResponse.jobid}.pdf`
}

if(!ocrResponse.success){
    context.log.error(`Response was empty or did not receive a status url for ${filename}`);
    mutationInsertObj.ocr_result = "ERROR";
}

const insertMutation = gql `
    mutation InsertOcrRequest($ocrResponse: inbound_fax_insert_input!)  {
        insert_inbound_fax_one(
            object: $ocrResponse
        ) {
            inboundfaxid
            ocr_job_id
            ocr_result_statuscheck_endpoint
            ocr_result
            date_time_ocr_response_deadline
            ocr_document_endpoint
        }
    }
`;

const {errors,data} = await client.mutate({
    mutation: insertMutation,
    errorPolicy: 'all',
    variables: mutationInsertObj
});

if(errors!== undefined){
    const errorList = [];
    errorList.push(errors);
    return errorList;
}

return data;

}

function dateAdd(date, interval, units) {
if(!(date instanceof Date))
  return undefined;

var ret = new Date(date); 
var checkRollover = function() { if(ret.getDate() != date.getDate()) ret.setDate(0);};

switch(String(interval).toLowerCase()) {
  case 'year'   :  ret.setFullYear(ret.getFullYear() + units); checkRollover();  break;
  case 'quarter':  ret.setMonth(ret.getMonth() + 3*units); checkRollover();  break;
  case 'month'  :  ret.setMonth(ret.getMonth() + units); checkRollover();  break;
  case 'week'   :  ret.setDate(ret.getDate() + 7*units);  break;
  case 'day'    :  ret.setDate(ret.getDate() + units);  break;
  case 'hour'   :  ret.setTime(ret.getTime() + units*3600000);  break;
  case 'minute' :  ret.setTime(ret.getTime() + units*60000);  break;
  case 'second' :  ret.setTime(ret.getTime() + units*1000);  break;
  default       :  ret = undefined;  break;
}

return ret;
}



export default httpTrigger;

Solution

  • If you use the package parse-multipart to parse multipart/form-data, it just returns the value like { filename: 'A.txt', type: 'text/plain', data: <Buffer 41 41 41 41 42 42 42 42> } and it will not return File Object. For more details, please refer to here
    enter image description here

    Besides, the package form-data just supports string, buffer, and file stream. For more details, please refer to here.

    So please update as follow

    1. Define an interface to receive value from parse-multipart
    export interface FileInfo {
      filename: string;
      type: string;
      data: Buffer;
    }
    
    1. Update HTTP trigger code
    import * as multipart from "parse-multipart";
    import axios from "axios";
    import { FileInfo } from "../entities/FileInfo";
    import * as FormDataFile from "form-data";
    const httpTrigger: AzureFunction = async function (
      context: Context,
      req: HttpRequest
    ): Promise<void> {
      context.log("HTTP trigger function processed a request.");
      const body = req.rawBody;
      // Retrieve the boundary id
      const boundary = multipart.getBoundary(req.headers["content-type"]);
      let logEntries = [];
    
      if (boundary) {
        const files: FileInfo[] = multipart.Parse(Buffer.from(body), boundary);
    
        if (files && files.length > 0) {
          var allUpdated = await Promise.all(
            files.map(async (file) => {
              await CallOcrService(context, file);
            })
          );
        }
      }
    
      context.res = {
        status: 200,
        body: "OK",
      };
    };
    
    async function CallOcrService(
      context: Context,
      file: FileInfo
    ): Promise<String> {
      const formData = new FormDataFile();
      formData.append("file", Buffer.from(file.data), {
        filename: file.filename,
        contentType: file.type,
      });
    
     // call you api
    }