programing

Firebase용 Cloud Functions(multer, busboy)에서 Express를 사용하여 HTTP 파일 업로드를 수행하는 방법

lastcode 2023. 7. 6. 22:17
반응형

Firebase용 Cloud Functions(multer, busboy)에서 Express를 사용하여 HTTP 파일 업로드를 수행하는 방법

Express를 사용하여 클라우드 기능에 파일을 업로드하고 요청을 처리하려고 하지만 성공하지 못하고 있습니다.로컬에서 작동하는 버전을 만들었습니다.

서버측 j

const express = require('express');
const cors = require('cors');
const fileUpload = require('express-fileupload');

const app = express();
app.use(fileUpload());
app.use(cors());

app.post('/upload', (req, res) => {
    res.send('files: ' + Object.keys(req.files).join(', '));
});

고객측 j.

const formData = new FormData();
Array.from(this.$refs.fileSelect.files).forEach((file, index) => {
    formData.append('sample' + index, file, 'sample');
});

axios.post(
    url,
    formData, 
    {
        headers: { 'Content-Type': 'multipart/form-data' },
    }
);

req.files가 정의되지 않은 Cloud Functions에 배포할 때 동일한 코드가 손상되는 것 같습니다.여기서 무슨 일이 일어나고 있는지 아는 사람?

EDIT 또한 사용해 보았습니다.multer로컬에서 잘 작동했지만 Cloud Functions에 업로드되면 빈 배열(동일한 클라이언트 사이드 코드)이 생성되었습니다.

const app = express();
const upload = multer();
app.use(cors());

app.post('/upload', upload.any(), (req, res) => {
    res.send(JSON.stringify(req.files));
});

이 문제를 촉발한 클라우드 기능 설정에 변화가 있었습니다.이는 HTTPS 기능을 제공하는 데 사용되는 모든 Express 앱(기본 앱 포함)에 적용되는 미들웨어 작동 방식과 관련이 있습니다. 분석하고 합니다.req.rawBody이를 사용하여 멀티파트 콘텐츠를 직접 구문 분석할 수 있지만 미들웨어(예: 멀터)로는 할 수 없습니다.

대신 busboy라는 모듈을 사용하여 직접 생체 콘텐츠를 처리할 수 있습니다.다음을 수락할 수 있습니다.rawBody버퍼에 있는 파일을 찾아서 다시 전화하겠습니다.업로드된 모든 콘텐츠를 반복하여 파일로 저장한 다음 삭제하는 샘플 코드가 있습니다.당신은 분명히 더 유용한 것을 하고 싶을 것입니다.

const path = require('path');
const os = require('os');
const fs = require('fs');
const Busboy = require('busboy');

exports.upload = functions.https.onRequest((req, res) => {
    if (req.method === 'POST') {
        const busboy = new Busboy({ headers: req.headers });
        // This object will accumulate all the uploaded files, keyed by their name
        const uploads = {}

        // This callback will be invoked for each file uploaded
        busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
            console.log(`File [${fieldname}] filename: ${filename}, encoding: ${encoding}, mimetype: ${mimetype}`);
            // Note that os.tmpdir() is an in-memory file system, so should only 
            // be used for files small enough to fit in memory.
            const filepath = path.join(os.tmpdir(), fieldname);
            uploads[fieldname] = { file: filepath }
            console.log(`Saving '${fieldname}' to ${filepath}`);
            file.pipe(fs.createWriteStream(filepath));
        });

        // This callback will be invoked after all uploaded files are saved.
        busboy.on('finish', () => {
            for (const name in uploads) {
                const upload = uploads[name];
                const file = upload.file;
                res.write(`${file}\n`);
                fs.unlinkSync(file);
            }
            res.end();
        });

        // The raw bytes of the upload will be in req.rawBody.  Send it to busboy, and get
        // a callback when it's finished.
        busboy.end(req.rawBody);
    } else {
        // Client error - only support POST
        res.status(405).end();
    }
})

