├── LICENSE
├── README.md
├── pom.xml
└── src
└── main
├── java
└── com
│ └── techshard
│ └── download
│ ├── AsyncConfiguration.java
│ ├── DownloadApplication.java
│ └── controller
│ └── DownloadController.java
└── resources
└── application.yml
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Swathi Prasad
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # download-using-streaming-response-body
2 | An example for streaming large files in chunks using StreamingResponseBody in Spring MVC
3 |
4 | Related Blog: https://techshard.com/2019/06/30/streaming-data-with-spring-boot-restful-web-service/
5 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.techshard.streamingresponse
8 | springboot-download
9 | 1.0-SNAPSHOT
10 |
11 |
12 | org.springframework.boot
13 | spring-boot-starter-parent
14 | 2.1.4.RELEASE
15 |
16 |
17 |
18 |
19 |
20 | org.springframework.boot
21 | spring-boot-starter-web
22 |
23 |
24 | org.springframework.boot
25 | spring-boot-starter-actuator
26 |
27 |
28 | org.springframework.boot
29 | spring-boot-starter-security
30 |
31 |
32 | org.slf4j
33 | slf4j-api
34 |
35 |
36 | org.apache.commons
37 | commons-io
38 | 1.3.2
39 |
40 |
41 |
42 |
43 |
44 |
45 | org.springframework.boot
46 | spring-boot-maven-plugin
47 |
48 |
49 | springboot-download
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/main/java/com/techshard/download/AsyncConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.techshard.download;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
6 | import org.springframework.aop.interceptor.SimpleAsyncUncaughtExceptionHandler;
7 | import org.springframework.context.annotation.Bean;
8 | import org.springframework.context.annotation.Configuration;
9 | import org.springframework.core.task.AsyncTaskExecutor;
10 | import org.springframework.scheduling.annotation.AsyncConfigurer;
11 | import org.springframework.scheduling.annotation.EnableAsync;
12 | import org.springframework.scheduling.annotation.EnableScheduling;
13 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
14 | import org.springframework.web.context.request.NativeWebRequest;
15 | import org.springframework.web.context.request.async.CallableProcessingInterceptor;
16 | import org.springframework.web.context.request.async.TimeoutCallableProcessingInterceptor;
17 | import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
18 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
19 |
20 | import java.util.concurrent.Callable;
21 |
22 | @Configuration
23 | @EnableAsync
24 | @EnableScheduling
25 | public class AsyncConfiguration implements AsyncConfigurer {
26 |
27 | private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);
28 |
29 | @Override
30 | @Bean (name = "taskExecutor")
31 | public AsyncTaskExecutor getAsyncExecutor() {
32 | log.debug("Creating Async Task Executor");
33 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
34 | executor.setCorePoolSize(5);
35 | executor.setMaxPoolSize(10);
36 | executor.setQueueCapacity(25);
37 | return executor;
38 | }
39 |
40 | @Override
41 | public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
42 | return new SimpleAsyncUncaughtExceptionHandler();
43 | }
44 |
45 | /** Configure async support for Spring MVC. */
46 | @Bean
47 | public WebMvcConfigurer webMvcConfigurerConfigurer(AsyncTaskExecutor taskExecutor, CallableProcessingInterceptor callableProcessingInterceptor) {
48 | return new WebMvcConfigurer() {
49 | @Override
50 | public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
51 | configurer.setDefaultTimeout(360000).setTaskExecutor(taskExecutor);
52 | configurer.registerCallableInterceptors(callableProcessingInterceptor);
53 | WebMvcConfigurer.super.configureAsyncSupport(configurer);
54 | }
55 | };
56 | }
57 |
58 | @Bean
59 | public CallableProcessingInterceptor callableProcessingInterceptor() {
60 | return new TimeoutCallableProcessingInterceptor() {
61 | @Override
62 | public Object handleTimeout(NativeWebRequest request, Callable task) throws Exception {
63 | log.error("timeout!");
64 | return super.handleTimeout(request, task);
65 | }
66 | };
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/main/java/com/techshard/download/DownloadApplication.java:
--------------------------------------------------------------------------------
1 | package com.techshard.download;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
6 | import org.springframework.scheduling.annotation.EnableAsync;
7 |
8 | @SpringBootApplication
9 | public class DownloadApplication extends SpringBootServletInitializer {
10 |
11 | public static void main(String[] args) {
12 | SpringApplication.run(DownloadApplication.class, args);
13 |
14 | }
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/src/main/java/com/techshard/download/controller/DownloadController.java:
--------------------------------------------------------------------------------
1 | package com.techshard.download.controller;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.http.HttpStatus;
6 | import org.springframework.http.MediaType;
7 | import org.springframework.http.ResponseEntity;
8 | import org.springframework.web.bind.annotation.GetMapping;
9 | import org.springframework.web.bind.annotation.RequestMapping;
10 | import org.springframework.web.bind.annotation.RestController;
11 | import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
12 |
13 | import javax.servlet.http.HttpServletResponse;
14 | import java.io.*;
15 | import java.util.zip.ZipEntry;
16 | import java.util.zip.ZipOutputStream;
17 |
18 | @RestController
19 | @RequestMapping ("/api")
20 | public class DownloadController {
21 |
22 | private final Logger logger = LoggerFactory.getLogger(DownloadController.class);
23 |
24 | @GetMapping (value = "/download", produces = MediaType.APPLICATION_JSON_VALUE)
25 | public ResponseEntity download(final HttpServletResponse response) {
26 |
27 | response.setContentType("application/zip");
28 | response.setHeader(
29 | "Content-Disposition",
30 | "attachment;filename=sample.zip");
31 |
32 | StreamingResponseBody stream = out -> {
33 |
34 | final String home = System.getProperty("user.home");
35 | final File directory = new File(home + File.separator + "Documents" + File.separator + "sample");
36 | final ZipOutputStream zipOut = new ZipOutputStream(response.getOutputStream());
37 |
38 | if(directory.exists() && directory.isDirectory()) {
39 | try {
40 | for (final File file : directory.listFiles()) {
41 | final InputStream inputStream=new FileInputStream(file);
42 | final ZipEntry zipEntry=new ZipEntry(file.getName());
43 | zipOut.putNextEntry(zipEntry);
44 | byte[] bytes=new byte[1024];
45 | int length;
46 | while ((length=inputStream.read(bytes)) >= 0) {
47 | zipOut.write(bytes, 0, length);
48 | }
49 | inputStream.close();
50 | }
51 | zipOut.close();
52 | } catch (final IOException e) {
53 | logger.error("Exception while reading and streaming data {} ", e);
54 | }
55 | }
56 | };
57 | logger.info("steaming response {} ", stream);
58 | return new ResponseEntity(stream, HttpStatus.OK);
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/src/main/resources/application.yml:
--------------------------------------------------------------------------------
1 | spring:
2 | security:
3 | user:
4 | name: admin
5 | password: admin
6 |
--------------------------------------------------------------------------------