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

标签 javascript angularjs node.js express multipartform-data

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

我想通过我的 Express 应用程序将上传内容传输到另一台服务器,该应用程序当前设置为使用正文解析器,我还看到它不支持多部分 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 路由所有内容,这可能吗,还是我必须使用多方之类的东西?

请求文档给出了使用 formData 的示例,但我不确定这将如何与我看到的多方示例一起使用。例如,一旦使用 mutliparty 在 Express 中完成上传,我是否必须构建另一个 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);
};

在上面的 Controller 中注销 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使用多方的文章,我让经理在我的设置中开始工作,如下所示:

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

似乎正在为请求对象分配一个私有(private)表单变量,而且我在多方页面上看不到以这种方式记录的任何内容

任何人都可以对这个可能的解决方案提出任何意见吗?

最佳答案

我们使用类似下面的东西:

客户

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

关于javascript - 管道多部分表单上传到另一台服务器,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34263257/

相关文章:

javascript - Karma 找不到配置文件中指定的文件

JavaScript 范围 : how do I get information out of a function passed into another function?

git - 在 Heroku 上使用带有 npm 和 Node 的 Git 依赖项

javascript - VueJS : Using nested components within jquery plugin

javascript - filepicker.io 在 Chrome 中阻止了 iframe

javascript - AngularJS - 使用 $routeProvider :resolve 拒绝了 $http promise

angularjs - 当 Controller 更改值时,为什么指令模板中的 ng-model 不更新

javascript - 尝试通过 Dropbox SDK 访问 Dropbox API 时出现错误 500

HTML5 中的 Javascript 表单验证

javascript - map API v3 : Calculate bearing