임시 공간에 저장된 파일은 메모리를 차지하므로 크기는 총 10MB로 제한됩니다.파일 크기가 큰 경우 클라우드 저장소에 업로드하고 저장 트리거를 사용하여 처리해야 합니다.

항목은 "Cloud Functions"를 통해 .firebase serve따라서 이 샘플은 이 경우 작동하지 않습니다(rawBody는 사용할 수 없습니다).

이 팀은 표준 Express 앱과 다른 HTTPS 요청 시 발생하는 모든 상황을 보다 명확하게 파악하기 위해 문서를 업데이트하는 작업을 수행하고 있습니다.

위의 답변 덕분에 저는 이 (github)을 위한 pm 모듈을 만들었습니다.

구글 클라우드 기능과 연동되니 설치만 하면 됩니다 (npm install --save express-multipart-file-parser다음과 같이 사용합니다.

const fileMiddleware = require('express-multipart-file-parser')

...
app.use(fileMiddleware)
...

app.post('/file', (req, res) => {
  const {
    fieldname,
    filename,
    encoding,
    mimetype,
    buffer,
  } = req.files[0]
  ...
})

저는 브라이언과 더그의 반응을 모두 결합할 수 있었습니다.여기 제 미들웨어가 있습니다. 리퀘스트 파일을 모방하여 코드의 나머지 부분을 변경할 필요가 없습니다.

module.exports = (path, app) => {
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
app.use((req, res, next) => {
    if(req.rawBody === undefined && req.method === 'POST' && req.headers['content-type'].startsWith('multipart/form-data')){
        getRawBody(req, {
            length: req.headers['content-length'],
            limit: '10mb',
            encoding: contentType.parse(req).parameters.charset
        }, function(err, string){
            if (err) return next(err)
            req.rawBody = string
            next()
        })
    } else {
        next()
    }
})

app.use((req, res, next) => {
    if (req.method === 'POST' && req.headers['content-type'].startsWith('multipart/form-data')) {
        const busboy = new Busboy({ headers: req.headers })
        let fileBuffer = new Buffer('')
        req.files = {
            file: []
        }

        busboy.on('field', (fieldname, value) => {
            req.body[fieldname] = value
        })

        busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
            file.on('data', (data) => {
                fileBuffer = Buffer.concat([fileBuffer, data])
            })

            file.on('end', () => {
                const file_object = {
                    fieldname,
                    'originalname': filename,
                    encoding,
                    mimetype,
                    buffer: fileBuffer
                }

                req.files.file.push(file_object)
            })
        })

        busboy.on('finish', () => {
            next()
        })


        busboy.end(req.rawBody)
        req.pipe(busboy)
    } else {
        next()
    }
})}

저는 며칠 동안 같은 문제를 겪고 있었는데, 소방본부 팀이 미들웨어로 멀티파트/폼 데이터의 원시 본체를 requ.body에 삽입한 것으로 밝혀졌습니다.multer로 요청을 처리하기 전에 console.log(req.body.toString())을 시도하면 데이터가 표시됩니다.multer가 결과 req를 재정의하는 새 req.body 개체를 생성하면 데이터가 사라지고 빈 req.body만 얻을 수 있습니다.소방대가 곧 이 문제를 해결할 수 있기를 바랍니다.

은 클우드기사처다니리됩전능은라-process를 사전 합니다.request더 이상 전달하기 전에 이의를 제기합니다.원본과 같이multer미들웨어가 작동하지 않습니다. 게가다, 사는것을 사용합니다.busboy너무 낮은 수준이고 이상적이지 않은 모든 것을 스스로 처리해야 합니다.대신 처리를 위해 포크 버전의 멀터 미들웨어를 사용할 수 있습니다.multipart/form-data클라우드 기능에 대해 설명합니다.

당신이 할 수 있는 일은 다음과 같습니다.

  1. 포크 설치
npm install --save emadalam/multer#master
  1. 사용하다startProcessing의 지정 구성입니다.req.rawBody클라우드 기능이 추가되었습니다.
