$ http.post:大文件不起作用

时间:2018-07-16 11:29:54

标签: angularjs node.js express ng-file-upload body-parser

我正在尝试使用以下代码通过我的Web应用程序上传文件。

查看:

  <form name="uploadForm" class="form-horizontal col-sm-12">
    <div class="col-sm-4">
      <input type="file" ng-model="rsdCtrl.viewData.file" name="file"/>
    </div>
    <div class="col-sm-4">
      <button class="btn btn-success" type="submit" ng-click="uploadFile()">Upload</button>
    </div>
  </form>

控制器:

function uploadFile(){
  if (uploadForm.file.$valid && file) {
    return uploadService.upload(vd.file, "Convictions Calculator", "PCCS").then(function(response){
      /* Some stuff */
    }).catch(handleServiceError);
  }
}

uploadService:

(function (){
'use strict';
angular.module('cica.common').service('uploadService', ['$http', '$routeParams', uploadService]);

function uploadService($http, $routeParams) {

    this.upload = function (file, name, type) {
        const fd = new FormData();
        fd.append('document', file);
        fd.append('jobId', $routeParams.jobId);
        fd.append('documentRename', name);
        fd.append('documentType', type);

        return $http.post('/document/upload', fd, {
            transformRequest: angular.identity,
            headers: {'Content-Type': undefined}
        }).catch(function(err){
            handleHttpError('Unable to upload document.', err);
        });
    };
  }
})();

routes.js:

    'POST /document/upload': {controller: 'DocumentController', action: 'uploadDocument'},

DocumentController:

"use strict";
const fs = require('fs');

