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
클라우드 기능에 대해 설명합니다.
당신이 할 수 있는 일은 다음과 같습니다.
- 포크 설치
npm install --save emadalam/multer#master
- 사용하다
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.body
POST
와 함께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를 사용하고 구문 분석하는 것 외에도rawReq
Axios 요청에 다음 구성을 추가해야 할 수도 있습니다.
{ headers: { 'content-type': `multipart/form-data; boundary=${formData._boundary}` }};
다만지는경우만 content-type
이 받는 경계가 .Boundary not found
서버에 오류가 있습니다.헤더를 모두 제거하면 Busboy는 필드를 제대로 구문 분석하지 못합니다.참조: Firebase Cloud Functions 및 Busboy가 필드 또는 파일을 구문 분석하지 않음
파이어베이스 기능을 사용하여 앱을 배포할 때도 같은 문제가 발생합니다.저는 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
'programing' 카테고리의 다른 글
SQL Server 2008 DateTimeOffset을 DateTime으로 변환하는 방법 (0) | 2023.07.16 |
---|---|
값을 전환하는 방법? (0) | 2023.07.16 |
단추를 클릭할 때 대화 상자가 닫히지 않도록 방지하는 방법 (0) | 2023.07.06 |
ID가 Next 페이지 매개 변수에 없을 때 ID별 API 리소스를 가져오는 방법 (0) | 2023.07.06 |
SQL Server 2008에서 SQL Profiler는 어디에 있습니까? (0) | 2023.07.06 |