const express = require('express')
const multer = require('multer')

const SIZE_LIMIT = 10 * 1024 * 1024 // 10MB
const app = express()

const multipartFormDataParser = multer({
  storage: multer.memoryStorage(),
  // increase size limit if needed
  limits: {fieldSize: SIZE_LIMIT},
  // support firebase cloud functions
  // the multipart form-data request object is pre-processed by the cloud functions
  // currently the `multer` library doesn't natively support this behaviour
  // as such, a custom fork is maintained to enable this by adding `startProcessing`
  // https://github.com/emadalam/multer
  startProcessing(req, busboy) {
    req.rawBody ? busboy.end(req.rawBody) : req.pipe(busboy)
  },
})

app.post('/some_route', multipartFormDataParser.any(), function (req, res, next) {
  // req.files is array of uploaded files
  // req.body will contain the text fields
})

Cloud Function 팀의 공식 답변에 추가하려면 다음 작업을 수행하여 로컬에서 이 동작을 에뮬레이트할 수 있습니다(이 미들웨어는 게시된 버스보이 코드보다 더 높게 추가해야 합니다).

const getRawBody = require('raw-body');
const contentType = require('content-type');

app.use(function(req, res, next){
    if(req.rawBody === undefined && req.method === 'POST' && req.headers['content-type'] !== undefined && req.headers['content-type'].startsWith('multipart/form-data')){
        getRawBody(req, {
            length: req.headers['content-length'],
            limit: '10mb',
            encoding: contentType.parse(req).parameters.charset
        }, function(err, string){
            if (err) return next(err);
            req.rawBody = string;
            next();
        });
    }
    else{
        next();
    }
});

오늘 이 문제가 발생했습니다. Google 클라우드에서 파일을 처리하는 방법에 대한 자세한 내용은 여기를 참조하십시오(기본적으로 Multer가 필요하지 않음).

파일을 추출하는 데 사용하는 미들웨어가 있습니다.이렇게 하면 모든 파일이 계속 켜져 있습니다.request.files의 기타 request.bodyPOST와 함께multipart/form-data할 수 다른 입니다.다른 미들웨어가 처리할 수 있도록 다른 모든 것을 동일하게 유지합니다.

// multiparts.js
const { createWriteStream } = require('fs')
const { tmpdir } = require('os')
const { join } = require('path')
const BusBoy = require('busboy')

exports.extractFiles = async(req, res, next) => {
  const multipart = req.method === 'POST' && req.headers['content-type'].startsWith('multipart/form-data')
  if (!multipart) return next()
  //
  const busboy = new BusBoy({ headers: req.headers })
  const incomingFields = {}
  const incomingFiles = {}
  const writes = []
  // Process fields
  busboy.on('field', (name, value) => {
    try {
      // This will keep a field created like so form.append('product', JSON.stringify(product)) intact
      incomingFields[name] = JSON.parse(value)
    } catch (e) {
      // Numbers will still be strings here (i.e 1 will be '1')
      incomingFields[name] = value
    }
  })
  // Process files
  busboy.on('file', (field, file, filename, encoding, contentType) => {
    // Doing this to not have to deal with duplicate file names
    // (i.e. TIMESTAMP-originalName. Hmm what are the odds that I'll still have dups?)
    const path = join(tmpdir(), `${(new Date()).toISOString()}-${filename}`)
    // NOTE: Multiple files could have same fieldname (which is y I'm using arrays here)
    incomingFiles[field] = incomingFiles[field] || []
    incomingFiles[field].push({ path, encoding, contentType })
    //
    const writeStream = createWriteStream(path)
    //
    writes.push(new Promise((resolve, reject) => {
      file.on('end', () => { writeStream.end() })
      writeStream.on('finish', resolve)
      writeStream.on('error', reject)
    }))
    //
    file.pipe(writeStream)
  })
  //
  busboy.on('finish', async () => {
    await Promise.all(writes)
    req.files = incomingFiles
    req.body = incomingFields
    next()
  })
  busboy.end(req.rawBody)
}

