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

9
我正在尝试在我的Node Express服务器上处理POST请求,以处理多部分表单上传,在我的情况下,用户正在上传图像。
我希望通过我的Express应用程序将上传管道传输到另一个服务器,该应用程序当前设置为使用body parser,我还发现它不支持多部分正文,而是建议使用其他库。
我已经看到了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});
}

注意: 我正在使用Restangular库来进行AngularJS编程,文档在这里

根据我查看的multiparty文档,我必须处理表单上传事件,并在表单上传完成后进一步操作它。

问题是,我希望能直接将上传内容传输到另一个服务器。之前,我的客户端应用程序直接调用了另一个服务器,但现在我正在尝试通过Express路由所有内容,这可能吗,还是我必须使用类似于multiparty的东西?

请求文档提供了使用formData的示例,但我不确定如何将其与我看到的multiparty示例配合使用。例如,在Express中使用mutliparty完成上传后,我是否需要构造另一个formData对象来进行进一步的请求,还是必须将每个部分都传输到其他服务器?

我很困惑,请有人帮助我澄清这一点好吗?

谢谢

编辑

好的,我已经查看了@yarons的意见并了解了multer,这似乎是我想要使用的类型。我尝试将其与下面的express路由器设置一起使用:

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:
好的,在进一步搜索后,我找到了this使用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);
};  

现在正在按照我的期望将文件上传到我的另一台服务器,虽然这个解决方案有效,但我不喜欢这行代码:

r._form = form

似乎将一个私有表单变量分配给请求对象,而且我在multiparty页面上没有看到任何以这种方式记录的内容。 有人能对这个可能的解决方案发表评论吗?

你尝试过multer吗?它是一个中间件,让你可以在文件上传后访问该文件,而且你不需要监听上传事件。 - Yaron Schwimmer
谢谢@yarons,这个库似乎更容易使用,我已经编辑了我的帖子,并尝试了一些代码,但是我还没有成功。我正在客户端构建一个formData对象,用于向Express进行初始发布,因此希望我只需将其作为请求调用中的formData参数值即可向其他服务器发出请求。现在看来,我必须使用两个formData对象,这不太对劲。 - mindparse
这个答案中,有一个使用Needle(我以前从未尝试过)发送文件从一个服务器到另一个服务器的示例。抱歉我只是向您推荐第三方软件包... - Yaron Schwimmer
感谢您的建议@yarons,我不确定是否想要再看另一个软件包。我会继续挖掘并希望有人能提供其他想法。 - mindparse
1个回答

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

服务器

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接