java - 从客户端浏览器 (REST) 上的服务器返回一个 zip(或任何文件)

标签 java angular rest download zip

所以我在我的服务器上使用 Java,在客户端上使用 Angular。我目前正在开发一项功能,您可以从表格中选择多个文件,当您按下下载时,它会生成一个 zip 文件并将其下载到您的浏览器。截至目前,服务器现在创建了 zip 文件,我可以在服务器文件中访问它。剩下要做的就是让它在客户端的浏览器上下载。 (zip文件在客户端下载后被删除)

经过一些研究,我发现您可以使用 fileOutputStream 来执行此操作。我还看到了一些类似改造的工具......我正在使用 REST,这就是我的代码的样子。我将如何尽可能简单地实现我的目标?

Angular

  httpGetDownloadZip(target: string[]): Observable<ServerAnswer> {
    const params = new HttpParams().set('target', String(target)).set('numberOfFiles', String(target.length));
    const headers = new HttpHeaders().set('token', this.tokenService.getStorageToken());
    const options = {
      headers,
      params,
    };
    return this.http
      .get<ServerAnswer>(this.BASE_URL + '/files/downloadZip', options)
      .pipe(catchError(this.handleError<ServerAnswer>('httpGetZip')));
  }

Java压缩方法

    public void getDownloadZip(String[] files, String folderName) throws IOException {
        [...] // The method is huge but basically I generate a folder called "Download/" in the server

        // Zipping the "Download/" folder
        ZipUtil.pack(new File("Download"), new File("selected-files.zip"));

        // what do I return ???
        return;
    }

Java 上下文

            server.createContext("/files/downloadZip", new HttpHandler() {

                @Override
                public void handle(HttpExchange exchange) throws IOException {
                    if (!handleTokenPreflight(exchange)) { return; }
                    System.out.println(exchange.getRequestURI());
                    Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());

                    String authToken = exchange.getRequestHeaders().getFirst("token");
                    String target = queryParam.get("target") + ",";
                    String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];

[...] // I process the data in this entire method and send it to the previous method that creates a zip

                    Controller.getDownloadZip(files, folderName);

                    // what do I return to download the file on the client's browser ????
                    return;
                }
            });

最佳答案

成功下载 zip 文件的可能方法如下文所述。

首先,考虑在您的 downloadZip 方法中返回对作为压缩结果获得的 zip 文件的引用:

public File getDownloadZip(String[] files, String folderName) throws IOException {
  [...] // The method is huge but basically I generate a folder called "Download/" in the server

  // Zipping the "Download/" folder
  File selectedFilesZipFile = new File("selected-files.zip")
  ZipUtil.pack(new File("Download"), selectedFilesZipFile);

  // return the zipped file obtained as result of the previous operation
  return selectedFilesZipFile;
}

现在,修改您的 HttpHandler 以执行下载:

server.createContext("/files/downloadZip", new HttpHandler() {

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        if (!handleTokenPreflight(exchange)) { return; }
        System.out.println(exchange.getRequestURI());
        Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());

        String authToken = exchange.getRequestHeaders().getFirst("token");
        String target = queryParam.get("target") + ",";
        String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];

    [...] // I process the data in this entire method and send it to the previous method that creates a zip

        // Get a reference to the zipped file
        File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);

        // Set the appropiate Content-Type
        exchange.getResponseHeaders().set("Content-Type", "application/zip");

        // Optionally, if the file is downloaded in an anchor, set the appropiate content disposition
        // exchange.getResponseHeaders().add("Content-Disposition", "attachment; filename=selected-files.zip");
        
        // Download the file. I used java.nio.Files to copy the file contents, but please, feel free
        // to use other option like java.io or the Commons-IO library, for instance
        exchange.sendResponseHeaders(200, selectedFilesZipFile.length());
        try (OutputStream responseBody = httpExchange.getResponseBody()) {
            Files.copy(selectedFilesZipFile.toPath(), responseBody);
            responseBody.flush();
        }
    }
});

现在的问题是如何处理Angular中的下载。

如前面代码中所建议的,如果资源是公共(public)的,或者您有办法管理您的安全 token ,例如,将其作为参数包含在 URL 中,一种可能的解决方案是不使用 Angular HttpClient 但带有 href 的 anchor 直接指向您曾经的后端处理程序方法。

