Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/javascript/449.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Javascript 如何使用Firebase(multer、busboy)的云上express功能执行HTTP文件上载_Javascript_Firebase_Express_Google Cloud Functions - Fatal编程技术网

Javascript 如何使用Firebase(multer、busboy)的云上express功能执行HTTP文件上载

Javascript 如何使用Firebase(multer、busboy)的云上express功能执行HTTP文件上载,javascript,firebase,express,google-cloud-functions,Javascript,Firebase,Express,Google Cloud Functions,我试图上传一个文件到云函数,使用Express处理那里的请求,但我没有成功。我创建了一个在本地工作的版本: 服务器端js const express = require('express'); const cors = require('cors'); const fileUpload = require('express-fileupload'); const app = express(); app.use(fileUpload()); app.use(cors()); app.post

我试图上传一个文件到云函数,使用Express处理那里的请求,但我没有成功。我创建了一个在本地工作的版本:

服务器端js

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(', '));
});
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' },
    }
);
客户端js

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(', '));
});
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的云函数时,这个完全相同的代码似乎会中断。有人知道这里发生了什么吗

编辑 我还尝试过使用
multer
,它在本地运行得很好,但一旦上传到云函数,就得到了一个空数组(相同的客户端代码):


几天来,我一直在遭受同样的问题,事实证明firebase团队已经将多部分/表单数据的原始主体与他们的中间件一起放入req.body中。如果在使用multer处理请求之前尝试console.log(req.body.toString()),您将看到数据。当multer创建一个新的req.body对象覆盖生成的req时,数据就消失了,我们只能得到一个空的req.body。希望firebase团队能够很快纠正这一问题。

云功能设置中确实发生了突破性的变化,引发了这一问题。这与中间件的工作方式有关,中间件应用于所有用于提供HTTPS功能的Express应用程序(包括默认应用程序)。基本上,云函数将解析请求的主体并决定如何处理它,将主体的原始内容保留在
req.rawBody
中的缓冲区中。您可以使用它直接解析您的多部分内容,但不能使用中间件(如multer)

相反,您可以使用一个名为的模块直接处理原始正文内容。它可以接受
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。对于较大的文件,您应该将其上载到云存储,并使用存储触发器对其进行处理

还请记住,云函数添加的中间件的默认选择当前未通过
firebase Service
添加到本地模拟器。因此,在这种情况下,此示例将不起作用(rawBody将不可用)


团队正在更新文档,以便更清楚地了解HTTPS请求过程中与标准Express应用程序不同的情况。

要添加到官方云功能团队答案,您可以通过执行以下操作在本地模拟此行为(显然,将此中间件添加到比他们发布的busboy代码更高的位置)


我能够将Brian和Doug的响应结合起来。这是我的中间件,它最终模拟了multer中的req.文件,因此不会对代码的其余部分进行破坏性更改

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()
    }
})}

我修复了G.Rodriguez的响应中的一些错误。我为Busboy添加了'field'和'finish'事件,并在'finish'事件中执行next()。这是我的工作。如下所示:

    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()
        }
    })}
多亏了我为这个()构建了一个npm模块

它与google云功能配合使用,只需安装它(
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]
  ...
})

谢谢大家在这方面的帮助。我浪费了一整天的时间尝试各种可能的组合和所有这些不同的库…在用尽所有其他选择后才发现这一点

结合上述一些解决方案,在此处创建一个TypeScript和支持中间件的脚本:


请注意,除了在服务器上使用Busboy并解析
rawReq
之外,您可能还需要在Axios请求中添加以下配置:

{ headers: { 'content-type': `multipart/form-data; boundary=${formData._boundary}` }};
如果只指定
内容类型
而不指定边界,则服务器上会出现
边界未找到
错误。如果完全删除标题,Busboy将无法正确解析字段。
请参阅:

当我使用firebase功能部署我的应用程序时,我遇到了同样的问题。我使用multer将图像上传到amazon s3。我使用Cristóvão创建的上述npm解决了这个问题

  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' 
}

在这种情况下,在将数据保存到数据库之前,您可能需要位置url。

云函数在进一步传递前预处理
请求
对象。因此,原始的
multer
中间件不起作用。此外,使用
busboy
的级别太低,您需要处理上的一切您自己的,这并不理想。相反,您可以使用中间件处理云函数上的
多部分/表单数据

这是你能做的

  • 安装拨叉
  • npm安装--保存emadalam/multer#master
    
  • 使用
    startProcessing
    配置自定义处理云函数添加的
    req.rawBody
  • const express=require('express'))
    常量multer=require('multer')
    常量大小限制=10*1024*1024//10MB
    const app=express()
    const multipartFormDataParser=multer({
    存储:multer.memoryStorage(),
    //如果需要,增加尺寸限制
    限制:{fieldSize:SIZE\u LIMIT},
    //支持firebase云功能
    //多部分表单数据请求对象由云函数预处理
    //目前,“multer”库本机不支持这种行为
    //因此,通过添加'startProcessing'来维护自定义fork以启用此功能`
    // https://github.com/emadalam/multer
    启动处理(请求,服务生){
    所需原料体?汇流排末端(所需原料体):所需管道(汇流排)
    },
    })
    app.post('/some_route',multipartFormDataParser.any(),函数(req,res,next){
    //req.files是上传文件的数组
    //请求机构将公司
    
    // 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);