You can use InputStreamResource to return stream result. I tested and it is started copying to output immediately.
@GetMapping(value = "download-single-report") public ResponseEntity<Resource> downloadSingleReport() { File dlFile = new File("some_path"); if (!dlFile.exists()) { return ResponseEntity.notFound().build(); } try { try (InputStream stream = new FileInputStream(dlFile)) { InputStreamResource streamResource = new InputStreamResource(stream); return ResponseEntity.ok() .contentType(MediaType.APPLICATION_PDF) .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + dlFile.getName() + "\"") .body(streamResource); } /* // FileSystemResource alternative FileSystemResource fileSystemResource = new FileSystemResource(dlFile); return ResponseEntity.ok() .contentType(MediaType.APPLICATION_PDF) .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + dlFile.getName() + "\"") .body(fileSystemResource); */ } catch (IOException e) { return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build(); } }
The second alternative is a partial download method.
@GetMapping(value = "download-single-report-partial") public void downloadSingleReportPartial(HttpServletRequest request, HttpServletResponse response) { File dlFile = new File("some_path"); if (!dlFile.exists()) { response.setStatus(HttpStatus.NOT_FOUND.value()); return; } try { writeRangeResource(request, response, dlFile); } catch (Exception ex) { response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); } } public static void writeRangeResource(HttpServletRequest request, HttpServletResponse response, File file) throws IOException { String range = request.getHeader("Range"); if (StringUtils.hasLength(range)) { //http ResourceRegion region = getResourceRegion(file, range); long start = region.getPosition(); long end = start + region.getCount() - 1; long resourceLength = region.getResource().contentLength(); end = Math.min(end, resourceLength - 1); long rangeLength = end - start + 1; response.setStatus(206); response.addHeader("Accept-Ranges", "bytes"); response.addHeader("Content-Range", String.format("bytes %s-%s/%s", start, end, resourceLength)); response.setContentLengthLong(rangeLength); try (OutputStream outputStream = response.getOutputStream()) { try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) { StreamUtils.copyRange(inputStream, outputStream, start, end); } } } else { response.setStatus(200); response.addHeader("Accept-Ranges", "bytes"); response.setContentLengthLong(file.length()); try (OutputStream outputStream = response.getOutputStream()) { try (InputStream inputStream = new BufferedInputStream(new FileInputStream(file))) { StreamUtils.copy(inputStream, outputStream); } } } } private static ResourceRegion getResourceRegion(File file, String range) { List<HttpRange> httpRanges = HttpRange.parseRanges(range); if (httpRanges.isEmpty()) { return new ResourceRegion(new FileSystemResource(file), 0, file.length()); } return httpRanges.get(0).toResourceRegion(new FileSystemResource(file)); }
Spring Framework Resource Response Process
Resource response managed by ResourceHttpMessageConverter class. In writeContent method, StreamUtils.copy is called.
package org.springframework.http.converter; public class ResourceHttpMessageConverter extends AbstractHttpMessageConverter<Resource> { .. protected void writeContent(Resource resource, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException { try { InputStream in = resource.getInputStream(); try { StreamUtils.copy(in, outputMessage.getBody()); } catch (NullPointerException ex) { // ignore, see SPR-13620 } finally { try { in.close(); } catch (Throwable ex) { // ignore, see SPR-12999 } } } catch (FileNotFoundException ex) { // ignore, see SPR-12999 } } }
out.write(buffer, 0, bytesRead); sends data immediately to output (I have tested on my local machine). When whole data is transferred, out.flush(); is called.
package org.springframework.util; public abstract class StreamUtils { .. public static int copy(InputStream in, OutputStream out) throws IOException { Assert.notNull(in, "No InputStream specified"); Assert.notNull(out, "No OutputStream specified"); int byteCount = 0; int bytesRead; for(byte[] buffer = new byte[4096]; (bytesRead = in.read(buffer)) != -1; byteCount += bytesRead) { out.write(buffer, 0, bytesRead); } out.flush(); return byteCount; } }