2

I've got a server application that uses JAX-RS. I'm adding an endpoint to it which will retrieve an object (aka a file) from a S3 bucket, save it locally in a temp folder, serve up this file, and then delete it from the temp folder. So one of the methods I'm adding looks something like this:

@GET
@Path("/download-file")
public Response downloadFile() {

    File tempFile = null;
    try {
        tempFile = s3FileDownloader.downloadFile();
        Response response = Response.ok(tempFile)
                .header("Content-Disposition", "attachment; filename=myfile.dat")
                .header("Content-Type", "application/octet-stream")
                .build();
        return response;
    } finally {
        if (tempFile != null) {
            tempFile.delete();
        }
    }
}

However, that doesn't work, because it seems it's executing the delete() method in the finally block before it renders the file. When I hit the endpoint, I get a 500 error from Tomcat stating "the system cannot find the file specified."

If I change it to tempFile.deleteOnExit() then it works. However this is not ideal as the server is not meant to ever really exit, besides on reboots which occur very infrequently. How can I get it to render the output before it hits my delete statement? Or is there a better way of going about this?

UPDATE: Adding the code I have for my FileDownloader class below, as requested.

public class FileDownloader {

    private final AmazonS3Client s3Client;
    private final String bucketName;

    public FileDownloader(AmazonS3Client s3Client, String bucketName) {
        this.s3Client = s3Client;
        this.bucketName = bucketName;
    }

    public File downloadFile(String key) {
        InputStream inputStream = null;
        OutputStream outputStream = null;
        try {
            S3Object data = s3Client.getObject(bucketName, key);
            inputStream = data.getObjectContent();
            File file = File.createTempFile(System.getProperty("java.io.tmpdr"), "." + FileNameUtils.getExtension(key));
            outputStream = new FileOutputStream(file);

            int read;
            byte[] bytes = new byte[1024];
            while ((read = inputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, read);
            }

            return file;

        } catch (AmazonS3Exception|IOException ex) {
            // log the exception
            // throw a custom exception type instead
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
            } catch (IOException) { }
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException ex) { }
        }
    }
}
soapergem
  • 9,263
  • 18
  • 96
  • 152
  • I am highly skeptical that Jax-RS, or any of the raw Servlet based frameworks, is going to let you serve a file out to a client by just giving a File object. I think you need to take that File object, turn it into some kind of a Stream, grab the response Stream, and stream the bits through yourself, before tempFile.delete(). If you have an actual Temp file, you don't need to delete that either, I believe the JVM deletes on Exit – Bob Kuhar Apr 14 '17 at 21:44
  • Perhaps to check the Responser with getLength() or getStatus() with if, and then delete. The comment above will be recommended as well. – loadP Apr 14 '17 at 21:51
  • How large is the file? Can you post the documentation of S3FileDownloader? Because storing what you download to a file looks useless, if you want to delete the file anyway. – JB Nizet Apr 14 '17 at 22:30
  • @JBNizet sure thing, I've added the code. Are you suggesting that I can just pass in the InputStream to JAX-RS directly instead of saving it to a file? Also the files will vary in size; some will be small but there is a potential that some might be quite large (i.e. 20+ MB). – soapergem Apr 17 '17 at 14:49
  • Yes, AFAIK (and as far as I understand the spec), you can pass an InputStream to ok() directly. – JB Nizet Apr 17 '17 at 14:58

0 Answers0