如何从Spring Rest Controller流式发送文本分片及就绪数据分片?
Hey there! Let's walk through two practical approaches to send text data in chunks from a Spring REST Controller as soon as each piece is ready—this is perfect for long-running tasks, real-time updates, or avoiding forcing clients to wait for a huge payload to finish processing.
1. Using StreamingResponseBody (Basic Sequential Streaming)
This is Spring's built-in tool for streaming responses directly to the client without buffering the entire payload. It’s ideal when you have a sequence of data chunks to send one after another.
Controller Implementation
import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody; import java.io.IOException; import java.io.OutputStream; import java.util.concurrent.TimeUnit; @RestController public class StreamingTextController { @GetMapping(value = "/stream-text", produces = MediaType.TEXT_PLAIN_VALUE) public StreamingResponseBody streamTextChunks() { return outputStream -> { // Simulate generating text chunks over time (replace with your actual logic) String[] textChunks = {"First chunk ready!\n", "Second chunk coming in...\n", "Third chunk here!\n", "Final chunk delivered!\n"}; for (String chunk : textChunks) { // Write the chunk to the output stream outputStream.write(chunk.getBytes()); // Flush immediately to ensure the client gets the chunk right away outputStream.flush(); // Simulate processing delay between chunks (adjust as needed) TimeUnit.SECONDS.sleep(1); } }; } }
Key Details:
- No Buffering: Spring automatically sets
Transfer-Encoding: chunkedin the response headers, so each chunk is sent as soon as you callflush(). - Error Handling: Wrap the loop in a try-catch block if you need to handle exceptions mid-stream (e.g., close the stream gracefully if processing fails).
- Use Case: Great for exporting large files, streaming log data, or any scenario where you need to send a continuous stream of text.
2. Using Server-Sent Events (SSE) with SseEmitter
If you need to send event-driven, real-time updates (like task progress notifications or live status updates), SSE is a better fit. It establishes a persistent connection, letting the server push chunks whenever new data is ready.
Controller Implementation
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; import java.util.concurrent.TimeUnit; @RestController public class SseStreamingController { @GetMapping("/sse-stream-updates") public SseEmitter streamSseEvents() { // Set a timeout (e.g., 30 minutes) to keep the connection alive SseEmitter emitter = new SseEmitter(TimeUnit.MINUTES.toMillis(30)); // Run chunk generation in a separate thread to avoid blocking the Spring MVC thread pool new Thread(() -> { try { // Send an initial welcome message emitter.send("Starting real-time updates...\n"); // Simulate dynamic chunk generation (replace with your actual data source) for (int i = 1; i <= 5; i++) { String progressUpdate = String.format("Task progress: %d/5 completed\n", i); // Send the chunk as an SSE event (you can add event IDs/types too) emitter.send(SseEmitter.event().data(progressUpdate)); TimeUnit.SECONDS.sleep(1); } // Mark the stream as complete when done emitter.complete(); } catch (IOException | InterruptedException e) { // Handle errors (e.g., client disconnected unexpectedly) emitter.completeWithError(e); } }).start(); return emitter; } }
Client-Side Example (JavaScript)
To receive the SSE stream in a browser:
const eventSource = new EventSource('/sse-stream-updates'); eventSource.onmessage = function(event) { console.log('Received update:', event.data); // Append to UI, update progress bar, etc. }; eventSource.onerror = function(error) { console.error('Stream error occurred:', error); eventSource.close(); };
Key Details:
- Persistent Connection: SSE keeps the HTTP connection open, so the server can push updates anytime new data is available.
- Event Metadata: You can add event IDs, custom event types, and retry intervals using
SseEmitter.event()(e.g.,event().id("update-1").name("progress").data(chunk)). - Use Case: Perfect for live notifications, task progress tracking, or any scenario where you need to push updates to the client in real time.
Which Approach Should You Pick?
- Go with
StreamingResponseBodyfor one-way, sequential streaming of large content (like exporting logs or large text files). - Use SSE with
SseEmitterfor real-time, event-driven updates where the server needs to initiate pushes to the client.
内容的提问来源于stack exchange,提问作者madcolonel10

