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

min*_*rse 9 javascript multipartform-data node.js express angularjs

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

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

我见过多方,但我不确定如何在我的客户端应用程序中使用它.

在我的客户端代码中,我发布了一个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});
}
Run Code Online (Sandbox Code Playgroud)

注意:我正在使用AngularJS的Restangular库,如此处所述

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

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

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

我很困惑,请有人帮我清理一下吗?

谢谢

编辑

好的,我看了一下multer跟随@yarons评论,这似乎是我想要使用的东西,我试图用我的快速路由器设置如下:

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);
Run Code Online (Sandbox Code Playgroud)

controller.js

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

在上面的控制器中注销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 }
Run Code Online (Sandbox Code Playgroud)

这是我通过客户端代码发布的内容:

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});
Run Code Online (Sandbox Code Playgroud)

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

编辑2

好吧,所以在经过更多的搜索后,我发现这篇文章使用multiparty,我有经理在我的设置中工作如下:

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);
};  
Run Code Online (Sandbox Code Playgroud)

这是现在按预期上传文件到我的其他服务器,虽然这个解决方案有效,我不喜欢这条线:

r._form = form
Run Code Online (Sandbox Code Playgroud)

似乎是将一个私有表单变量分配给请求对象,另外我在多方页面上看不到以这种方式记录的任何内容

任何人都可以对这个可能的解决方案发表任

mal*_*lix 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);
  },
Run Code Online (Sandbox Code Playgroud)

服务器

//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));
  }
}
Run Code Online (Sandbox Code Playgroud)