A handler method which returns ResponseBodyEmitter can be used for HTTP Streaming.
ResponseBodyEmitter can send multiple Objects to the client, where each Object sent is written to the response with an HttpMessageConverter.
Let's see an example how to achieve that.
The controller
package com.logicbig.example;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Controller
public class MyWebController {
@GetMapping(value = "/test")
public ResponseBodyEmitter handleRequest() {
final ResponseBodyEmitter emitter = new ResponseBodyEmitter();
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(() -> {
for (int i = 0; i < 100; i++) {
try {
emitter.send(i + " - ", MediaType.TEXT_PLAIN);
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
emitter.completeWithError(e);
return;
}
}
emitter.complete();
});
return emitter;
}
}
Running the example
To try examples, run embedded Jetty (configured in pom.xml of example project below):
mvn jetty:run
Using Curl
$ curl -s "http://localhost:8080/test" 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - 23 - 24 - 25 - 26 - 27 - 28 - 29 - 30 - 31 - 32 - 33 - 34 - 35 - 36 - 37 - 38 - 39 - 40 - 41 - 42 - 43 - 44 - 45 - 46 - 47 - 48 - 49 - 50 - 51 - 52 - 53 - 54 - 55 - 56 - 57 - 58 - 59 - 60 - 61 - 62 - 63 - 64 - 65 - 66 - 67 - 68 - 69 - 70 - 71 - 72 - 73 - 74 - 75 - 76 - 77 - 78 - 79 - 80 - 81 - 82 - 83 - 84 - 85 - 86 - 87 - 88 - 89 - 90 - 91 - 92 - 93 - 94 - 95 - 96 - 97 - 98 - 99 -
This GIF shows how the curl command streams output in real time:
Wrapping ResponseBodyEmitter in ResponseEntity
The handler method can also return ResponseEntity with ResponseBodyEmitter wrapped as the body. We would probably want to do that to customize the status and headers of the response.
package com.logicbig.example;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Controller
public class MyWebController2 {
@RequestMapping("/test2")
public ResponseEntity<ResponseBodyEmitter> handleRequest(
@RequestParam(value = "count", defaultValue = "500") int count) {
final ResponseBodyEmitter emitter = new ResponseBodyEmitter();
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(() -> {
for (int i = 0; i < count; i++) {
try {
emitter.send(i + " - ", MediaType.TEXT_PLAIN);
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
emitter.completeWithError(e);
return;
}
}
emitter.complete();
});
return new ResponseEntity(emitter, HttpStatus.OK);
}
}
Example ProjectDependencies and Technologies Used: - spring-webmvc 7.0.6 (Spring Web MVC)
Version Compatibility: 4.2.0.RELEASE - 7.0.6 Version compatibilities of spring-webmvc with this example: Versions in green have been tested.
- jakarta.servlet-api 6.1.0 (Jakarta Servlet API documentation)
- spring-test 7.0.6 (Spring TestContext Framework)
- junit-jupiter-engine 6.0.3 (Module "junit-jupiter-engine" of JUnit)
- hamcrest 3.0 (Core API and libraries of hamcrest matcher framework)
- JDK 25
- Maven 3.9.11
|