With the release of Spring 4.2 version, Three new classes have been introduced to handle Requests Asynchronously of the Servlet Thread. Which are;
  1. ResponseBodyEmitter
  2. SseEmitter
  3. StreamingResponseBody
ResponseBodyEmitter enables to send DeferredResult with a compatible HttpMessageConverter. Results can be emitted from threads which are not necessarily the Servlet Request Thread of the Servlet Container.
 
SseEmitter is used to send Server Sent Events to the Client. Server Sent Events has a fixed format and the response type for the result will be text/event-stream.
 
StreamingResponseBody is used to send raw unformatted data such as bytes to the client asynchronously of the Servlet Thread.
 
ResponseBodyEmitter and SseEmitter has a method named complete to mark its completion and StreamingResponseBody will complete when there is no more data to send. 
 
All three options will be keeping alive a connection to the endpoint until the end of the request.
 
StreamingResponseBody is particularly useful for streaming large files such as Media Files as writing of the bytes to the Response's OutputStream will be done asynchronously. StreamingResponseBody has a writeTo(OutputStream os) call back method which needs to be overridden inorder to support streaming.
 
I wrote a small Spring Boot Application to showcase the StreamingResponseBody capabilities in terms of Streaming large files. The application source code can be found at www.github.com/shazin/itube. Below is a screen shot of the application.
In order to send the Video files streaming to the Projekktor player in the web page following code snippet is used.
@RequestMapping(method = RequestMethod.GET, value = "/{video:.+}")
public StreamingResponseBody stream(@PathVariable String video)
throws FileNotFoundException {
File videoFile = videos.get(video);
final InputStream videoFileStream = new FileInputStream(videoFile);
return (os) -> {
readAndWrite(videoFileStream, os);
};
}

And a Custom Web Configuration to over ride default timeout behavior to no timeout and finally configuring an AsyncTaskExecutor

@Configuration
public static class WebConfig extends WebMvcConfigurerAdapter { @Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
configurer.setDefaultTimeout(-1);
configurer.setTaskExecutor(asyncTaskExecutor());
} @Bean
public AsyncTaskExecutor asyncTaskExecutor() {
return new SimpleAsyncTaskExecutor("async");
} }

转载自:http://shazsterblog.blogspot.com/2016/02/asynchronous-streaming-request.html

笔者代码:

    @RequestMapping(value = "{uuid}.m3u8")
public ResponseEntity<StreamingResponseBody> m3u8Generator(@PathVariable("uuid") String uuid){ String key = "media.".concat(uuid);
Map<String, Object> cached = cacheService.getCacheMap(key);
if(CollectionUtils.isEmpty(cached))
{
return new ResponseEntity(null, HttpStatus.OK);
}
String playlist = (String) cached.get("playlist");
String[] lines = playlist.split("\n"); //人为在每个MPEG-2 transport stream文件前面加上一个地址前缀
StringBuffer buffer = new StringBuffer(); StreamingResponseBody responseBody = new StreamingResponseBody() {
@Override
public void writeTo (OutputStream out) throws IOException {
for(int i = 0; i < lines.length; i++)
{
String line = lines[i]; if(line.endsWith(".ts"))
{
buffer.append("/streaming/");
buffer.append(uuid);
buffer.append("/");
buffer.append(line);
}else {
buffer.append(line);
}
buffer.append("\r\n");
}
out.write(buffer.toString().getBytes());
out.flush();
}
}; return new ResponseEntity(responseBody, HttpStatus.OK);
}

其他类似功能代码:

//https://www.baeldung.com/spring-mvc-sse-streams
//https://www.logicbig.com/tutorials/spring-framework/spring-web-mvc/streaming-response-body.html
//@GetMapping("/{fileName:[0-9A-z]+}")
@GetMapping("/streaming2/{uuid}/{fileName}")
//https://stackoverflow.com/questions/47868352/how-to-stream-large-http-response-in-spring
@ResponseBody
public ResponseEntity<InputStreamResource> get_File2(
@PathVariable String uuid,
@PathVariable String fileName) throws IOException { String dbFile = fileRepository.findByUUID(uuid,fileName); dbFile=String.format("E:/hls/test/%s",fileName); if (dbFile.equals(null)) {
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
} Resource file = storageService.loadAsResource(dbFile); long len = 0;
try {
len = file.contentLength();
} catch (IOException e) {
e.printStackTrace();
} MediaType mediaType = MediaType.valueOf(FileTypeMap.getDefaultFileTypeMap().getContentType(file.getFile())); // if (filename.toLowerCase().endsWith("mp4") || filename.toLowerCase().endsWith("mp3") ||
// filename.toLowerCase().endsWith("3gp") || filename.toLowerCase().endsWith("mpeg") ||
// filename.toLowerCase().endsWith("mpeg4"))
if(fileName.toLowerCase().endsWith("ts")) {
mediaType = MediaType.APPLICATION_OCTET_STREAM;
} InputStreamResource resource = new InputStreamResource(new FileInputStream(file.getFile())); return ResponseEntity.ok()
.contentType(mediaType)
.contentLength(len)
.header(HttpHeaders.ACCEPT_RANGES, "bytes")
.body(resource);
}
    @RequestMapping("/111111")
public StreamingResponseBody handleRequest (HttpServletRequest request) {
return outputStream -> {
Map<String, BigInteger> map = new HashMap<>();
map.put("one", BigInteger.ONE);
map.put("ten", BigInteger.TEN);
try(ObjectOutputStream oos = new ObjectOutputStream(outputStream)){
oos.writeObject(map);
}
};
}
    @GetMapping("/media/{uuid}/{fileName}")