module.exports = {
  uploadDocument: function (req, res) {
    console.log(req.allParams());   //Inserted as part of debugging
    const params = req.allParams();
    req.file('document').upload({
        // don't allow the total upload size to exceed ~100MB
        maxBytes: 100000000
    }, function whenDone(err, uploadedFiles) {
        if (err) {
            return res.serverError(err);
        }
        // If no files were uploaded, respond with an error.
        else if (uploadedFiles.length === 0) {
            return res.serverError('No file was uploaded');
        } else {
            const filePath = uploadedFiles[0].fd;
            const filename = uploadedFiles[0].filename;
            return fs.readFile(filePath, function (err, data) {
                if (err) {
                    return res.serverError(err);
                } else {
                    const jobId = params.jobId;
                    const jobVars =
                        {
                            filePath: results.filePath,
                            fileName: params.documentRename,
                            fileType: params.documentType
                        };
                    return DocumentService.uploadConvictions(req.session.sessionId, jobId, jobVars).then(function (response) {
                        return res.send("Document uploaded.");
                    }).catch(function (err) {
                        return res.serverError(err);
                    });
                }
            });
        }
    });
},

如果我上传一个.jpeg(大约11kB),则上传的效果与预期的一样,但是,如果我尝试上传一个更大的.jpeg(大约170kB),它就会掉下来。尽管没有立即引发/捕获的错误,但是发生的是在上载服务中创建的formData对象似乎丢失了其数据。如果我在其值上设置断点,则对于较大的文件它将返回空,当函数尝试进一步使用这些变量时,最终将导致错误。您可以通过这种方法上传的文件大小是否设置了某种限制,或者我配置不正确?

3 个答案:

答案 0 :(得分:3)

我借此机会并假设您使用print (df) No. timestamp letter 0 1 2018-07-07 00:15:52 A 1 2 2018-07-07 09:55:34 A 2 3 2018-07-07 14:13:32 A 3 4 2018-07-08 02:22:51 A 作为中间件。 bodyParser的默认bodyParser为100kb。看limit

node_modules/body-parser/lib/types/urlencoded.js

您可以通过

更改var limit = typeof options.limit !== 'number' ? bytes(options.limit || '100kb') : options.limit 中的限制
app.js

答案 1 :(得分:3)

我使用此替代方法...

HTML:

<input type="file" style="display:none" value="" id="uploadNewAttachment"/>

JavaScript:

在JavaScript中,您可以使用3种方法上传文件:

var binBlob = []; // If you use AngularJS, better leave it out of the DOM
var fi = document.getElementById('uploadNewAttachment');
fi.onchange = function(e) {
    r = new FileReader();
    r.onloadend = function(ev) {
        binBlob[binBlob.length] = ev.target.result;
    };
    //r.readAsDataURL(e.target.files[0]); // Very slow due to Base64 encoding
    //r.readAsBinaryString(e.target.files[0]); // Slow and may result in incompatible chars with AJAX and PHP side
    r.readAsArrayBuffer(e.target.files[0]); // Fast and Furious!
};
$(fi).trigger('click');

我们拥有的JavaScript端是一个Uint8Array字节,其值从0到255(或Int8Array -128到127)。

通过AJAX发送此数组时,将使用符号和逗号将其“最大化”。这样会增加发送的总字节数。

EX:

[123, 38, 98, 240, 136, ...] or worse: [-123, 38, -81, 127, -127, ...]

如您所见,传输的字符数过大。

我们可以改为进行以下操作:

在通过AJAX发送数据之前,请执行以下操作:

var hexBlob = [];
for(var idx=0; idx<binBlob.length; idx++) {
    var ex = Array.from(new Uint8Array(binBlob[idx]));;
    for(var i=0;i<ex.length; i++) {
        ex[i] = ex[i].toString(16).padStart(2,'0');
    };
    hexBlob[idx] = ex.join('');
}

您现在拥有的是一个用char组成的十六进制字节字符串!

例如:

3a05f4c9...

使用较少的带符号或无符号javascript数组的字符。

PHP: 在PHP方面,您只需使用以下命令即可将该数组直接解码为二进制数据:

for($idx=0; $idx<=count($hexBlob); $idx++) {
    // ...
    $binData = pack('H*',$hexBlob[$idx]);
    $bytesWritten = file_put_contents($path.'/'.$fileName[$idx], $binData);
    //...
}

此解决方案对我来说效果很好。

答案 2 :(得分:1)

上传大文件时避免使用FormData API 1

FormData APIbase64编码数据,这会增加33%的额外开销。

直接发送文件而不是发送FormData

app.service('fileUpload', function ($http) {
    this.uploadFileToUrl = function (url, file) {
        ̶v̶a̶r̶ ̶f̶d̶ ̶=̶ ̶n̶e̶w̶ ̶F̶o̶r̶m̶D̶a̶t̶a̶(̶)̶;̶
        ̶f̶d̶.̶a̶p̶p̶e̶n̶d̶(̶'̶f̶i̶l̶e̶'̶,̶ ̶f̶i̶l̶e̶)̶;̶
        ̶r̶e̶t̶u̶r̶n̶ ̶$̶h̶t̶t̶p̶.̶p̶o̶s̶t̶(̶u̶r̶l̶,̶ ̶f̶d̶,̶ ̶{̶
        return $http.post(url, file, {
            transformRequest: angular.identity,
            headers: { 'Content-Type': undefined }
        });
    };
});

浏览器发送FormData时,它使用'Content-Type': multipart/formdata并使用base64编码每个部分。

浏览器发送文件(或Blob)时,会将内容类型设置为文件(或Blob)的MIME类型。它将二进制数据放入请求的正文中。


如何启用<input type="file">ng-model一起使用 2

现成的ng-model指令不适用于input type="file"。它需要一个指令:

app.directive("selectNgFile", function() {
  return {
    require: "ngModel",
    link: function postLink(scope,elem,attrs,ngModel) {
      elem.on("change", function(e) {
        var files = elem[0].files[0];
        ngModel.$setViewValue(files);
      })
    }
  }
});

用法:

<input type="file" select-ng-file ng-model="rsdCtrl.viewData.file" name="file"/>