代码之家  ›  专栏  ›  技术社区  ›  0xC0DED00D

在iOS Safari中使用XHR下载文件

  •  0
  • 0xC0DED00D  · 技术社区  · 5 年前

    我正在尝试添加下载托管在服务器中的文件的功能。要访问文件,我必须发送 Authorization 头,因此我必须发送一个XHR请求以从服务器获取文件。由于文件内容在一个变量中,我必须创建一个数据url以使其作为 href 锚标记的属性,并以编程方式单击它以下载文件。 它在几乎所有的浏览器中都运行良好(除了IE11,我已经为它编写了单独的代码),但是在iOS Safari(在iOS的一些版本中)中,它会给出错误。这是我正在使用的代码-

    var isBrowserIE = window.navigator && window.navigator.msSaveOrOpenBlob;
    var dataHref = 'https://example.com/doc.pdf';
    var xhr = new XMLHttpRequest();
    xhr.open('GET', dataHref, true);
    xhr.setRequestHeader('Content-Type', 'application/pdf');
    xhr.setRequestHeader('Authorization', 'Bearer ' + token);
    xhr.responseType = isBrowserIE ? 'blob' : 'arraybuffer';
    xhr.onload = function (e) {
        if (this.status == 200) {
            //For IE11
            if (isBrowserIE) {
                // Create a new Blob object using the response data of the onload object
                var blob = new Blob([this.response], { type: 'application/pdf' });
    
                var bool = window.navigator.msSaveOrOpenBlob(blob, docName);
                if (!bool) {
                    alert("Download failed, Please try again later");
                }
            } else {
                var uInt8Array = new Uint8Array(this.response);
                var i = uInt8Array.length;
                var binaryString = new Array(i);
                while (i--) {
                    binaryString[i] = String.fromCharCode(uInt8Array[i]);
                }
                var data = binaryString.join('');
    
                var base64 = window.btoa(data);
    
                var dataUrl = 'data:application/octet-stream;charset=utf-16le;base64,' + base64;
                var element = document.createElement('a');
                element.setAttribute('href', dataUrl);
                element.setAttribute('download', 'doc.pdf');
                element.style.display = 'none';
                document.body.appendChild(element);
                element.click();
                document.body.removeChild(element);
            }
        } else {
            alert("Download failed, Please try again later");
            closeWindow();
        }
    };
    
    xhr.send();
    

    下面是我可能遇到的错误-

    Safari cannot open the page.<br><br>The error was: “Data URL decoding&nbsp;failed”.

    我有没有漏掉什么是这个错误的原因?这个错误只发生在iPad4和iPad5上,但在iPadMini和iPhoneXR上。不知道为什么它在某些版本的iOS设备上工作,而在其他版本上不工作。

    0 回复  |  直到 5 年前
        1
  •  2
  •   0xC0DED00D    5 年前

    所以,我终于明白了。这是我的最终代码,注释中有解释(对不起ES5代码,我需要支持IE11,而当前项目还没有使用babel)-

    /* exported DownloadHandler */
    /* global Uint8Array*/
    var DownloadHandler = (function() {
      function isMobileDevice() {
        return navigator.userAgent.match(/Android|iPhone|iPad|iPod|BlackBerry|Opera Mini|IEMobile/i);
      }
      function isChromeBrowser() {
        return navigator.userAgent.match(/Crios|Chrome/i);
      }
      function isIEBrowser() {
        return window.navigator && window.navigator.msSaveOrOpenBlob;
      }
      function isSafariBrowser() {
        return navigator.userAgent.match(/Safari/i);
      }
      function getResponseType() {
        // Both Desktop Chrome and IE supports blob properly
        // Chrome also supports Data URI way, but it fails miserably when the file size is more than 2 MB (Not sure about the exact limit though).
        if (isIEBrowser() || isChromeBrowser()) {
          return 'blob';
        } else if (isMobileDevice()) {
          return 'arraybuffer';
        }
        return 'blob';
      }
      function getBlobUriFromResponse(response) {
        var blob = new Blob([response], { type: 'application/pdf' });
        var downloadUrl = URL.createObjectURL(blob);
        return downloadUrl;
      }
    
      function getDataUriFromResponse(response) {
        var uInt8Array = new Uint8Array(response);
        var i = uInt8Array.length;
        var binaryString = new Array(i);
        while (i--) {
          binaryString[i] = String.fromCharCode(uInt8Array[i]);
        }
        var data = binaryString.join('');
    
        var base64 = window.btoa(data);
    
        var dataUrl = 'data:application/octet-stream;charset=utf-16le;base64,' + base64;
        return dataUrl;
      }
      function downloadFileUsingXHR(fileName, fileUrl, fileMimeType, requestType, headersList) {
        var xhr = new XMLHttpRequest();
        xhr.open(requestType, fileUrl, true);
        xhr.setRequestHeader('Content-Type', fileMimeType);
        for (var i = 0; i < headersList.length; i++) {
          var header = headersList[i];
          xhr.setRequestHeader(header.key, header.value);
        }
        xhr.responseType = getResponseType();
        xhr.onload = function() {
          if (this.status == 200) {
            //For IE11
            //IE uses blob with vendor specific code
            if (isIEBrowser()) {
              // Create a new Blob object using the response data of the onload object
              var blob = new Blob([this.response], { type: fileMimeType });
    
              var bool = window.navigator.msSaveOrOpenBlob(blob, fileName);
              if (!bool) {
                alert('Download failed, Please try again later');
              }
            } else {
              var dataUrl;
              if (this.responseType === 'blob') {
                dataUrl = getBlobUriFromResponse(this.response);
              } else {
                dataUrl = getDataUriFromResponse(this.response);
              }
              var element = document.createElement('a');
              // Safari doesn't work well with blank targets
              if (!isSafariBrowser()) {
                element.setAttribute('target', '_blank');
              }
              element.setAttribute('href', dataUrl);
              element.setAttribute('download', fileName);
              element.style.display = 'none';
              document.body.appendChild(element);
              element.click();
              document.body.removeChild(element);
            }
          } else {
            alert('Download failed, Please try again later');
          }
        };
    
        xhr.send();
      }
      return {
        downloadFileUsingXHR: downloadFileUsingXHR
      };
    })();
    

    下面是如何使用上述代码:

    DownloadHandler.downloadFileUsingXHR('example.pdf', 'https://example.com/doc.pdf', 'application/pdf','GET',[{key:'Authorization',value:'Bearer ' + token}]);
    

    我以后可能会把它转换成一个库并在这里发布一个链接。我也会有机会改进代码的