├── 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 | --------------------------------------------------------------------------------