使用javascript从blob:http保存文件而无重定向

时间:2019-02-05 20:46:43

标签: javascript ajax image browser webclient-download

我已经在SO中通过了一些建议的解决方案,但是没有成功。这就是问题。使用从API端点检索的数据生成Blob时,我想强制浏览器下载Blob。到目前为止,我已经尝试了三种解决方案,但是没有一种有效。代码如下。请注意,我在代码中添加了一些注释以进一步解释。

const FILE_NAME_REGEX = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;
    
    export function Download(url) {
    	return APICall.get(url).then(response => {
    		const disposition = response.request.getResponseHeader('Content-Disposition');
    		//^This line gives the 'Refused to get unsafe header "Content-Disposition"' error, so next few lines won't execute and part with generating anchor is not used, but the part of code under the 'else' branch.
    
    		if (disposition && disposition.indexOf('attachment') !== -1) {
    			const matches = FILE_NAME_REGEX.exec(disposition);
    
    			if (matches != null && matches[1]) {
    				filename = matches[1].replace(/['"]/g, '');
    			}
    		}
    
    		const type = response.request.getResponseHeader('Content-Type');
    
    		const blob = new Blob([response.data], { type });
    
    		if (typeof window.navigator.msSaveBlob !== 'undefined') {
    			window.navigator.msSaveBlob(blob, filename);
    		} else {
    			const URL = window.URL || window.webkitURL;
    			const downloadUrl = URL.createObjectURL(blob);
    
    			if (filename) {
    				const a = document.createElement('a');
    
    				if (typeof a.download === 'undefined') {
    					window.location = downloadUrl;
    				} else {
    					a.href = downloadUrl;
    					a.download = filename;
    					document.body.appendChild(a);
    					a.click();
    					document.body.removeChild(a);
    				}
    			} else {

 // 1. soultion 
   window.location = downloadUrl;

 }
    
    			setTimeout(() => {
    				URL.revokeObjectURL(downloadUrl);
    			}, 100);
    		}
    	});
    }

// 2.解决方案

        const ifrmDownloader = document.createElement('iframe');
        ifrmDownloader.setAttribute('src', downloadUrl);
        ifrmDownloader.style.width = '0px';
        ifrmDownloader.style.height = '0px';
        document.body.appendChild(ifrmDownloader);

// 3.解决方案

window.open(downloadUrl,_blank);
  1. 解决方案不起作用,因为它打开了另一个选项卡并返回空的小方块而不是文件。这可能是由于打开新选项卡时丢失了临时Blob。

  2. 解决方案根本行不通,我也不知道为什么。将iframe添加到dom中,并将请求记录在开发者控制台下的“网络”标签中,但不会触发下载。可能与解决方案1中的原因相同。此外,还会记录此消息:

资源被解释为文档,但以MIME类型image / png传输:“ blob:https://example.com/e53bf47b-7f39-4758-b3dd-3cc2df5889ad”。

  1. 解决方案无法正常运行。首先,浏览器阻止弹出窗口,并且在允许弹出窗口之后,不会启动文件下载。

我觉得这里缺少什么,但是呢?

2 个答案:

答案 0 :(得分:1)

您的代码被设置为仅在设置filename变量时下载文件。

仅当您能够读取Content-Disposition标头时(即仅在同源时)才设置此变量。

因此,简单的解决方法是在无法从标头获取filename的情况下设置伪值,因此,filename总是被设置,并且您总是尝试以insted的方式下载它只是在新页面中打开它。
更聪明的解决方法是尝试从url变量中解析文件名,但是在不知道可能的URL格式的情况下,很难为您创建一个。

const FILE_NAME_REGEX = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/;

export function Download(url) {
  return APICall.get(url).then(response => {
      const disposition = response.request.getResponseHeader('Content-Disposition');
      if (disposition && disposition.indexOf('attachment') !== -1) {
        const matches = FILE_NAME_REGEX.exec(disposition);

        if (matches != null && matches[1]) {
          filename = matches[1].replace(/['"]/g, '');
        }
      }
      if (!filename) {
        // getFileNameFromURL(url);
        filename = 'dummy.png';
      }
      const type = response.request.getResponseHeader('Content-Type');

      const blob = new Blob([response.data], {
        type
      });

      if (typeof window.navigator.msSaveBlob !== 'undefined') {
        window.navigator.msSaveBlob(blob, filename);
      } else {
        const URL = window.URL || window.webkitURL;
        const downloadUrl = URL.createObjectURL(blob);

//     if (filename) { // always truthy
        const a = document.createElement('a');

        if (typeof a.download === 'undefined') {
          window.location = downloadUrl;
        } else {
          a.href = downloadUrl;
          a.download = filename;
          document.body.appendChild(a);
          a.click();
          document.body.removeChild(a);
        }
//    }
/*  else { // useless
    window.location = downloadUrl;
    }
*/
      setTimeout(() => {
        URL.revokeObjectURL(downloadUrl);
      }, 100);
    }
  });
}

答案 1 :(得分:0)

关于我发布的问题,这里是我所发现的。实际上有两个问题:

  1. Content-Disposition 标头被拒绝,因此没有文件名或发布信息。根据@Kaiido的回复,这是通过从URL中提取文件名并添加',png'来实现的。例如。

    const chunks = extendedURl.split('/');
    const pngExtension = '.png';
    const baseName = chunks[chunks.length - 1].split('?')[0];
    filename = `${baseName}${pngExtension}`;
    
  2. 直到今天我才意识到的更严重的问题是,当调用axios的 Get 方法时, responseType 未设置为' arraybuffer '。

    因此,我能够下载“损坏”的文件。这个问题帮助我弄清楚实际问题出在哪里:File is corrupted while downloading using angular $http

一旦为调用的 Get 方法提供了' responseType:“ arraybuffer” ',文件开始显得正常,而不是“损坏”。

总而言之,在调用返回 FileStreamResult 的.net Core Web API端点时,为了能够在您的FE JavaScript中创建 Blob ,您应该明确将responseType设置为“ arraybuffer”,如下所示:

axios.get(extendedURl, { responseType: 'arraybuffer' }).then(response =>...