Typescript、Azure Function、Axios - 从文件发布 FormData
Typescript, Azure Function, Axios - Posting FormData from File
我有一个 Azure 函数,它接收文件上传到端点,并试图将该文件重新发布到另一个使用 Axios 处理文件的端点微服务。当我尝试将 formData 添加到 ocrServicePromise 时,我收到一个错误 source.on is not a function.
有什么问题吗?
错误是:
[10/26/2020 2:06:27 PM] 已执行 'Functions.ReceiveInboundFax'(失败,Id=0a7bb949-9ff8-4762-8c21-ba2177ba96e2)
[10/26/2020 2:06:27 下午] System.Private.CoreLib:执行函数时出现异常:Functions.ReceiveInboundFax。 System.Private.CoreLib:结果:失败
[10/26/2020 2:06:27 PM] 异常:类型错误:source.on 不是函数
[2020 年 10 月 26 日 2:06:27 下午]堆栈:类型错误:source.on 不是函数
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;
如果你使用包parse-multipart
解析multipart/form-data,它只是return像{ filename: 'A.txt', type: 'text/plain', data: <Buffer 41 41 41 41 42 42 42 42> }
那样的值,它不会returnFile
对象。详情请参考here
此外,包form-data
只支持字符串、缓冲区和文件流。详情请参考here.
所以请更新如下
- 定义一个接口以从
parse-multipart
接收值
export interface FileInfo {
filename: string;
type: string;
data: Buffer;
}
- 更新 HTTP 触发代码
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
}
我有一个 Azure 函数,它接收文件上传到端点,并试图将该文件重新发布到另一个使用 Axios 处理文件的端点微服务。当我尝试将 formData 添加到 ocrServicePromise 时,我收到一个错误 source.on is not a function.
有什么问题吗?
错误是: [10/26/2020 2:06:27 PM] 已执行 'Functions.ReceiveInboundFax'(失败,Id=0a7bb949-9ff8-4762-8c21-ba2177ba96e2) [10/26/2020 2:06:27 下午] System.Private.CoreLib:执行函数时出现异常:Functions.ReceiveInboundFax。 System.Private.CoreLib:结果:失败 [10/26/2020 2:06:27 PM] 异常:类型错误:source.on 不是函数 [2020 年 10 月 26 日 2:06:27 下午]堆栈:类型错误:source.on 不是函数
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;
如果你使用包parse-multipart
解析multipart/form-data,它只是return像{ filename: 'A.txt', type: 'text/plain', data: <Buffer 41 41 41 41 42 42 42 42> }
那样的值,它不会returnFile
对象。详情请参考here
此外,包form-data
只支持字符串、缓冲区和文件流。详情请参考here.
所以请更新如下
- 定义一个接口以从
parse-multipart
接收值
export interface FileInfo {
filename: string;
type: string;
data: Buffer;
}
- 更新 HTTP 触发代码
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
}