Angular:下载“较大”文件时浏览器崩溃

Angular: browser crashes when downloading 'larger' files

提问人:James A Cubeta 提问时间:8/11/2022 最后编辑:James A Cubeta 更新时间:8/13/2022 访问量:462

问:

我在尝试通过我的 Angular (v11) Web 应用程序实现基于 Spring Boot 的服务器提供的电子表格下载时遇到了问题。我的应用程序提供了一些“更大”的电子表格供下载 (~32 MB),浏览器有时会在尝试下载这些文件时崩溃。

这是我的代码的基础知识 - 首先是我的 Angular 应用程序中的 RESTful 客户端:

export class RestService {
    
    private url: string = "http://example.com/api/dowwnload";
    constructor(private http: HttpClient) {}

    requestFile(id: number): Observable<HttpResponse<any>> { 
        this.http.get<any>(url+"?id="+id, {observe: 'response', responseType: 'blob' as 'json'});
    }
}

...以及尝试进行下载的代码(使用文件保护程序):

this.restService.requestFile(id).subscribe(res => {
   // requires: import {saveAs} from 'file-saver';
   saveAs(res.body, 'filename.xlxs');
});

下面是服务器端的终结点:

@GetMapping("/download")
public ResponseEntity<Resource> downloadFile(@RequestParam("id") int id) { 

    File out = exportFileToTmpById(id); // method that writes file to /tmp here 
    try { 
        FileSystemResource resource = new FileSystemResource(out);
        
        HttpHeaders headers = new HttpHeaders();
        headers.set(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + out.getName() + "\"");
        return ResponseEntity.ok()
            .headers(httpHeaders)
            .contentLength(out.length())
            .contentType(MediaType.APPLICATION_OCTET_STREAM)
            .body(resource);
    }
    catch (Exception e) { 
        e.printStackTrace();
    }

}

我的直觉是,Angular 方面要么存在内存问题,要么存在竞争条件,但我不是这方面的专家(#programmingbystackoverflow)。浏览器(Chrome 和 Firefox)死机时没有错误消息,也没有相关消息写入 Chrome 的日志。我应该在单击后删除子元素“a”吗?

如果您需要更多详细信息,请告诉我,以帮助我解决此问题。

java angular typescript spring-boot 休息

评论

1赞 onrails 8/11/2022
实际上,如果没有错误或stackblitz上的可重现示例,就很难猜测失败在哪里。
0赞 onrails 8/11/2022
但是,我发现在客户端安全性方面,您错过了一些非常重要的东西。在下载 DOMSanitizer 文档中读取的文件时,您没有对 DOM 进行清理,以防止 XSS 攻击:angular.io/api/platform-browser/DomSanitizer
1赞 CuriousMind 8/11/2022
我想问题出在你正在构造的新 Blob 对象上。由于已将 responseType 设置为 blob,因此无需再次构造它。它会在开始下载之前将所有内容加载到内存中。除此之外,您还可以参考以下帖子 - stackoverflow.com/questions/42543448/...
1赞 CuriousMind 8/12/2022
@JamesACubeta 这有点奇怪。使用新代码时,它不应将数据加载到客户端内存中,而是直接流式传输数据。您能否确认使用 Postman 调用此 api 时会发生什么?另外,作为替代方案,您可以跳过对 HttpClient 的调用,而是直接构造带有指向 Spring Rest API 的链接的锚标记,在其上调用单击事件,然后删除锚标记。(如果您希望设置任何授权参数,此方法将失败)。试试这个,看看它是否能够下载文件。
1赞 James A Cubeta 8/12/2022
@CuriousMind 不幸的是,我无法使用像 Postman 这样的工具。您的建议似乎是解决方案!我创建了一个直接指向 REST 服务端点的锚标记,它似乎完全稳定。我注意到在尝试写入文件时它似乎“暂停”了我的应用程序,但这与浏览器崩溃相比还不错。非常感谢您的建议!

答:

0赞 James A Cubeta 8/13/2022 #1

多亏了@CuriousMind,通过 HttpClient 删除我的 REST 调用并使用锚标记直接调用端点非常适合我的目的:

const a = document.createElement('a');
a.setAttribute('display', 'none');
a.href = downloadServiceUrl+"?id="+id";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);

不知道为什么会这样,但它确实有效。

另外,我注意到浏览器/应用程序在下载较大文件时会暂时冻结。也许网络工作者可以减轻这种副作用。

评论

0赞 CuriousMind 8/13/2022
棒。在我看来,问题似乎是别的什么。Web Worker 无法解决此问题。可能的罪魁祸首可能是防病毒软件、浏览器扩展程序,或者您正在 localhost 中运行可能正在消耗内存的应用程序。但同样,这些都是猜测。