【问题标题】:Downloading a large blob from API to user doesn't show download until complete从 API 向用户下载大型 blob 直到完成后才会显示下载
【发布时间】:2019-09-10 00:08:39
【问题描述】:

我的问题与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 调用,该调用从数据库中获取文件并将其作为 blob 响应流式传输。 Angular 接受此响应,等到它下载完所有 50MB 后,然后显示下载对话框。

API 将返回 Content Disposition 标头以及内容长度、内容类型、文件名等。我已经在谷歌上搜索了所有内容并搜索了一些东西来模仿这一点,我有点理解浏览器不会自动下载像往常一样,因为请求是通过 Angular 传递的,并且浏览器不会自动假设所有 blob XHR 都应该被保存。

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

【问题讨论】:

    标签: angular


    【解决方案1】:

    所以对于有同样问题的人,我就是这样做的:

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

    如果您有一个未加密的 API,那么您可以使用@Pace 使用window.open(urlToFile)window.location.href(urlToFile) 执行上述操作。

    因为我的 API 需要通过标头中的 Bearer 令牌进行授权,所以我所做的是创建一个端点来检查令牌以及所请求的文件 ID。然后 API 创建一个 GUID(或唯一的 id)并将 GUID 和文件 ID 一起存储在 list 中,然后将其与过期时间一起存储在 MemoryCache 中。

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

    • 用户必须通过身份验证才能请求 GUID
    • GUID 与某个文件 ID 相关联
    • GUID 的过期时间很短(例如 1 分钟)
    • GUID 使用一次后被删除
    • API 发回 guid 和 Angular 应用程序使用 GUID 回调 API 之间的时间跨度如此之短,我看不出任何人怎么可能在该时间范围内插入它并弄清楚如何使用它

    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 应用程序中创建自己的下载管理器,而不是使用大多数用户期望的浏览器默认设置。

    【讨论】:

      【解决方案2】:

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

      如果您因为想要操作字节或类似的东西而必须将其流式传输到 Angular,那么您可以订阅 HttpClient 的进度事件来报告您自己的进度 (see here)。

      如果您想将文件保存到用户的磁盘,您可以使用file-saver 包。

      【讨论】:

      • 关于这个的几个问题:1)我的 API 已经过身份验证,所以我不能只是 window.open 一个文件 url。 2)我不希望用户在点击下载时看到弹出窗口。
      • 如果是要下载的文件,它实际上并没有打开一个新窗口。你也可以window.location = ... 在任何一种情况下它都会开始下载。如果您使用 cookie 进行身份验证,那么这些方法可以正常工作。但是,如果您正在执行基本身份验证或基于标头的身份验证,那么这些方法将无法直接工作。 This question 讨论了在需要标头身份验证时下载文件的方法。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-07
      相关资源
      最近更新 更多