下载 <href= "blob:http://url/guid" 下载= "filename" /> 忽略下载属性



根据第行中的一些教程和示例,我在客户端上构建了一个基于jQuery的文件下载系统,在服务器上构建了MS WebAPI。

提供文件的直接链接是不可能的,因为API需要身份验证,因此文件URL是API端点,而不是文件位置。

在服务器上我有这个:

[HttpGet]
[Route("download/{filename}")]
public HttpResponseMessage DownloadFile(string filename)
{
try
{
// https://gist.github.com/joeriks/3714093
string path = string.Format("{0}/Exports/{1}", root, filename);
HttpResponseMessage result = new HttpResponseMessage(HttpStatusCode.OK);
var stream = new FileStream(path, FileMode.Open);
result.Content = new StreamContent(stream);
result.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream");
result.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
result.Content.Headers.ContentDisposition.FileName = "download.txt";
return result;
}
catch (Exception ex)
{
throw new HttpResponseException(HttpStatusCode.InternalServerError);
}
}

按预期返回响应:

HTTP/1.1 200 OK
Cache-Control: no-cache
Pragma: no-cache
Content-Length: 4809
Content-Type: application/octet-stream
Expires: -1
Server: Microsoft-IIS/10.0
Content-Disposition: attachment; filename=download.txt
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: POST, PUT, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: content-Type, accept, origin, X-Requested-With, X-Authentication, X-Nonce, name
Date: Thu, 25 Oct 2018 13:07:25 GMT

带有响应中的文本内容。到目前为止,一切如我所愿。

在客户端上,我有以下用于处理来自API的响应:

// https://stackoverflow.com/a/23797348
let disposition = jqXHR.getResponseHeader('Content-Disposition');
if (disposition && disposition.indexOf('attachment') !== -1) {
let filename = "scada-download.txt";
let matches = /filename[^;=n]*=((['"]).*?2|[^;n]*)/.exec(disposition);
if (matches != null && matches[1]) filename = matches[1].replace(/['"]/g, '');
let type = jqXHR.getResponseHeader('Content-Type');
let blob = new Blob([data], { type: "text/csv" });
var downloadUrl = URL.createObjectURL(blob);
let $a = $("<a id='temp_download_link' style='display: none;' />").attr("href", downloadUrl).attr("download", filename);
$("body").append($a);
$a.trigger("click");
}

这就像广告中所说的那样,并在页面上添加一个锚点,然后点击它触发下载。

下载工作正常,并保存具有正确内容的文件。

唯一不起作用的是,在我测试过的两种浏览器(Chrome 69,FF:62)中,提供的默认文件名只是一个GUID。

api和客户端代码目前都在我的本地开发机器http://127.0.0.1:9000/[client | api]上运行,所以跨源代码不应该起任何作用。

对api的调用是通过ajax进行的。最终,jQuery$.ajax()方法

为了清楚起见,插入DOM的锚点是:

<a id="temp_download_link" style="display: none;" href="blob:http://127.0.0.1:9000/c2c5ffb5-3f22-4a57-8775-4e0bbfbfef9e" download="download.txt"></a>

Chrome提供的默认文件名是URL中的GUID,FF生成一个看似未连接的6个字符的随机字符串。

具体而言,为什么浏览器同时忽略锚点的download="download.txt"属性和Content-Disposition: attachment; filename=download.txt

更新:

我用叉子叉了这把小提琴:

http://jsfiddle.net/Qjvb3/

并为href属性添加了一些其他值,似乎整个文件名设置充其量是不稳定的:

http://jsfiddle.net/yubjqwvs/

更新2

我已经把一个工作示例中的链接复制到了我的fiddle中,它是从原始网站上运行的,但不是从fiddle上运行的。

http://jsfiddle.net/yubjqwvs/2/

我有一种感觉,答案会归结为"为什么它在David Walsh博客上有效,但在小提琴上无效?">

我个人使用downloadjs来避免类似的问题:

  • 发出AJAX请求以下载该文件;这只是一个带有适当标头(Content-Disposition: attachment; filename="...")的PHP脚本
  • 用户点击链接,使用hashchange事件触发下载,然后我将AJAX响应传递给downloadjs。我想使用Blob URL也可以做到这一点

downloadjs的代码与jsfiddle非常相似,大致基于您的示例。它在Firefox 63、Opera 56上运行良好(我想它应该在Chrome上运行),在IE 11上失败,但好吧,这就是IE。

  • downloadjs创建了一个隐藏链接,超时设置为66ms(我想是等待DOM准备好或类似的事情)
  • 超时调用链接上的click(),该链接可能调用本机处理程序

使用您的代码,并使用click()document.getElementById()而不是jQuery,给我们这个jsfiddle。与downloadjs相反,我不使用window.setTimeout,它工作得很好,至少在Fx 63中是这样。这可能只是一种绕过浏览器错误的方法。

在您的情况下,我的猜测是,当您调用trigger('click')时,jQuery没有触发默认的处理程序,我无法理解,因为trigger文档似乎告诉我们它是这样的:

从jQuery 1.3开始,.trigger()ed事件在DOM树中冒泡;事件处理程序可以通过从处理程序或对传入的事件对象调用.stopPropagation()方法活动。尽管.trigger()模拟事件激活,但完成对于合成的事件对象,它不能完全复制自然发生的事件。

要触发通过jQuery绑定的处理程序,而不同时触发本机事件,请改用.trigerHandler()。(引用自jquery文档)

也许默认的点击链接不被视为jquery的处理程序(?),并且不会被解雇。

我看到的原因是您使用的是jQuery.attr()函数,而不是.prp()函数。HTML5属性和属性之间存在差异。第一个为事件绑定等情况下的元素提供标记,而另一个为DOM元素本身提供访问/设置值。

我将在这里链接一个可能有用的解释。

当修复这个问题变得至关重要时,我终于回到了这个问题上。事实证明,我的(国产)SPA框架是个问题,因为它覆盖了<a />的浏览器默认点击事件

我通过为blob链接添加一个catch来修复这个问题,然后触发默认的点击事件:

// convert all a/href to a#href
$("body").delegate("a", "click", function () {
let href: string = $(this).attr("href"); 
// check its not an external / absolute URL
let regex: RegExp = new RegExp("^(blob:)?(http|https)(://)", "ig");
let match = regex.exec(href);
if (match) {
// match[0] is the full match, match[1] is lookign for "blob:"
// it will either be undefined or blob:
if (match[1]) { // its a blob url, call the default
return true; // !! this line is the core of the fix !!
}
else { // load a normal link
// see if its got a target.
let target: string = $(this).attr("target");
switch (target) {
default: document.location.href = href; break;
case "_blank": window.open(href); break;
}
}   
}
else
// SPA stuff
return false;
});

因此,本质上这是其他人都不会遇到的问题,除非他们像我一样愚蠢到扮演自己的SPA框架。

就在这里https://github.com/JohnRayson/JSPA

最新更新