0
# Server-Sent Events
1
2
Implementation of the W3C EventSource specification for server-sent events, enabling real-time server-to-client communication. This provides a standardized way to push data from server to web clients using HTTP connections.
3
4
## Capabilities
5
6
### EventSource Interface
7
8
Core interface for handling server-sent event connections.
9
10
```java { .api }
11
/**
12
* Passive half of an event source connection as defined by the EventSource specification.
13
* Allows applications to be notified of connection events and handle the connection lifecycle.
14
*/
15
public interface EventSource {
16
/**
17
* Callback invoked when an event source connection is opened
18
* @param emitter The Emitter instance for operating on the connection
19
* @throws IOException if the implementation throws such exception
20
*/
21
void onOpen(Emitter emitter) throws IOException;
22
23
/**
24
* Callback invoked when an event source connection is closed
25
*/
26
void onClose();
27
}
28
```
29
30
### Emitter Interface
31
32
Active interface for sending events to the client.
33
34
```java { .api }
35
/**
36
* Active half of an event source connection, allows applications to operate on the connection
37
* by sending events, data, comments, or closing the connection.
38
* Instances are fully thread-safe and can be used from multiple threads.
39
*/
40
public interface Emitter {
41
/**
42
* Send a named event with data to the client
43
* @param name The event name
44
* @param data The data to be sent
45
* @throws IOException if an I/O failure occurred
46
*/
47
void event(String name, String data) throws IOException;
48
49
/**
50
* Send a default event with data to the client
51
* Multi-line data is automatically split into multiple data lines
52
* @param data The data to be sent
53
* @throws IOException if an I/O failure occurred
54
*/
55
void data(String data) throws IOException;
56
57
/**
58
* Send a comment to the client
59
* @param comment The comment to send
60
* @throws IOException if an I/O failure occurred
61
*/
62
void comment(String comment) throws IOException;
63
64
/**
65
* Close this event source connection
66
*/
67
void close();
68
}
69
```
70
71
### EventSourceServlet
72
73
Abstract servlet that implements the event source protocol.
74
75
```java { .api }
76
/**
77
* Servlet implementing the event source protocol (server-sent events).
78
* Must be subclassed to implement newEventSource method.
79
*/
80
public abstract class EventSourceServlet extends HttpServlet {
81
/**
82
* Initialize the servlet, set up heartbeat configuration
83
* @throws ServletException if initialization fails
84
*/
85
public void init() throws ServletException;
86
87
/**
88
* Clean up resources when servlet is destroyed
89
*/
90
public void destroy();
91
92
/**
93
* Create an EventSource instance for the given request.
94
* Must be implemented by subclasses.
95
* @param request The HTTP request
96
* @return EventSource instance or null to send 503 error
97
*/
98
protected abstract EventSource newEventSource(HttpServletRequest request);
99
100
/**
101
* Set up the HTTP response for event streaming
102
* @param request The HTTP request
103
* @param response The HTTP response
104
* @throws IOException if response setup fails
105
*/
106
protected void respond(HttpServletRequest request, HttpServletResponse response) throws IOException;
107
108
/**
109
* Handle the opening of an event source connection
110
* @param eventSource The EventSource instance
111
* @param emitter The Emitter for the connection
112
* @throws IOException if connection setup fails
113
*/
114
protected void open(EventSource eventSource, EventSource.Emitter emitter) throws IOException;
115
}
116
```
117
118
**Configuration Parameters:**
119
120
- **heartBeatPeriod**: Heartbeat period in seconds for checking closed connections (default: 10)
121
122
### EventSourceEmitter
123
124
Internal implementation of the Emitter interface.
125
126
```java { .api }
127
/**
128
* Implementation of EventSource.Emitter that handles the actual event streaming
129
*/
130
public class EventSourceEmitter implements EventSource.Emitter, Runnable {
131
/**
132
* Create a new emitter for the given EventSource and AsyncContext
133
* @param eventSource The EventSource instance
134
* @param async The AsyncContext for the connection
135
* @throws IOException if emitter creation fails
136
*/
137
public EventSourceEmitter(EventSource eventSource, AsyncContext async) throws IOException;
138
}
139
```
140
141
## Usage Examples
142
143
### Basic EventSource Implementation
144
145
```java
146
import org.eclipse.jetty.ee10.servlets.EventSource;
147
import org.eclipse.jetty.ee10.servlets.EventSourceServlet;
148
import jakarta.servlet.http.HttpServletRequest;
149
import java.io.IOException;
150
import java.util.concurrent.ScheduledExecutorService;
151
import java.util.concurrent.Executors;
152
import java.util.concurrent.TimeUnit;
153
154
public class TimeEventSourceServlet extends EventSourceServlet {
155
private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
156
157
@Override
158
protected EventSource newEventSource(HttpServletRequest request) {
159
return new EventSource() {
160
@Override
161
public void onOpen(Emitter emitter) throws IOException {
162
// Send welcome message
163
emitter.data("Connection established");
164
165
// Send current time every 5 seconds
166
executor.scheduleAtFixedRate(() -> {
167
try {
168
emitter.event("time", new java.util.Date().toString());
169
} catch (IOException e) {
170
emitter.close();
171
}
172
}, 0, 5, TimeUnit.SECONDS);
173
}
174
175
@Override
176
public void onClose() {
177
// Cleanup when client disconnects
178
System.out.println("Client disconnected");
179
}
180
};
181
}
182
183
@Override
184
public void destroy() {
185
super.destroy();
186
executor.shutdown();
187
}
188
}
189
```
190
191
### Chat Room EventSource
192
193
```java
194
import org.eclipse.jetty.ee10.servlets.EventSource;
195
import org.eclipse.jetty.ee10.servlets.EventSourceServlet;
196
import jakarta.servlet.http.HttpServletRequest;
197
import java.io.IOException;
198
import java.util.concurrent.CopyOnWriteArrayList;
199
import java.util.List;
200
201
public class ChatEventSourceServlet extends EventSourceServlet {
202
private static final List<EventSource.Emitter> clients = new CopyOnWriteArrayList<>();
203
204
@Override
205
protected EventSource newEventSource(HttpServletRequest request) {
206
final String username = request.getParameter("username");
207
208
return new EventSource() {
209
private EventSource.Emitter myEmitter;
210
211
@Override
212
public void onOpen(Emitter emitter) throws IOException {
213
myEmitter = emitter;
214
clients.add(emitter);
215
216
// Welcome message to new client
217
emitter.data("Welcome to the chat, " + username + "!");
218
219
// Notify other clients
220
broadcastMessage("system", username + " joined the chat");
221
}
222
223
@Override
224
public void onClose() {
225
clients.remove(myEmitter);
226
broadcastMessage("system", username + " left the chat");
227
}
228
};
229
}
230
231
private void broadcastMessage(String type, String message) {
232
clients.removeIf(emitter -> {
233
try {
234
emitter.event(type, message);
235
return false; // Keep in list
236
} catch (IOException e) {
237
emitter.close();
238
return true; // Remove from list
239
}
240
});
241
}
242
243
// Method to send messages from other servlets/endpoints
244
public static void sendChatMessage(String username, String message) {
245
ChatEventSourceServlet servlet = new ChatEventSourceServlet();
246
servlet.broadcastMessage("message", username + ": " + message);
247
}
248
}
249
```
250
251
### News Feed EventSource
252
253
```java
254
public class NewsFeedEventSourceServlet extends EventSourceServlet {
255
@Override
256
protected EventSource newEventSource(HttpServletRequest request) {
257
final String category = request.getParameter("category");
258
259
return new EventSource() {
260
@Override
261
public void onOpen(Emitter emitter) throws IOException {
262
// Send initial data
263
emitter.comment("News feed started for category: " + category);
264
265
// Send recent articles
266
List<Article> recentArticles = getRecentArticles(category);
267
for (Article article : recentArticles) {
268
String articleData = String.format(
269
"{\"title\":\"%s\",\"url\":\"%s\",\"timestamp\":\"%s\"}",
270
article.getTitle(),
271
article.getUrl(),
272
article.getTimestamp()
273
);
274
emitter.event("article", articleData);
275
}
276
277
// Register for live updates
278
NewsService.registerListener(category, (article) -> {
279
try {
280
String articleData = String.format(
281
"{\"title\":\"%s\",\"url\":\"%s\",\"timestamp\":\"%s\"}",
282
article.getTitle(),
283
article.getUrl(),
284
article.getTimestamp()
285
);
286
emitter.event("new-article", articleData);
287
} catch (IOException e) {
288
emitter.close();
289
}
290
});
291
}
292
293
@Override
294
public void onClose() {
295
NewsService.unregisterListener(category);
296
}
297
};
298
}
299
300
private List<Article> getRecentArticles(String category) {
301
// Implementation to fetch recent articles
302
return NewsService.getRecentArticles(category, 10);
303
}
304
}
305
```
306
307
## Web.xml Configuration
308
309
```xml
310
<servlet>
311
<servlet-name>EventSourceServlet</servlet-name>
312
<servlet-class>com.example.MyEventSourceServlet</servlet-class>
313
<init-param>
314
<param-name>heartBeatPeriod</param-name>
315
<param-value>30</param-value>
316
</init-param>
317
</servlet>
318
<servlet-mapping>
319
<servlet-name>EventSourceServlet</servlet-name>
320
<url-pattern>/events/*</url-pattern>
321
</servlet-mapping>
322
```
323
324
## Client-Side JavaScript
325
326
```javascript
327
// Basic client connection
328
const eventSource = new EventSource('/events');
329
330
eventSource.onmessage = function(event) {
331
console.log('Data received:', event.data);
332
};
333
334
eventSource.addEventListener('time', function(event) {
335
console.log('Time event:', event.data);
336
});
337
338
eventSource.onerror = function(event) {
339
console.log('Error occurred:', event);
340
};
341
342
// Close connection
343
eventSource.close();
344
```
345
346
## Thread Safety
347
348
The `EventSource.Emitter` instances are fully thread-safe and can be used from multiple threads simultaneously. This allows for complex event publishing scenarios where multiple background threads may need to send events to connected clients.
349
350
## Connection Management
351
352
- Connections are automatically cleaned up when clients disconnect
353
- Heartbeat mechanism detects stale connections (configurable via `heartBeatPeriod`)
354
- The servlet uses async processing to handle multiple concurrent connections efficiently
355
- Failed write operations automatically close the connection and trigger `onClose()` callback