@ResponseBody
public ResponseEntity<ByteArrayResource> getStream(@PathVariable String uuid,
@PathVariable String fileName) throws IOException {
String key = "media.".concat(uuid);
String encoded = cacheService.getCachedHashValue(key, fileName, String.class);
byte[] bytes = Base64.getDecoder().decode(encoded); long len = bytes.length; //InputStreamResource resource = new InputStreamResource(new FileInputStream(file.getFile()));
ByteArrayResource resource = new ByteArrayResource(bytes); return ResponseEntity.ok().contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(len).header(HttpHeaders.ACCEPT_RANGES, "bytes").body(resource);
// return ResponseEntity.ok()
// .contentType(MediaType.APPLICATION_OCTET_STREAM)
// .contentLength(len)
// .header(HttpHeaders.ACCEPT_RANGES, "bytes")
// .body(resource);
}

Asynchronous Streaming Request Processing in Spring MVC 4.2 + Spring Boot(SpringBoot中处理异步流请求 SpringMvc4.2以上)的更多相关文章

  1. 2017.3.31 spring mvc教程(六)转发、重定向、ajax请求

    学习的博客:http://elf8848.iteye.com/blog/875830/ 我项目中所用的版本:4.2.0.博客的时间比较早,11年的,学习的是Spring3 MVC.不知道版本上有没有变 ...

  2. Spring MVC普通类或工具类中调用service报空空指针的解决办法(调用service报java.lang.NullPointerException)

    当我们在非Controller类中应用service的方法是会报空指针,如图: 这是因为Spring MVC普通类或工具类中调用service报空null的解决办法(调用service报java.la ...

  3. Spring MVC 零配置 / Spring MVC JavaConfig

    1. Spring MVC的核心就是DispatcherServlet类,Spring MVC处理请求的流程如下图所示: 2. Spring MVC中典型的上下文层次 当我们初始化一个Dispatch ...

  4. spring mvc注解和spring boot注解

    1 spring mvc和spring boot之间的关系 spring boot包含spring mvc.所以,spring mvc的注解在spring boot总都是可以用的吗? spring b ...

  5. java之spring mvc之初始spring mvc

    1. mvc : mvc框架是处理 http请求和响应的框架 2. mvc 做的事情有哪些: 将 url 映射到一个java的处理方法上 将表单数据提交到 java 类中 将后台 java 类处理的结 ...

  6. Spring MVC mapping[From Spring MVC Beginner's Guide]

    In a Spring MVC application, the URL can logically be divided into five parts (see the following fig ...

  7. Spring MVC 学习笔记 spring mvc Schema-based configuration

    Spring mvc 目前支持5个tag,分别是 mvc:annotation-driven,mvc:interceptors,mvc:view-controller, mvc:resources和m ...

  8. Spring MVC(十六)--Spring MVC国际化实例

    上一篇文章总结了一下Spring MVC中实现国际化所需的配置,本文继上一文举一个完整的例子,我选择用XML的方式.我的场景是这样的: 访问一个页面时,这个页面有个表格,对表头中的列名实现国际化. 第 ...

  9. 玩转spring mvc(四)---在spring MVC中整合JPA

    关于在Spring MVC中整合JPA是在我的上一篇关于spring mvc基本配置基础上进行的,所以大家先参考一下我的上一篇文章:http://blog.csdn.net/u012116457/ar ...

随机推荐

  1. laravel 依赖注入

    <?php interface Animal{ public function attack(); public function talk(); } class People implemen ...

  2. RANSEC算法

    移步:https://blog.csdn.net/u010128736/article/details/53422070 clc;clear;close all; %%%二维直线拟合 %%%生成随机数 ...

  3. ztree树默认根据ID默认选中该条数据

    functiongetZtree() { varsetting = { view: { expandSpeed: 100, selectedMulti: true, showLine: true, / ...

  4. 001——CC2530——终端或者路由器传输到不同多协调器上

    (一)参考文献:协调器太多,cc2530的终端或则路由器选择性加入协调器的方法 (二)频道和ID一样 进入协调器和终端和路由器的两个程序,找到Tools中的f8wConfig.cfg文件.修改默认PA ...

  5. PostgreSQL 分区索引演进

    PostgreSQL 分区表,操作性相当便捷. 但只能在创建时决定是否为分区表,并决定分区条件字段,普通表创建后,不能在修改为分区表. Note:通过其他方法也可转化为分区表. 和其他数据库一样,分区 ...

  6. 如何使用git把本地代码上传到github

    Git Bash Here git init git add . git commit -m '说明' git remote add origin https://github.com//.git g ...

  7. shell 命令的重命名alias

    1.实现别名:alias ddd="df -Th": 2.删除别名:unalias ddd: 3.显示所有别名命令列表:alias: 4.存放位置:~/.bashrc  (加入该文 ...

  8. spark-shell启动错误

    18/06/24 16:41:40 ERROR spark.SparkContext: Error initializing SparkContext.java.net.BindException: ...

  9. kubernetes架构和组件

    一.Kubernetes整体架构 Kubernetes属于主从分布式架构,主要由Master Node和Worker Node组成,以及包括客户端命令行工具kubectl和其它附加项. Master ...

  10. 一个想要拥有正常的F1~F12的联想小新潮

    联想如何切换至正常的F1~F12 Fn+Fx的生活让我疲倦,实在难受,就开始了尝试关闭Fn 问题一:无法打开BIOS设置界面 重启,在开机界面时,按F2???顺利开机-(相较于台式机开机时,可以按F2 ...