3

I have a servlet that offers a CSV file for download:

@RestController 
@RequestMapping("/")
public class FileController {

    @RequestMapping(value = "/export", method = RequestMethod.GET)
    public FileSystemResource getFile() {
        return new FileSystemResource("c:\file.csv"); 
    }
}

This works just fine.

Question: how can I offer this file as compressed file? (zip, gzip, tar doesn't matter)?

membersound
  • 81,582
  • 193
  • 585
  • 1,120
  • Do you want to create a zip file on the server or use the "on the fly gzip compression" between webserver and client-browser? – reto Apr 28 '16 at 08:59
  • I want to create a compressed file (from the uncompressed local file). If it's `zip` or `gzip` doesn't matter. – membersound Apr 28 '16 at 09:02
  • See http://stackoverflow.com/questions/26113345/compress-dynamic-content-to-servletoutputstream . The same trick would work with a plain controller. – M. Deinum Apr 28 '16 at 09:17

3 Answers3

11

Based on the solution here (for a plain Servlet), you can also do the same with a Spring MVC based controller.

@RequestMapping(value = "/export", method = RequestMethod.GET)
public void getFile(OutputStream out) {
    FileSystemResource resource = new FileSystemResource("c:\file.csv"); 
    try (ZipOutputStream zippedOut = new ZipOutputStream(out)) {
        ZipEntry e = new ZipEntry(resource.getName());
        // Configure the zip entry, the properties of the file
        e.setSize(resource.contentLength());
        e.setTime(System.currentTimeMillis());
        // etc.
        zippedOut.putNextEntry(e);
        // And the content of the resource:
        StreamUtils.copy(resource.getInputStream(), zippedOut);
        zippedOut.closeEntry();
        zippedOut.finish();
    } catch (Exception e) {
        // Do something with Exception
    }        
}

You created a ZipOutputStream based on the responses OutputStream (which you can simply have injected into the method). Then create an entry for the zipped out stream and write it.

Instead of the OutputStream you could also wire the HttpServletResponse so that you would be able to set the name of the file and the content type.

@RequestMapping(value = "/export", method = RequestMethod.GET)
public void getFile(HttpServletResponse response) {
    FileSystemResource resource = new FileSystemResource("c:\file.csv"); 
    response.setContentType("application/zip");
    response.setHeader("Content-Disposition", "attachment; filename=file.zip");

    try (ZipOutputStream zippedOut = new ZipOutputStream(response.getOutputStream())) {
        ZipEntry e = new ZipEntry(resource.getName());
        // Configure the zip entry, the properties of the file
        e.setSize(resource.contentLength());
        e.setTime(System.currentTimeMillis());
        // etc.
        zippedOut.putNextEntry(e);
        // And the content of the resource:
        StreamUtils.copy(resource.getInputStream(), zippedOut);
        zippedOut.closeEntry();
        zippedOut.finish();
    } catch (Exception e) {
        // Do something with Exception
    }        
}
Tuom
  • 596
  • 5
  • 19
M. Deinum
  • 115,695
  • 22
  • 220
  • 224
  • What would the return type of the method be? `ZipOutputStream`? Would the same be possible with `GZIPOutputStream`? – membersound Apr 28 '16 at 09:45
  • I tried `return zippedOUt`, but gave me: `java.lang.IllegalArgumentException: No converter found for return value of type: class java.util.zip.ZipOutputStream`. When adding `produces = "application/zip"`, it results in `org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation`. – membersound Apr 28 '16 at 09:48
  • There would be no return... Hence the `void`, I wrote it like that for a reason! – M. Deinum Apr 28 '16 at 09:51
  • Sorry, I missed that point. Could I also set the target filename (like `file.csv.zip`) into the `OutputStream`? I didn't know it's possible to just use the `OutputStream` as a method parameter. A very keen solution, ty! – membersound Apr 28 '16 at 09:54
  • 1
    See that last part of the question... I covered that already briefly (modified it to include a sample as well). – M. Deinum Apr 28 '16 at 09:56
  • Solved! See this page if you need generic solution for any case: https://stackoverflow.com/a/75595252/15959923 – Mavic More Feb 28 '23 at 17:14
0

Untested but something like that should work:

final Path zipTmpPath = Paths.get("C:/file.csv.zip");
final ZipOutputStream zipOut = new ZipOutputStream(Files.newOutputStream(zipTmpPath, StandardOpenOption.WRITE));
final ZipEntry zipEntry = new ZipEntry("file.csv");
zipOut.putNextEntry(zipEntry);
Path csvPath = Paths.get("C:/file.csv");
List<String> lines = Files.readAllLines(csvPath);
for(String line : lines)
{
    for(char c : line.toCharArray())
    {
        zipOut.write(c);
    }
}
zipOut.flush();
zipOut.close();
return new FileSystemResource("C:/file.csv.zip");
M. Deinum
  • 115,695
  • 22
  • 220
  • 224
specializt
  • 1,913
  • 15
  • 26
  • I thought there could be a simpler way than just having to create an additional local file. Eg returning a csv file is a simple as returning a `FileSystemResource` with spring. Thought there might be also something for compression. ot: I have 27 gold points though not 11k ;) – membersound Apr 28 '16 at 09:11
  • oh ... sorry, my mistake. – specializt Apr 28 '16 at 09:14
-3

use this :

@RequestMapping(value = "/zip", produces="application/zip")

This may solve your issue

rounak
  • 15
  • 1
    ... why on earth does everyone suddenly think that spring is a no-brain framework with magic superpowers?? Spring doesnt compress automatically - there **may** be some sort of plugin or special configuration for that but .... – specializt Apr 28 '16 at 09:14