将多部分表单上传到另一台服务器

时间:2015-12-14 09:17:36

标签: javascript angularjs node.js express multipartform-data

我正在尝试在我的Node Express服务器上处理POST请求以处理多部分表单上传,在我的情况下,用户正在上传图像。

我想通过我的Express应用程序将上传管道传输到另一台服务器,该应用程序当前设置为使用body解析器,我也看到它不支持多部分bodes,而是建议使用其他一些库。

我见过multiparty,但我不确定如何在客户端应用程序中使用它。

在我的客户端代码中,我发布了一个FormData对象,如下所示:

function create(data, name) {
  var formData = new FormData();
  formData.append('file', data, name);
  return this.parentBase.one('photos').withHttpConfig({transformRequest: angular.identity}).customPOST(formData, undefined, undefined, {'Content-Type': undefined});
}

注意:我正在使用AngularJS的Restangular库,记录为here

因此,根据我对多方文档的理解,我必须处理表单上传事件,并在表单上传完成后再对其进行操作。

问题是,我希望我可以将上传直接传送到另一台服务器。事先我的客户端应用程序正在直接调用这个其他服务器,但我现在正试图通过Express路由一切,这是可能的,还是我必须使用像multiparty这样的东西?

请求文档提供了使用formData的示例,但我不确定这将如何与我见过的多方示例一起使用。例如,一旦使用mutliparty在Express中完成上传,我是否必须构造另一个formData对象然后再发出请求,或者我是否必须将每个部分传递给另一个服务器?

我很困惑,请有人帮我解决这个问题吗?

由于

修改

好的,我在@yarons评论之后看了一下multer,这似乎是我想要使用的那种东西,我试图用我的快速路由器设置如下所示:

routes.js

var express = require('express'),
  router = express.Router(),
  customers = require('./customers.controller.js'),
  multer = require('multer'),
  upload = multer();

router.post('/customers/:customerId/photos/', upload.single('file'), customers.createPhoto);

controller.js

module.exports.createPhoto = function(req, res) {
  console.log(req.file);
  var options = prepareCustomersAPIHeaders(req);
  options.formData = req.file;
  request(options).pipe(res);
};

在上面的控制器中注销req.file属性我看到了:

{ fieldname: 'file',
  originalname: '4da2e703044932e33b8ceec711c35582.jpg',
  encoding: '7bit',
  mimetype: 'image/png',
  buffer: <Buffer 89 50 4e 47 0d 0a 1a 0a 00 00 00 0d 49 48 44 52 00 00 00 fa 00
 00 00 fa 08 06 00 00 00 88 ec 5a 3d 00 00 20 00 49 44 41 54 78 5e ac bd f9 8f e
6 e9 7a ... >,
  size: 105868 }

我通过客户端代码使用以下内容发布的内容:

var formData = new FormData();
      formData.append('file', data, name);
      return this.parentBase.one('photos').withHttpConfig({transformRequest: angular.identity}).customPOST(formData, undefined, undefined, {'Content-Type': undefined});

我的尝试是否明智?只有它不起作用,我从服务器收到错误,我试图发帖。事先我把这个帖子请求直接发送到服务器它一切正常,所以我的Express \ Multer设置中一定有问题

编辑2

好的,所以在经过更多的搜索之后,我遇到了使用multiparty的this文章,我有经理在我的设置中工作如下:

var request = require('request'),
  multiparty = require('multiparty'),
  FormData = require('form-data');

module.exports.createPhoto = function(req, res) {
  //console.log(req.file);
  var options = prepareCustomersAPIHeaders(req),
    form = new multiparty.Form();
  options.headers['Transfer-Encoding'] = 'chunked';

  form.on('part', function(part){
    if(part.filename) {
      var form = new FormData(), r;
      form.append(part.name, part, {filename: part.filename, contentType: part['content-type']});


      r = request(options, function(err, response, body){
        res.status(response.statusCode).send(body);
      });
      r._form = form
    }
  });

  form.on('error', function(error){
    console.log(error);
  });

  form.parse(req);
};  

现在正按预期将文件上传到我的其他服务器,虽然此解决方案有效,但我不喜欢这行:

r._form = form

似乎是在请求对象中分配私有表单变量,而且我无法在多方页面上看到以这种方式记录的任何内容

有人可以对这个可能的解决方案发表任何意见吗?

1 个答案:

答案 0 :(得分:0)

我们使用以下内容:

客户端

//HTML
    <input type="file" ng-file-select uploader="info.uploadPath" />


//DIRECTIVES
  // It is attached to <input type="file" /> element
  .directive('ngFileSelect', function() {
    return {
      link: function($scope, $element) {
        $element.bind('change', function() {
          $scope.$emit('file:add', this.files ? this.files : this);
        });
      }
    };
  })

//OTHER
    var uploadPath = '/api/things/' + $stateParams.thingId + '/add_photo'

    var uploadInfo = {
              headers: {
                'Authorization': authToken
              },
              form: {
                title: scope.info.name
              }
            }


//SERVICE:
  $rootScope.$on('file:add', function(event, items) {
    this.addToQueue(items);
  }.bind(this));
  ...
  addToQueue: function(items) {
    var length = this.queue.length;
    angular.forEach(items.length ? items : [items], function(item) {
      var isValid = !this.filters.length ? true : !!this.filters.filter(function(filter) {
        return filter.apply(this, [item]);
      }, this).length;

      if (isValid) {
        item = new Item({
          url: this.url,
          alias: this.alias,
          removeAfterUpload: this.removeAfterUpload,
          uploader: this,
          file: item
        });

        this.queue.push(item);
      }
    }, this);

    this.uploadAll();
  },
  getNotUploadedItems: function() {
    return this.queue.filter(function(item) {
      return !item.isUploaded;
    });
  },

  /**
   * Upload a item from the queue
   * @param {Item|Number} value
   */
  uploadItem: function(value, uploadInfo) {
    if (this.isUploading) {
      return;
    }

    var index = angular.isObject(value) ? this.getIndexOfItem(value) : value;
    var item = this.queue[index];
    var transport = item.file._form ? '_iframeTransport' : '_xhrTransport';
    this.isUploading = true;
    this[transport](item, uploadInfo);
  },

  uploadAll: function(uploadInfo) {
    var item = this.getNotUploadedItems()[0];
    this._uploadNext = !!item;
    this._uploadNext && this.uploadItem(item, uploadInfo);
  },

  _xhrTransport: function(item, uploadInfo) {
    var xhr = new XMLHttpRequest();
    var form = new FormData();
    var that = this;

    form.append(item.alias, item.file);

    angular.forEach(uploadInfo.form, function(value, name) {
      form.append(name, value);
    });

    xhr.upload.addEventListener('progress', function(event) {
      var progress = event.lengthComputable ? event.loaded * 100 / event.total : 0;
      that._scope.$emit('in:progress', item, Math.round(progress));
    }, false);

    xhr.addEventListener('load', function() {
      xhr.status === 200 && that._scope.$emit('in:success', xhr, item);
      xhr.status !== 200 && that._scope.$emit('in:error', xhr, item);
      that._scope.$emit('in:complete', xhr, item);
    }, false);

    xhr.addEventListener('error', function() {
      that._scope.$emit('in:error', xhr, item);
      that._scope.$emit('in:complete', xhr, item);
    }, false);

    xhr.addEventListener('abort', function() {
      that._scope.$emit('in:complete', xhr, item);
    }, false);

    this._scope.$emit('beforeupload', item);

    xhr.open('POST', item.url, true);

    angular.forEach(uploadInfo.headers, function(value, name) {
      xhr.setRequestHeader(name, value);
    });

    xhr.send(form);
  },

SERVER

//things.router
app.route('/api/things/:thingId/add_photo')
  .post(things.uploadPhoto);

//things.controller
exports.uploadPhoto = function(req, res) {
  var formidable = require('formidable');

  var form = new formidable.IncomingForm();

  form.parse(req, function(err, fields, files) {
    var data = files.qqfile;
    //actual file is at data.path
    fs.createReadStream(data.path).pipe(request.put(uploadUrl));
  }
}