0

I have set up a simple front- & backend project for browsing & downloading files.

My backend server is written in java and the frontend is an angular application.

The backend has a path to a directory which it exposes to the frontend. The use can then use the frontend to navigate through the directories which are exposed by my backend.

Now I have set up a simple file download, which works fine without any problems EXCEPT when I try to download a jar-archive from the backend. When I try to download a .jar file, the downloaded file ends up being way larger than expected. For example: I want to download a 18mb .jar file and it ends up downloading a 30mb .jar file instead.

I'm pretty much at the end of my wits when it comes to this problem, as I'm still new to the Frontend & web environment, please keep that in mind. Thank you.

What makes this Problem even more confusing for me is that my response-header shows the correct Content length and the correct amount of transferred bytes, matching my expected file-size (see attached images). I also tried to apply the "application/java-archive" MediaType to both my Request and Response headers but this didn't fix the problem.

I hope i provided most of the Information needed to fix this, but just in case, here is the relevant code:

Backend: `

/** Snippet by @fateddy - https://stackoverflow.com/a/35683261
 *
 * @param path the path to the file as a String
 * @return a resource representing the file to download
 */
@GetMapping("/browsable/download")
public  ResponseEntity<Resource> getFile(@RequestParam (required = true) String path) {
   String pathToFile = ROOT_PATH + File.separator + path;
   Path pathTMP = Paths.get(pathToFile); // Temporary Path-Object
   File file = new File(pathToFile);
   HttpHeaders headers = new HttpHeaders();
   MediaType mediatype = MediaType.APPLICATION_OCTET_STREAM;

   if (resolveFileExtension(file).equals(".jar")){ // custom mediaType for .jar files
      mediatype = MediaType.parseMediaType("application/java-archive");
   }
   try {
      ByteArrayResource resource = new ByteArrayResource(Files.readAllBytes(pathTMP));
      return ResponseEntity.ok()
            .headers(headers)
            .contentLength(file.length())
            .contentType(mediatype)
            .body(resource);
   } catch (IOException e) {
      System.err.println("Something went wrong");
      throw new RuntimeException(e);
   }
}

`

which is called by my frontend code: `

  /**
   * The wrapper function for Requesting a file Download
   * Called by the HTMLElements of the "Files"-Part of the filebrowser
   * @param fileClicked the file which is to be downloaded
   */
  requestFile(fileClicked: string) {
    if (this.currentURL === '') {
      var url = fileClicked;
    } else {
      var url = this.currentURL + this.pathSeperator + fileClicked; // The full URL of the Child directory
    }
    this.dl_filename = fileClicked;
    this.mediaType = 'application/octet-stream';
    if (this.resolveFileExtension(fileClicked) == '.jar') {
      this.mediaType = 'application/java-archive'; // apply custom mediaType for .jar files
    }
    this.http
      .downloadFile(url, this.mediaType)
      .subscribe(this.observerDOWNLOAD); // Call the Server and download the file -> calls download() inside the Observer
  }

then in the httpService:

  /**
   * Calls the /download Endpoint to request a download of the given file
   * @param filePath The relative path to the file which was requested for Download
   * @returns An Observable containing the response of the Server (aka. the File as an Observable)
   */
  downloadFile(filePath: string, mediaType: string) {
    var httpOptions = {};
    httpOptions = {
      params: new HttpParams().set('path', filePath),
      responseType: mediaType
    };
    const url = `${environment.serverUrl}/browsable/download`;
    const result = this.http.get(url, httpOptions);
    return result;
  }
}

`

and finally downloaded: `

  /**
   * Downloads the given file inside a newly-created, hidden, a-tag-HTMLElement, which is destroyed afterwards
   * Called by the Observer responsible for downloads
   * @param response an Observable representing the File
   */
  download(response: any) {
    // Create a blob representing the File - https://stackblitz.com/edit/angular-blob-file-download?file=app%2Fapp.component.ts for more info
    // "Blobs represent data that isn't necessarily in a JavaScript-native format."
    const blob = new Blob([response], { type: this.mediaType });
    const url = window.URL.createObjectURL(blob);
    /** Workaround for FileDownload | Snippet customized from @Kol - https://stackoverflow.com/a/19328891
      1 - Create a hidden <a> tag.
      2 - Set its href attribute to the blob's URL.
      3 - Set its download attribute to the filename.
      4 - Click on the <a> tag.
       */
    var a = document.createElement('a');
    document.body.appendChild(a);
    a.className = 'hidden';
    a.href = url;
    a.download = this.dl_filename;
    a.click();
    window.URL.revokeObjectURL(url);
    a.remove();
  }

`

And here is the picture of my request & response: (here the resulting jar file is ~30mb) GET-request

request & response

Thomas
  • 87,414
  • 12
  • 119
  • 157
  • I'm not sure why you are creating an a-tag, click and destroy it again instead of just executing an ajax request but I'm no Angular expert so there might be a reason for this. As for the download: Did you try calling the download url directly in your browser and did that work? Did you try with an arbitrary binary media type instead of "java-archive" and maybe also without any extension? Jar-files are essentially zip-files so there might be some deflating going on. Also, did you try and do spot comparisons of the source and downloaded files, e.g. via some hex viewer? – Thomas Jan 03 '23 at 10:56
  • A for the a-tag thing, I originally had the problem with naming the files so i used this as a workaround to set the filename via the download attribute. I tried calling the url directly and the resulting file, tho unnamed, has the correct size and when i rename it to it's intended name, everything seems to work. I'll look into this a bit more but this seems to be a possible fix for this problem. Thank you very much – Sven Eckert Jan 03 '23 at 12:33
  • 1
    It seems the problem was cause by the httpClient's get request. For some reason my response always got parsed into json and then back into an object, this was the cause of the problem. I made sure to use an overload of the httpclient's get request which uses the responseType 'blob' and it works just fine now. I also checked the checksum of the files which are now perfectly matching. I think this question can be closed. Thank you very much for the great response – Sven Eckert Jan 03 '23 at 14:00

0 Answers0