NodeJS / Restify:如何在API中接收file upload?

我试图从移动应用程序上载图像文件(这是写在原生态,现在在iOS上运行)。

该文件发送到我的REST API,如下所示。 我遇到了两个问题:

  1. 我没有得到req.body ,因为它总是一个空的对象,虽然标题提交正确。
  2. 我想通过gridfs-stream把接收到的文件写到我的数据库(GridFS)中,但是我不明白该把代码放在哪里。

API

 const restify = require('restify') const winston = require('winston') const bunyanWinston = require('bunyan-winston-adapter') const mongoose = require('mongoose') const Grid = require('gridfs-stream') const config = require('../config') // Configure mongoose to work with javascript promises mongoose.Promise = global.Promise // Setting up server const server = restify.createServer({ name: config.name, version: config.version, log: bunyanWinston.createAdapter(log) }) server.use(restify.plugins.multipartBodyParser()) server.listen(config.port, () => { mongoose.connection.on('open', (err) => { server.post('/upload', (req, res, next) => { console.log(req.headers) // <- returns headers as expected /* Problem 1 */ console.log(req.body) // <- is empty object (unexpected) res.send(200, { message: 'successful upload' }) res.end() }) }) global.db = mongoose.connect(config.db.uri, { useMongoClient: true }) /* Problem 2: The recieved file should be stored to DB via `gridfs-stream` */ // I think this is the wrong place for this line... var gfs = Grid(global.db, mongoose.mongo) }) 

我试图find错误,但我没有find它,所以这里是我的API中获得的数据:

 { host: 'localhost:3000', 'content-type': 'multipart/form-data; boundary=pUqK6oKvY65OfhaQ3h01xWg0j4ajlanAA_e3MXVSna4F8kbg-zT0V3-PeJQm1QZ2ymcmUM', 'user-agent': 'User/1 CFNetwork/808.2.16 Darwin/15.6.0', connection: 'keep-alive', accept: '*/*', 'accept-language': 'en-us', 'accept-encoding': 'gzip, deflate', 'content-length': '315196' } 

身体

 { } 

为什么body是空的?


React本机file upload

