2017-11-11 16 views
5

ファイルをクラウド機能にアップロードしようとしていますが、Expressを使用してリクエストを処理していますが、Expressを使用してFirebase上でExpressファイルを使用してHTTPファイルをアップロードする方法

サーバーサイド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(', ')); 
}); 

クライアント側のjs

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に展開する際の、改行ようだ:私はローカルで動作するバージョンを作成しました。ファイルは未定義です。誰が何が起こっているのか分かりませんか?

私はこれが私の空の配列(同じクライアント側コード)だ、ローカルでもうまく働いたmulterを使用しての外出を、持っていたが、一度クラウド機能にアップロード 編集:私は持っている

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

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

私は 'express-fileupload'について知らないけど、' multer'モジュールを使ってファイルのアップロードをうまく受け取りました。 –

+0

実例がありますか?私は 'multer'と' express-form-data'を試しましたが、何らかの理由でそれらのどれかで成功しなかったのです。 – Eindbaas

+1

私はmulterを使って何かをローカルに設定しようとしましたが、すぐに機能しました(記事の編集を参照)。しかし、やはりFirebaseにデプロイすると結果は得られません。私は何が間違っているのか分かりません。 – Eindbaas

答えて

3

を数日間同じ問題を抱えていたFirebaseチームは、ミドルウェアを使ってmultipart/form-dataの元の本体をreq.bodyに入れていたことが判明しました。 multerでリクエストを処理する前にconsole.log(req.body.toString())を実行すると、データが表示されます。 multerが新しいreq.bodyオブジェクトを作成し、結果のreqを上書きすると、データはなくなり、取得できるのは空のreq.bodyだけです。うまくいけば、firebaseチームはすぐにこれを修正することができます。

+0

詳細は私の答えを見てください。 –

13

実際この問題を引き起こしたクラウド機能のセットアップには大きな変化がありました。これは、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 Storageにアップロードし、ストレージトリガーで処理してください。

クラウド機能によって追加されたデフォルトのミドルウェアは、現時点ではfirebase serve経由でローカルエミュレータに追加されていないことに注意してください。そのため、このサンプルは動作しません(rawBodyは使用できません)。

チームは、標準のExpressアプリケーションとは異なるHTTPS要求の間に何が起こったのかを明確にするために、ドキュメントを更新する作業を進めています。

+3

私はあなたのソリューションを試しましたが、私はバスボイの 'フィールド'コールバックを使用してボディコンテンツを収集しようとしています。しかし、 'field'イベントは決して起動されず、' finish'も決して起動されず、要求はタイムアウトに終わります。 – nicholas

3

公式クラウド機能チームの答えに追加するには、次の手順を実行して、ローカルにこの動作をエミュレートすることができますが(彼らは明らかに、掲載busboyコードよりも高く、このミドルウェアを追加)

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

私が結合することができましたブライアンとダグの両方の反応。ここで私のミドルウェアはmulterでreq.filesを模倣しているので、残りのコードには大きな変更はありません。

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

この回答に感謝します!心から感謝する。 – wcandillon

+1

このメソッドは、マルチパートフォーム – rendom

+0

Yupによって渡されたすべての文字列属性をスローし、例に修正を追加しました。基本的にはbusboy.on( 'field')を追加し、それをOG req.bodyに保存します。フィードバックをお寄せいただきありがとうございます! @rendom –

3

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

このメソッドは、マルチパートフォームによって渡されたすべての文字列属性をスローします – rendom

1

私はそれがGoogleのクラウド機能と連携し、この(github

のためのNPMモジュールを構築しましたanswers aboveのおかげで、それをインストールします(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] 
    ... 
}) 
+0

このリンクは質問に答えるかもしれませんが、スタック交換。com/q/8231)に回答の重要な部分を含め、参照用のリンクを提供してください。リンクされたページが変更された場合、リンクのみの回答は無効になる可能性があります。 – iBug

+0

このリンクは質問に答えるかもしれませんが、ここでは回答の重要な部分を含めて参考にしてください。リンクされたページが変更された場合、リンクのみの回答は無効になります。 - [レビューから](レビュー/低品質の投稿/ 18741514) –

+0

リンクを参照として保持するコード例を追加しました –

関連する問題