이제 여러분의 기능에서 이것이 여러분이 처음으로 사용하는 미들웨어인지 확인하십시오.

// index.js
const { onRequest } = require('firebase-functions').https
const bodyParser = require('body-parser')
const express = require('express')
const cors = require('cors')
const app = express()
// First middleware I'm adding
const { extractFiles } = require('./multiparts')
app.use(extractFiles)
app.use(bodyParser.urlencoded({ extended: true }))
app.use(bodyParser.json())
app.use(cors({ origin: true }))

app.use((req) => console.log(req.originalUrl))

exports.MyFunction = onRequest(app);

G. 로드리게스의 반응을 몇 가지 버그를 수정했습니다.저는 Busboy를 위한 'field'와 'finish' 이벤트를 추가하고 'finish' 이벤트에서 다음()을 수행합니다.이것은 나를 위한 일입니다.다음과 같습니다.

    module.exports = (path, app) => {
    app.use(bodyParser.json())
    app.use(bodyParser.urlencoded({ extended: true }))
    app.use((req, res, next) => {
        if(req.rawBody === undefined && req.method === 'POST' && req.headers['content-type'].startsWith('multipart/form-data')){
            getRawBody(req, {
                length: req.headers['content-length'],
                limit: '10mb',
                encoding: contentType.parse(req).parameters.charset
            }, function(err, string){
                if (err) return next(err)
                req.rawBody = string
                next()
            })
        } else {
            next()
        }
    })

    app.use((req, res, next) => {
        if (req.method === 'POST' && req.headers['content-type'].startsWith('multipart/form-data')) {
            const busboy = new Busboy({ headers: req.headers })
            let fileBuffer = new Buffer('')
            req.files = {
                file: []
            }

            busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
                file.on('data', (data) => {
                    fileBuffer = Buffer.concat([fileBuffer, data])
                })

                file.on('end', () => {
                    const file_object = {
                        fieldname,
                        'originalname': filename,
                        encoding,
                        mimetype,
                        buffer: fileBuffer
                    }

                    req.files.file.push(file_object)
                })
            })

            busboy.on('field', function(fieldname, val, fieldnameTruncated, valTruncated) {
              console.log('Field [' + fieldname + ']: value: ' + inspect(val));
            });

            busboy.on('finish', function() {
              next()
            });

            busboy.end(req.rawBody)
            req.pipe(busboy);
        } else {
            next()
        }
    })}

이 스레드에 대한 모든 사람의 도움에 감사드립니다.가능한 모든 조합과 도서관을 시도하느라 하루 종일 허비했어요다른 모든 옵션을 다 사용한 후에야 이를 발견할 수 있습니다.

위의 솔루션 중 일부를 결합하여 TypeScript 및 미들웨어 지원 스크립트를 생성했습니다.

https://gist.github.com/jasonbyrne/8dcd15701f686a4703a72f13e3f800c0

요청에서 업로드된 파일 하나만 가져오려면 다음을 사용합니다.busboy파일을 읽을 수 있는 스트림으로 가져오려면:

const express = require('express')
const Busboy = require('busboy')

express().post('/', (req, res) => {
  const busboy = new Busboy({ headers: req.headers })

  busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
    // Do something with `file`, e.g. pipe it to an output stream.
    // file.pipe(fs.createWriteStream('upload.pdf')
  })

  // The original input was moved to `req.rawBody`
  busboy.write(req.rawBody)
})

서버에서 Busboy를 사용하고 구문 분석하는 것 외에도rawReqAxios 요청에 다음 구성을 추가해야 할 수도 있습니다.

{ headers: { 'content-type': `multipart/form-data; boundary=${formData._boundary}` }};

다만지는경우만 content-type이 받는 경계가 .Boundary not found서버에 오류가 있습니다.헤더를 모두 제거하면 Busboy는 필드를 제대로 구문 분석하지 못합니다.참조: Firebase Cloud FunctionsBusboy가 필드 또는 파일을 구문 분석하지 않음

