Search code examples
javascriptjavarestjerseyzip

How to send a large file from server using Jersey?


I am trying to download and save a large zip file. The zip file is possibly larger than the heap, so I want to use a stream to avoid java.lang.OutOfMemoryError: Java heap space error.

Also, the large zip file is generated on request, so I would like to delete the file after downloading it.

My current code is

@POST
@Path("/downloadLargeZip")
public Response largeZip() throws FileNotFoundException {
    File file = generateZipFile(); // generates zip file successfully
    FileInputStream input = new FileInputStream(file);
    StreamingOutput so = os -> {
        try {
            int n;
            byte[] buffer = new byte[1024];
            while ((n = input.read(buffer)) >= 0) {
                os.write(buffer, 0, n);
            }
            os.flush();
            os.close();
        } catch (Exception e) {
            throw new WebApplicationException(e);
        }
    };
    return Response.ok(so).build();
}

My current client-side code is

import { saveAs } from 'browser-filesaver/FileSaver.js';

save() {
    this.http.post<any>('url', '', { observe: 'response', responseType: 'blob'})
      .subscribe(res => {
        this.downloadFile(res);
    });
}

downloadFile(response: any) {
    const contentDisposition = 'attachment; filename="KNOWN_FILE_NAME"'; // response.headers('content-disposition'); - response object has no headers
    // Retrieve file name from content-disposition
    let fileName = contentDisposition.substr(contentDisposition.indexOf('filename=') + 9);
    fileName = fileName.replace(/\"/g, '');
    const contentType = 'application/zip'; // response.headers('content-type');
    const blob = new Blob([response.data], { type: contentType });
    saveAs(blob, fileName);
}

I have a few problems with my code:

  1. Using dev tools to check the response, it has no headers (normalizedNames is a map with no entries) or data.
  2. Checking the saved zip file, I can't open it using WinRAR. The error is The archive is either in unknown format or damaged.
  3. Trying to open the zip file with Notepad++, the content is the text undefined.

The JSON representation of the response is

{
    "headers":{
        "normalizedNames":{  
        },
       "lazyUpdate":null
    },
    "status":200,
    "statusText":"OK",
    "url":"URL",
    "ok":true,
    "type":4,
    "body":{ 
    }
}

Although the body does contain data {size: 2501157, type: "application/json"}. Please ignore the number (I am guessing it's the zip file size in bytes, the actual file will be much larger).

What am I doing wrong? How can I read the stream and save the generated zip file? I think the issue is in my downloadFile function, but I don't know what to change there.

Any help would be appreciated.


Solution

  • I needed to completely change the way I approached the issue.

    The server will now generate the file and return a URI for the client. The client will then download the file via given URI.

    Server code

    @POST
    @Path("/create")
    public Response createLogs(String data) {
        String fileName = generateFileAndReturnName(data);
        if (fileName != null) {
            return Response.created(URI.create(manipulateUri(fileName))).build();
        }
        return Response.status(500).build();
    }
    

    Client code

    save() {
      this.http.post<any>(this.baseUrl + '/create', this.data, { observe: 'response'}).subscribe(postResponse => {
        if (postResponse.status !== 201) {
          this.logger.error('Failed.');
          return;
        }
        postResponse.headers.keys(); // lazy init headers
        const uri = postResponse.headers.get('location');
        if (!uri) {
          this.logger.error('URI not present.');
          return;
        }
        const link = document.createElement('a');
        link.href = uri;
        link.setAttribute('download', 'fileName.fileExtension');
        document.body.appendChild(link);
        link.click();
        if (link.parentNode) {
          link.parentNode.removeChild(link);
        }
      });
    }
    

    Working fine now with 40GB file (32GB RAM, so file is definitely bigger than any allocated heap).