NodeJS / Restify:如何在API中接收文件上传?

时间:2017-08-29 21:59:12

标签: javascript node.js rest reactjs react-native

我试图从移动应用程序上传图像文件(以本机编写,现在在iOS上运行)。

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

  1. 我没有得到import plotly import numpy as np import random import sqlite3 def graph_test_q(): cur = conn.cursor() cur.execute("SELECT q FROM pump_data_test WHERE pump_id = 1229") rows = cur.fetchall() return rows def graph_test_h(): cur = conn.cursor() cur.execute("SELECT h FROM pump_data_test WHERE pump_id = 1229") rows = cur.fetchall() return rows plotly.offline.init_notebook_mode() # Create some mockup data conn = sqlite3.connect(':memory:') c = conn.cursor() c.execute("CREATE TABLE pump_data_test (h, q, pump_id)") for i in range(10): c.execute("INSERT INTO pump_data_test VALUES ('{}','{}',1229)".format(i, i + random.random())) conn.commit() trace1 = plotly.graph_objs.Scatter( name='works', x=np.array(graph_test_q())[:,0], y=np.array(graph_test_h())[:,0], ) trace2 = plotly.graph_objs.Scatter( name='does not work', x=np.array(graph_test_q()), y=np.array(graph_test_h()), ) data = [trace1, trace2] fig = dict(data=data) plotly.offline.iplot(fig) ,因为它总是一个空对象,虽然标题已正确提交。
  2. 我想通过req.body将收到的文件写入我的数据库(GridFS),但我不知道在哪里放置该代码。
  3. API

    gridfs-stream

    我试图找到错误,但我没有找到它,所以这里是我在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)
    })
    

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

    为什么{ } 为空?

    React Native file upload

    这就是我将文件发送到API的方式。我还向您展示了一些变量的内容:

    body

    照片

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

    我认为'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中的正文发送)格式错误。为什么数组中有数组?

3 个答案:

答案 0 :(得分:1)

以下示例在React Native部分使用react-native-fetch-blob,在Express和Formidable使用Nodejs在服务器端解析表单。

在确定用户是否上传了照片或视频后,我们首先上传文件:

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

并使用控制器功能:

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

请务必阅读代码中包含的注释。 Datapath 是使用react-native-image-picker后创建的媒体路径(不是base64字符串)。您可以使用react-native-progress来显示上传进度。

查看react-native-fetch-blob的multipartform-data部分以获取进一步的参考:https://github.com/wkh237/react-native-fetch-blob#multipartform-data-example-post-form-data-with-file-and-data

答案 1 :(得分:0)

您可以使用co-busboy节点模块编写中间件,这是koa的示例:

首先,您需要按co-busboynpm安装yarn

npm i co-busboy -Syarn add co-busboy

&#13;
&#13;
// 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())
&#13;
&#13;
&#13;

参考:

co-busboy

答案 2 :(得分:-3)

你需要对图像进行base64编码,然后将主体作为json发送(标题Content-Type设置为application / json)