HttpTrigger를 사용하여 NodeJS Azure 함수에서 multipart/form-data 처리

소개



JAM-stack 호스팅 플랫폼(예: NetlifyCloudflare Workers)에 대한 Microsoft의 답변은 Azure Static Web Apps 입니다.

모든 플랫폼이 유사한 구성 요소를 제공하지만 세부 사항에서 큰 차이가 있습니다. 먼저 유사한 성분에 대해:
  • 정적 콘텐츠 호스팅, CDN을 통해 전 세계적으로 사용 가능
  • API 및 웹훅과 같은 HTTP 엔드포인트를 구축할 수 있도록 컴퓨팅 - NodeJS 또는 기타 언어로 작성됨
  • CI/CD 지원
  • 도메인 구성, 인증/권한 부여, 환경 설정 등을 위한 관리

  • 내가 우연히 발견한 세부 사항의 큰 차이점 중 하나는 양식 필드와 파일을 모두 지원하기 위해 multipart/form-data으로 양식 게시를 처리하는 것이었습니다. 제 경우에는 필드 세트와 이미지 세트를 처리해야 했습니다.

    열반에 이르는 긴 구불구불한 길



    Netlify는 간단하고 무료이기 때문에 내 솔루션을 위한 플랫폼으로 처음 시작했습니다. 많은 시행착오와 (항상) 오류 후에 Netlify 함수에 대한 (손상된) 구현이 바이너리 데이터 게시를 지원하지 않는다는 것을 분명히 하는 discussion thread을 발견했습니다. 기본 AWS Lambda에는 지원이 있지만 이 지원은 Netlify에 대해 활성화되어 있지 않습니다.Netlify 기능의 또 다른 매우 성가신 점은 VSCode로 Netlify 기능을 디버그하는 방법에 대한 정보가 없다는 것입니다. 주제에 대한 this discussion thread과 같은 엉터리와 this discussion thread에서 올바른 방향의 일부 포인터만 볼 수 있습니다. 이것은 문서의 일부여야 합니다! console.log()을 사용한 디버깅은 정말... 90년대입니다.

    다음 단계는 정말 성숙한 Azure Functions를 통해 컴퓨팅을 지원하는 Azure Static Web Apps였습니다. 우선 VSCode는 Azure Static Web Apps extension for Visual Studio Code을 통해 Azure Static Web Apps와 기본 기능을 생성, 관리 및 디버깅하는 데 큰 도움이 됩니다.

    그러나 가장 중요한 것은 이진 데이터로 POST 메시지를 처리할 수 있다는 것입니다!
    multipart/form-data 구문 분석은 까다로운 작업이기 때문에 다른 사람들이 성공했다고 보고한 많은 npm 패키지를 시도했습니다. 자주 사용되는 npm 패키지는 블로그 게시물 Multipart/form-data processing via HttpTrigger using NodeJS Azure functionsthis discussion thread에 설명된 대로 parse-multipart입니다. 파일 필드만 있을 때만 작동했지만 텍스트와 파일 필드가 혼합되어 있으면 완전히 충돌했습니다. multipartymulter과 같은 다른 패키지와 작동하지 않는 많은 다른 패키지를 조사한 후 Parsing post data 3 different ways in Node.js without third-party libraries 코드를 시도했지만 작동하지 못했습니다.

    니르바나



    긴 이야기를 짧게 줄이자면 마침내 작동하게 되었습니다. 들어오는 HTML 양식 데이터를 구문 분석하기 위한 A node.js 모듈로 스스로를 설명하는 저수준 패키지 busboy (이름에 무엇이 들어 있는지!)을 우연히 발견했습니다. 문서에서 Express 을 사용하여 http package 또는 req.pipe(busboy) 기반 상황에 적용할 수 있는 데이터 가져오기의 스트리밍 모델에 연결하는 방법만 설명하기 때문에 작동하지 못했습니다. Azure 함수에서 요청 본문은 이미 완전히 로드되었습니다.

    패키지의 소스 코드를 파헤친 후 문서화되지 않은 호출 busboy.write(request.body, function() {});을 사용하여 필요한 접근 방식을 정확히 사용하는 some tests을 찾았습니다. 여기서 Azure 함수에서 이미 사용할 수 있는 본문 데이터를 전달할 수 있습니다. 이것은 나를 다음 코드로 데려왔다. 우리가 찾은 모든 필드에 대한 정보를 기록하고, 테스트로 서버가 바이너리 파일을 올바르게 처리할 수 있는지 확인하기 위해 임시 폴더의 디스크에 파일을 작성합니다.Netlify 함수에서 이 동일한 코드를 실행하면 모든 것이 작동하는 것처럼 보이지만 불구가 되는 파일을 얻게 됩니다!

    HttpTrigger를 사용하여 NodeJS Azure 함수에서 multipart/form-data 처리:

    import { AzureFunction, Context, HttpRequest } from "@azure/functions"
    import * as Busboy from 'busboy';
    import * as fs from 'fs';
    
    function getTime() {
        return (new Date()).toISOString().substr(11, 8);
    }
    
    function log(s) {
        console.log(`${getTime()}: ${s}`);
    }
    
    const httpTrigger: AzureFunction = async function (context: Context, request: HttpRequest): Promise<void> {
        var busboy = new Busboy({ headers: request.headers });
        busboy.on('file', function(fieldname, file, filename, encoding, mimetype) {
            log(`File [${fieldname}]: filename: '${filename}', encoding: ${encoding}, mimetype: ${mimetype}`);
            file.on('data', function(data) {
              log(`File [${fieldname}]: filename: '${filename}', got ${data.length} bytes`);
              fs.writeFileSync('c:\\temp\\' + filename, data);
            });
            file.on('end', function() {
              log(`File [${fieldname}]: filename: '${filename}', Finished`);
            });
        });
        busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated, encoding, mimetype) {
            log(`Field [${fieldname}]: value: ${val}`);
        });
        busboy.on('finish', function() {
            log('Done parsing form!');
    
            context.res = {
                status: 200, 
                body: { result: "done" }
            };
        });
        busboy.write(request.body, function() {});
    };
    
    export default httpTrigger;
    


    필드 및 이미지를 포함하는 샘플:

    <form action="http://localhost:7071/api/handlePostData" method="post" enctype="multipart/form-data">
      <label for="fname">First name:</label>
      <input type="text" id="fname" name="fname"><br><br>
      <label for="lname">Last name:</label>
      <input type="text" id="lname" name="lname"><br><br>
      <label for="selfie">Selfie:</label>
      <input type="file" accept="image/*" capture id="selfie" name="selfie"><br><br>
      <input type="submit" value="Submit">
    </form>
    


    구성 옵션과 처리 필드 및 파일에 대한 자세한 내용은 Busboy documentation을 확인하십시오.

    좋은 웹페이지 즐겨찾기