【问题标题】:Javascript FormData() File Upload to S3 works in Chrome but not in SafariJavascript FormData() File Upload to S3 适用于 Chrome 但不适用于 Safari
【发布时间】:2017-06-03 18:57:39
【问题描述】:

在开发 Web 应用程序时,我将图像文件从浏览器直接发送到 S3 存储桶。这在 Chrome 中运行良好,但在 Safari 中没有错误,但代码最终将一个空文件上传到 S3 存储桶。一切基本上看起来都在 Safari 中工作,S3 服务器甚至返回了一个 204 成功的 http 响应,并且文件看起来像是在存储桶中(但大小为 0 字节)。

我正在调试,blobData 在 Chrome 中的大小为 55747,但在 Safari 中对于同一图像只有 47560 的大小。另外,我发现在网络部分开发工具中看起来略有不同:

Chrome - 工作(204 响应的大小为正数 ~ 534B):

Safari - 不使用空文件(大小列仅显示虚线)

这里是JS上传代码:

    function uploadFile(data_url, s3Data, url, filename, id, thumbnail_url){
        var xhr = new XMLHttpRequest();
        xhr.open("POST", s3Data.url);

        var postData = new FormData();
        for(key in s3Data.fields){
            postData.append(key, s3Data.fields[key]);
        }

        var blobData = dataURItoBlob(data_url);

        postData.append('file', new File([blobData], filename));

        xhr.onreadystatechange = function() {
            if(xhr.readyState === 4){
                if(xhr.status === 200 || xhr.status === 204){
                    setTimeout(function(){

                        $('#photo_container').append('<div id="image_container_'+id+'" class="hover-card mdl-cell--3-col-desktop mdl-cell--2-col-tablet mdl-cell--2-col-phone mdl-shadow--2dp"></div>');

                        $('.star-image').unbind('click').click( star_image_click );
                        $('.close-image').unbind('click').click(remove_image_click);
                        loading_files -= 1;
                        if ( loading_files == 0 ) {
                            $('#photo_load_spinner').removeClass('is-active');
                            $('#photo_load_text').text("");
                        }else{
                            $('#photo_load_text').text(loading_files+" files loading.  Please wait.");
                        }

                    }, 1000);


                }else{
                    alert("Could not upload file. "+xhr.responseText);
                }
            }
        };
        xhr.send(postData);
    }

    function dataURItoBlob(dataURI) {
        var binary = atob(dataURI.split(',')[1]);
        var array = [];
        for(var i = 0; i < binary.length; i++) {
            array.push(binary.charCodeAt(i));
        }
        return new Blob([new Uint8Array(array)], {type: 'image/png'});
    }

我不知道接下来要寻找什么。任何帮助将不胜感激。谢谢!

编辑

只是想我应该提供更多信息。 data_url 是从我用来在浏览器中调整图像大小的画布元素生成的,尽管我已经确认画布在上传之前在 safari 中正确显示了图像。这是代码:

    function ResizeFile(raw_file) {

        var reader = new FileReader();
        reader.onload = function(e) {
            var img = document.createElement("img");

            img.onload = function() {

                var canvas = document.createElement('canvas');

                var ctx = canvas.getContext("2d");
                //ctx.drawImage(img, 0, 0);

                var MAX = 1200;
                var width = img.width;
                var height = img.height;

                if (width > height) {
                  if (width > MAX) {
                    height *= MAX / width;
                    width = MAX;
                  }
                } else {
                  if (height > MAX) {
                    width *= MAX / height;
                    height = MAX;
                  }
                }
                canvas.width = width;
                canvas.height = height;
                ctx.drawImage(img, 0, 0, img.width, img.height, 0, 0, width, height);

                var dataurl = canvas.toDataURL("image/png");

                getSignedRequest(dataurl);

            }

            img.src = e.target.result;

        }
        reader.readAsDataURL(raw_file);
    }

