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;
}
}