从API向用户下载大块文件直到完成才显示下载

时间:2019-04-19 19:03:49

标签: angular

我的问题类似于this one,但那是一篇过时的文章,没有太多的上下文或细节,所以这是我得到的:

当我的组件调用下载文件方法时,该方法又调用下载文件服务,该服务从我的API发出HTTP GET请求,该请求将blob流回到服务。

组件:

    this.svc.getDownloadFile(fileID, fileName).subscribe(res => {
      console.log(res);
    });
  }

服务:

  getDownloadFile(fileId: number, fileName: string): Observable<any> {
    return this.http.get(this.downloadFileUrl + "/" + fileId, {
      reportProgress: true,
      observe: "events",
      headers: this.getHeaders(),
      responseType: "blob"
    });
  }

问题在于,因为这是一个API调用,所以Blob通过XHR流传输,我可以在Chrome的“网络”标签中看到它的下载及其进度。我遇到的问题是,在Chrome(或任何浏览器)中下载文件时,通常会收到某种浏览器下载通知,这只会在整个响应完成后才会发生。

例如,我在数据库中有一个50MB的文件。用户单击一个链接以从Angular应用程序下载该文件,该组件调用该服务,该服务依次执行API调用,该API将从DB获取文件并将其作为Blob响应进行流式传输。 Angular接受此响应,等到它已下载所有50MB内容,然后显示下载对话框。

Download notification only shows up after its 100% complete

Network Tab shows download in progress

API正在返回Content Disposition标头以及内容长度,内容类型,文件名等。我已经在Google各处进行搜索,并在上下搜索了类似内容,我知道浏览器不会自动下载之所以像往常一样是因为请求是通过Angular传递的,并且浏览器不会自动假设所有的Blob XHR应该都已保存。

我尝试使用:https://www.npmjs.com/package/streamsaver 但是此程序包实际上仅处理已存储的文件,而不处理从API作为blob发送的文件。

2 个答案:

答案 0 :(得分:1)

如果API返回了Content-Disposition标头,则可以使用window.open(url),它应该可以正常下载。

如果由于要操作字节或类似操作而必须将其流式传输到Angular,则可以订阅HttpClient的进度事件以报告自己的进度(see here)。

如果随后要将文件保存到用户磁盘,则可以使用file-saver软件包。

答案 1 :(得分:0)

所以对于有相同问题的人,我要做的是:

请记住,这是在我的工作设置中,我们有一个存储加密blob的数据库,一个API通过文件流将其提供给我们的角度应用程序。

如果您拥有未加密的API,则可以使用window.open(urlToFile)window.location.href(urlToFile)用@Pace进行上述操作。

由于我的API需要通过标头中的Bearer令牌进行授权,因此我创建了一个端点,该端点将检查令牌以及所请求的文件ID。然后,API创建一个GUID(或唯一的种类ID),并将GUID和文件ID一起存储在一个列表中,然后将其与到期时间一起存储在MemoryCache中。

完成此操作后,将返回带有已创建GUID的Ok响应。我不是安全专家,但是我认为这可能会为它提供安全性,原因如下:我确信这不是一个完美的解决方案:

  • 必须对用户进行身份验证才能请求GUID
  • GUID与特定文件ID关联
  • GUID的到期时间很短(例如1分钟)
  • 一次使用后,GUID被删除
  • 从API发送回GUID到使用GUID回调Angular应用之间的时间如此短,我看不出有人可以在这段时间里插进去,并弄清楚如何使用它

GUID返回到Angular应用程序,然后在GUID返回之后调用window.location.href并将其设置为通过GUID和文件ID的另一个端点。

此端点配置为不需要身份验证,并且仅允许下载列表中带有正确GUID的文件。

例如,这是我的服务被调用:

getDownloadFile(fileId: number): Observable<any> {
    return this.http
      .get(this.downloadFileUrl + "/" + fileId, {
        headers: this.getHeaders() //Attaches bearer token
      })
      .pipe(
        tap(res => { //res = the GUID being returned from API
          window.location.href =
            this.downloadFileUrl + "/temp/" + fileId + "/" + res;
        })
      );
  }

如您所见,它调用已验证的端点,取回GUID,并立即使用GUID和文件ID将当前浏览器窗口的url设置为临时端点。请注意,由于这是正确设置了内容处置的文件流或Blob,因此它不会离开Angular应用程序,因此只能通过下载管理器开始下载。

这是我发现的唯一方法,它允许浏览器正常处理来自流API端点的下载。

大多数较小的文件并不重要,因为它们通过API下载得如此之快,以至于您通常一眨眼就看不到下载是在一开始还是没有开始,但是对于大文件如果您仅通过XHR进行API调用,则用户将没有任何指示,表明文件正在下载,直到请求完成,然后BAM!突然之间,您的下载文件中就会出现150MB的文件。这也是一种不错的方法,因为即使您离开Angular应用程序,它也会继续下载,这与常规API XHR订阅不同,后者将随该应用程序结束。 我还发现了如何点按具有Blob的XHR请求并跟踪进度,但是您将不得不在Angular应用程序中创建自己的下载管理器,而不使用大多数用户期望的默认浏览器。