【问题讨论】:

  • 你解决了吗?我现在遇到了同样的问题,我已经卡了好几天了
  • @user2634633 好吧......有点解决了。因此,它被认为是 Safari navigator 的一项重要安全功能。它不允许程序更改文件输入的内容。安全性背后的想法是,它只允许上传用户实际选择的文件。所以我解决的方法是在表单中使用普通的文件输入元素,然后从中上传。我决定在上传之前不进行图像调整大小,而是在上传整个文件后在 S3 服务器上进行处理。
  • 感谢您的信息。我最终在客户端上裁剪和调整大小,并将数据 URI 发送到服务器,服务器将其转换为文件并进行 S3 上传。有点绕,但是想不出更好的了。
  • @user2634633 这样您仍然需要先将文件上传回您的服务器,对吧?但我想如果你在客户端调整它的大小,你会在上传回服务器时节省一点,但是你会失去从客户端直接上传到 s3 的资源节省。
  • 我收回了这一点——我刚刚设法让它与从 Safari 上传的客户端一起工作。问题出在我的缩放代码中,而不是上传代码中。我同步设置image.src,虽然它是一个异步操作,即使与特定数据URI一起使用也是如此。在 Chrome 上,这不是问题,但在 Safari 上,图像高度和宽度设置为 0,因为我没有等待图像加载 - 因此文件大小为 0b。检查您的 data_url 在 Safari 和 Chrome 上是否相同。通过使用image.onload 回调使我的缩放功能异步后,一切正常!

标签: javascript file-upload amazon-s3 safari


【解决方案1】:

我们可以调整图像文件的大小并上传到服务器。在 Chrome 和 Safari 上测试,都可以正常工作。

fileChange() {

    var fileInputTag = document.getElementById("myFile");

    if ('files' in fileInputTag) {
        if (fileInputTag.files.length > 0) {

            var file = fileInputTag.files[0];

            var type = 'image/jpeg',
            maxWidth = 800,
            maxHeight = 600,
            quality = 0.5,
            ratio = 1,

            canvas = document.createElement('canvas'),
            context = canvas['getContext']('2d'),
            canvasCopy = document.createElement('canvas'),
            copyContext = canvasCopy.getContext('2d'),

            img = new Image();

            img.onload = function () {
              if (img.width > maxWidth) {
                ratio = maxWidth / img.width;
              } else if (img.height > maxHeight) {
                ratio = maxHeight / img.height;
              }

              canvasCopy.width = img.width;
              canvasCopy.height = img.height;
              copyContext.drawImage(img, 0, 0);
              canvas.width = img.width * ratio;
              canvas.height = img.height * ratio;
              context.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);

              canvas.toBlob((blob) =>  {
                blob['name'] = file.name.split('.')[0] + '.jpeg';

                var formData:FormData = new FormData();
                formData.append('uploadFile', blob, blob.name);

                // do upload here

              }, type, quality);

            };

            img.src = URL.createObjectURL(file);

        }
    }
}

将其添加到 index.html 或在执行上述脚本之前执行它

if (!HTMLCanvasElement.prototype.toBlob) {
    Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
    value: function (callback, type, quality) {

        var binStr = atob( this.toDataURL(type, quality).split(',')[1] ),
            len = binStr.length,
            arr = new Uint8Array(len);

        for (var i = 0; i < len; i++ ) {
        arr[i] = binStr.charCodeAt(i);
        }

        callback( new Blob( [arr], {type: type || 'image/png'} ) );
    }
    });
}

【讨论】:

    【解决方案2】:

    【讨论】:

    • set 函数的大写“不支持”看起来不太好。谢谢,虽然我正在使用的append 方法有一个红色的?
    • 不确定,但他们有这 2 个脚注:[1] 在 Gecko 7.0(Firefox 7.0 / Thunderbird 7.0 / SeaMonkey 2.4)之前,如果您指定 Blob 作为要附加到对象的数据, “Content-Disposition”HTTP 标头中报告的文件名是空字符串;这导致某些服务器报告错误。从 Gecko 7.0 开始,发送文件名“blob”。 [2] Android 4.0 中的 XHR 使用 blob 发送 FormData 的空内容。我假设部分合规?
    • 是的,我正在寻找解决方法,似乎允许开发人员设置文件输入的值并不完全安全。浏览器只应该让用户选择文件...
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-16
    • 1970-01-01
    • 2014-08-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多