파이어베이스 기능을 사용하여 앱을 배포할 때도 같은 문제가 발생합니다.저는 multer를 사용하여 이미지를 amazon s3에 업로드하고 있었습니다.Cristóvang이 작성한 위의 npm https://stackoverflow.com/a/48648805/5213790 을 사용하여 이 문제를 해결합니다.

  const { mimetype, buffer, } = req.files[0]

  let s3bucket = new aws.S3({
     accessKeyId: functions.config().aws.access_key,
     secretAccessKey: functions.config().aws.secret_key,
  });

  const config = {
     Bucket: functions.config().aws.bucket_name,
     ContentType: mimetype,
     ACL: 'public-read',
     Key: Date.now().toString(),
     Body: buffer,    
   }

   s3bucket.upload(config, (err, data) => {
     if(err) console.log(err)

     req.file = data;
     next()
  })

단일 파일 이미지 업로드용입니다.다음 미들웨어는 s3에서 반환된 객체를 가질 것입니다.

{ 
  ETag: '"cacd6d406f891e216f9946911a69aac5"',
  Location:'https://react-significant.s3.us-west1.amazonaws.com/posts/1567282665593',
  key: 'posts/1567282665593',
  Key: 'posts/1567282665593',
  Bucket: 'react-significant' 
}

이 경우 데이터를 DB에 저장하기 전에 위치 URL이 필요할 수 있습니다.

저는 더그스의 답변을 시도해 보았지만, 마무리가 발사되지 않았기 때문에 코드를 조금 수정하여 제게 맞는 것을 얻었습니다.

// It's very crucial that the file name matches the name attribute in your html
app.post('/', (req, res) => {
  const busboy = new Busboy({ headers: req.headers })
  // This object will accumulate all the uploaded files, keyed by their name
  const uploads = {}

  // This callback will be invoked for each file uploaded
  busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
    console.log(`File [${fieldname}] filename: ${filename}, encoding: ${encoding}, mimetype: ${mimetype}`)
    // Note that os.tmpdir() is an in-memory file system, so should only
    // be used for files small enough to fit in memory.
    const filepath = path.join(os.tmpdir(), filename)
    uploads[fieldname] = { file: filepath }
    console.log(`Saving '${fieldname}' to ${filepath}`)
    const stream = fs.createWriteStream(filepath)
    stream.on('open', () => file.pipe(stream))
  })

  // This callback will be invoked after all uploaded files are saved.
  busboy.on('finish', () => {
    console.log('look im firing!')
// Do whatever you want here
    res.end()
  })

  // The raw bytes of the upload will be in req.rawBody.  Send it to busboy, and get
  // a callback when it's finished.
  busboy.end(req.rawBody)
})

다음은 타자기용 제 버전입니다.

import { FileInfo, } from 'busboy';
import Busboy from 'busboy';


app.post('/images', (req, resp, next) => {
  const busboy = Busboy({ headers: req.headers });
  const allFiles: FileInfo[] = [];

  busboy.on('file', (fieldname: string, file: any, fileInfo: FileInfo) => {
    const { filename, encoding, mimeType } = fileInfo;
    console.log(`Fiild ${fieldname}, File: ${file}, filename: ${filename}, encoding: ${encoding}, mimetype: ${mimeType}`);
    allFiles.push(fileInfo);
    file.on('data', (data: Uint8Array) => {
      console.log(`File got ${data.length} bytes`);
    })
  });

  busboy.on('finish', () => {
    resp.send(allFiles);
  });

  busboy.on('error', () => {
    resp.status(400);
  });

  busboy.end((req as any).rawBody);

});

app은 express app 또는 express 라우터일 수 있습니다.

언급URL : https://stackoverflow.com/questions/47242340/how-to-perform-an-http-file-upload-using-express-on-cloud-functions-for-firebase

반응형