如果您需要使用 Angular HttpClient,也许要包含您的身份验证 token ,那么您可以尝试这个很棒的 SO question 中提出的方法.

首先,在您的 handler 中,将压缩文件内容编码为 Base64 以简化字节处理任务(在一般用例中,您通常可以从服务器返回一个包含文件的 JSON 对象内容和描述该内容的元数据,如内容类型等):

server.createContext("/files/downloadZip", new HttpHandler() {

    @Override
    public void handle(HttpExchange exchange) throws IOException {
        if (!handleTokenPreflight(exchange)) { return; }
        System.out.println(exchange.getRequestURI());
        Map<String, String> queryParam = parseQueryParam(exchange.getRequestURI().getQuery());

        String authToken = exchange.getRequestHeaders().getFirst("token");
        String target = queryParam.get("target") + ",";
        String[] files = new String[Integer.parseInt(queryParam.get("numberOfFiles"))];

    [...] // I process the data in this entire method and send it to the previous method that creates a zip

        // Get a reference to the zipped file
        File selectedFilesZipFile = Controller.getDownloadZip(files, folderName);

        // Set the appropiate Content-Type
        exchange.getResponseHeaders().set("Content-Type", "application/zip");

        // Download the file
        byte[] fileContent = Files.readAllBytes(selectedFilesZipFile.toPath());
        byte[] base64Data = Base64.getEncoder().encode(fileContent);
        exchange.sendResponseHeaders(200, base64Data.length);
        try (OutputStream responseBody = httpExchange.getResponseBody()) {
            // Here I am using Commons-IO IOUtils: again, please, feel free to use other alternatives for writing 
            // the base64 data to the response outputstream
            IOUtils.write(base64Data, responseBody);
            responseBody.flush();
        }
    }
});

之后,在您的客户端 Angular 组件中使用以下代码来执行下载:

this.downloadService.httpGetDownloadZip(['target1','target2']).pipe(
  tap((b64Data) => {
    const blob = this.b64toBlob(b64Data, 'application/zip');
    const blobUrl = URL.createObjectURL(blob);
    window.open(blobUrl);
  })
).subscribe()

如上述问题所示,b64toBlob 将如下所示:

private b64toBlob(b64Data: string, contentType = '', sliceSize = 512) {
  const byteCharacters = atob(b64Data);
  const byteArrays = [];

  for (let offset = 0; offset < byteCharacters.length; offset += sliceSize) {
    const slice = byteCharacters.slice(offset, offset + sliceSize);

    const byteNumbers = new Array(slice.length);
    for (let i = 0; i < slice.length; i++) {
      byteNumbers[i] = slice.charCodeAt(i);
    }

    const byteArray = new Uint8Array(byteNumbers);
    byteArrays.push(byteArray);
  }

  const blob = new Blob(byteArrays, {type: contentType});
  return blob;
}

您可能需要稍微修改服务中的 httpGetDownloadZip 方法以考虑返回的 base 64 数据 - 基本上,将 ServerAnswer 更改为 string 作为返回信息类型:

httpGetDownloadZip(target: string[]): Observable<string> {
    const params = new HttpParams().set('target', String(target)).set('numberOfFiles', String(target.length));
    const headers = new HttpHeaders().set('token', this.tokenService.getStorageToken());
    const options = {
      headers,
      params,
    };
    return this.http
      .get<string>(this.BASE_URL + '/files/downloadZip', options)
      .pipe(catchError(this.handleError<ServerAnswer>('httpGetZip')));
}

关于java - 从客户端浏览器 (REST) 上的服务器返回一个 zip(或任何文件),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69367700/

相关文章:

java - 更改WSO2 IS证书导致登录失败

angular - 如何使用 TypeScript 和 ionic Bootstrap 进行 ng-click?

java - 使用 Angular 2 前端在 Heroku Java 应用程序上部署

javascript - 如何从 Nodejs 后端的 JSON 填充 React 数组

rest - 使用Grails Spring Security Rest插件时如何获取客户端登录失败的原因?

java - 如何使对象能够被垃圾回收?

java - 如何将 HTTPRequest 从一个 Controller 类重定向到另一 Controller 类?

java - 对 Paypal 中某些 IPN 消息的无效响应

typescript - 将 ngModel 与自定义组件一起使用

python - 在 Django REST 框架 API 根中包含 list_route 方法