4

I'm working on a spring boot project with thymeleaf and I need to create a file and put some lines on it then send it for the user to download it.

@PostMapping("/filegenerator")
public String createFile(@ModelAttribute("page") Page page, Model model) {
    List<String> lines = new ArrayList<String>();

    //Some code ..........

    lines.forEach(l->{
        System.out.println(l);
    });

    //Here I need to create a file with the List of lines

    //And also put some code to download it        

    return "filegenerator";
}
Sarah Abouyassine
  • 169
  • 1
  • 2
  • 13
  • please share more info. What is this file (text file? image? pdf?) ? is it downloadable only by this one single user? is there any time limit after which the file should no longer be downloadable (perhaps after 1h keeping this file would just be a waste of disk space?) ? Are you running your application in some cloud or have other kind of access to object buckets? – Kamil Janowski May 30 '20 at 11:32
  • Thanks for your comment.. the type of the file is a sql script(.sql), yes is downloadable only by this user, he will fill a form and by submiting he would get the file for the download. is just local and instant I don't need cloud or storage – Sarah Abouyassine May 30 '20 at 12:35

2 Answers2

3

So if you want to return a file, you probably want to stream it to limit the amount of memory used (or at least that was probably the reasoning of Spring Framework creators). In your case I understand that the file is fairly small and doesn't really have to be persisted anywhere. It's just a one time download based on the uploaded form, right?

so this approach worked on my PC:

@PostMapping("/filegenerator")
public void createFile(HttpServletResponse response) throws IOException {
    List<String> lines = Arrays.asList("line1", "line2");
    InputStream is = new ByteArrayInputStream(lines.stream().collect(Collectors.joining("\n")).getBytes());
    IOUtils.copy(is, response.getOutputStream());
    response.setContentType("application/sql");
    response.setHeader("Content-Disposition", "attachment; filename=\"myquery.sql\"");
    response.flushBuffer();
}

note the content-disposition header. It explicitly states that you don't want to display the file in the browser, but instead you want it to be downloaded as a file and myquery.sql is the name of that file that will be downloaded.

Kamil Janowski
  • 1,872
  • 2
  • 21
  • 43
  • Yeees it works perfectly as I want .. However it gives an Exception `java.lang.IllegalStateException: getOutputStream() has already been called for this response` .. is there anything to do for it ? – Sarah Abouyassine May 30 '20 at 13:44
  • I'm afraid I cannot help you with that. I don't get that exception. I would have to see more code like, what the final version of this function looks now? – Kamil Janowski May 30 '20 at 13:48
1

@Kamil janowski

This is how it looks like now

@PostMapping("/filegenerator")
public String createFile(@ModelAttribute("page") Page page, Model model,HttpServletResponse response) throws IOException{

    List<String> lines = new ArrayList<String>();
    if(page.getTables().get(0).getName()!="") {
    List<String> output = so.createTable(page.getTables());
    output.forEach(line -> lines.add(line));
    }
    if(page.getInserttables().get(0).getName()!="") {
        List<String> output = so.insert(page.getInserttables());
        output.forEach(line -> lines.add(line));
    }
    if(page.getUpdatetables().get(0).getName()!="") {
        List<String> output = so.update(page.getUpdatetables());
        output.forEach(line -> lines.add(line));
    }

    lines.forEach(l->{
        System.out.println(l);
    });

    InputStream is = new ByteArrayInputStream(lines.stream().collect(Collectors.joining("\n")).getBytes());
    IOUtils.copy(is, response.getOutputStream());
    response.setContentType("application/sql");
    response.setHeader("Content-Disposition", "attachment; filename=\"myquery.sql\"");
    response.flushBuffer();

    model.addAttribute("page", getPage());
    return "filegenerator";
}       
Sarah Abouyassine
  • 169
  • 1
  • 2
  • 13
  • it's probably because of the return type? You cannot return both a file and a website. Every method has only 1 return type. On that note you can add the Location header to redirect the user to a different website – Kamil Janowski May 30 '20 at 14:00
  • Ahhh OK I ll try to do that .. Thank yoou very much for saving my day – Sarah Abouyassine May 30 '20 at 14:05