这就是我将文件发送到API的方式。 我也给你看一些variables的内容:

 async function upload (photo) { console.log('photo', photo); // OUTPUT SHOWN BELOW if (photo.uri) { // Create the form data object var data = new FormData() data.append('picture', { uri: photo.uri, name: 'selfie.jpg', type: 'image/jpg' }) // Create the config object for the POST const config = { method: 'POST', headers: { 'Accept': 'application/json' }, body: data } console.log('config', config); // OUTPUT SHOWN BELOW fetchProgress('http://localhost:3000/upload', { method: 'post', body: data }, (progressEvent) => { const progress = progressEvent.loaded / progressEvent.total console.log(progress) }).then((res) => console.log(res), (err) => console.log(err)) } } const fetchProgress = (url, opts = {}, onProgress) => { console.log(url, opts) return new Promise((resolve, reject) => { var xhr = new XMLHttpRequest() xhr.open(opts.method || 'get', url) for (var k in opts.headers || {}) { xhr.setRequestHeader(k, opts.headers[k]) } xhr.onload = e => resolve(e.target) xhr.onerror = reject if (xhr.upload && onProgress) { xhr.upload.onprogress = onProgress // event.loaded / event.total * 100 ; //event.lengthComputable } xhr.send(opts.body) }) } 

照片

 { fileSize: 314945, origURL: 'assets-library://asset/asset.JPG?id=106E99A1-4F6A-45A2-B320-B0AD4A8E8473&ext=JPG', longitude: -122.80317833333334, fileName: 'IMG_0001.JPG', height: 2848, width: 4288, latitude: 38.0374445, timestamp: '2011-03-13T00:17:25Z', isVertical: false, uri: 'file:///Users/User/Library/Developer/CoreSimulator/Devices/D3FEFFA8-7446-42AB-BC7E-B6EB88DDA840/data/Containers/Data/Application/17CE8C0A-B781-4E56-9347-857E74055119/Documents/images/69C2F27F-9EEE-4611-853E-FC7FF6E5C373.jpg' } 

configuration

 'http://localhost:3000/upload', { method: 'post', body: { _parts: [ [ 'picture', { uri: 'file:///Users/User/Library/Developer/CoreSimulator/Devices/D3FEFFA8-7446-42AB-BC7E-B6EB88DDA840/data/Containers/Data/Application/17CE8C0A-B781-4E56-9347-857E74055119/Documents/images/69C2F27F-9EEE-4611-853E-FC7FF6E5C373.jpg', name: 'selfie.jpg', type: 'image/jpg' } ] ] } } 

我认为data (应在config作为主体发送)格式不正确。 为什么数组中有一个数组?

下面的例子使用了React Native部分的react-native-fetch-blob ,而使用Express和Formidable的 Nodejs在服务器端parsing表单。

确定用户是否上传了照片或video之后,首先上传文件:

 RNFetchBlob.fetch( 'POST', Constants.UPLOAD_URL + '/upload', { 'Content-Type': 'multipart/form-data' }, [ { name: this.state.photoURL ? 'image' : 'video', filename: 'avatar-foo.png', type: 'image/foo', data: RNFetchBlob.wrap(dataPath) }, // elements without property `filename` will be sent as plain text { name: 'email', data: this.props.email }, { name: 'title', data: this.state.text } ] ) // listen to upload progress event .uploadProgress((written, total) => { console.log('uploaded', written / total); this.setState({ uploadProgress: written / total }); }) // listen to download progress event .progress((received, total) => { console.log('progress', received / total); }) .then(res => { console.log(res.data); // we have the response of the server this.props.navigation.goBack(); }) .catch(err => { console.log(err); }); }; 

同样,接收文件并相应加载数据:

 exports.upload = (req, res) => { var form = new formidable.IncomingForm(); let data = { email: '', title: '', photoURL: '', videoURL: '', }; // specify that we want to allow the user to upload multiple files in a single request form.multiples = true; // store all uploads in the /uploads directory form.uploadDir = path.join(__dirname, '../../uploads'); form.on('file', (field, file) => { let suffix = field === 'image' ? '.png' : '.mp4'; let timestamp = new Date().getTime().toString(); fs.rename(file.path, path.join(form.uploadDir, timestamp + suffix)); //save file with timestamp. data[field === 'image' ? 'photoURL' : 'videoURL'] = timestamp + suffix; }); form.on('field', (name, value) => { data[name] = value; }); form.on('error', err => { console.log('An error has occured: \n ' + err); }); form.on('end', () => { // now we have a data object with fields updated. }); form.parse(req); }; 

并使用控制器function:

 let route = express.Router(); // other controller functions... route.post('/upload', uploadController.upload); app.use(route); 

确保你阅读了代码中的注释。 Datapath是使用react-native-image-picker后创build的媒体path(不是base64string)。 您可以使用react-native-progress来显示上传进度。

查看react-native-fetch-blob的multipartform-data部分以供进一步参考: https : //github.com/wkh237/react-native-fetch-blob#multipartform-data-example-post-form-data-with-文件和数据

你可以使用co-busboy节点模块写一个中间件,这是koa一个例子:

首先,你需要用npmyarn来安装co-busboy

npm i co-busboy -Syarn add co-busboy

 // upload.js var parse = require('co-busboy') var fs = require('fs') var path = require('path') var upload = function * (next) { var parts = parse(this, { autoFields: true }) while(var part = yield parts) { part.pipe(fs.createReadStream(path.join('uploadPath', part.filename))) } yield next } module.exports = () => upload // app.js var upload = require('upload') app.use(upload()) 

你需要对图像进行base64编码,然后以json的forms发送主体(头部Content-Type设置为application / json)