Use this when the server has to update a page without the user doing anything: a job that reports progress, a chat, a dashboard that follows live data. How native WebSockets work explains what happens underneath.
Add the dependency and the filter
Add the module to your build:
pom.xml
<dependency>
<groupId>org.apache.wicket</groupId>
<artifactId>wicket-native-websocket-javax</artifactId>
<version>${wicket.version}</version>
</dependency>
Replace WicketFilter with JavaxWebSocketFilter. In web.xml, only the filter class changes:
web.xml
<filter>
<filter-name>wicket</filter-name>
<filter-class>org.apache.wicket.protocol.ws.javax.JavaxWebSocketFilter</filter-class>
<init-param>
<param-name>applicationClassName</param-name>
<param-value>com.example.WicketApplication</param-value>
</init-param>
</filter>
The servlet container has to support Jakarta WebSocket, as Tomcat, Jetty and WildFly do.
With Spring Boot
Add spring-boot-starter-websocket, register the filter, and export Wicket’s endpoint:
@Bean
public FilterRegistrationBean<JavaxWebSocketFilter> wicketFilter() {
var registration = new FilterRegistrationBean<>(new JavaxWebSocketFilter());
registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR,
DispatcherType.FORWARD, DispatcherType.ASYNC);
registration.setAsyncSupported(true);
registration.addInitParameter(WicketFilter.APP_FACT_PARAM,
SpringWebApplicationFactory.class.getName());
registration.addInitParameter(WicketFilter.FILTER_MAPPING_PARAM, "/*");
registration.addUrlPatterns("/*");
return registration;
}
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
@Bean
public WicketServerEndpointConfig wicketServerEndpointConfig() {
return new WicketServerEndpointConfig();
}
Answer messages from the browser
Add a WebSocketBehavior to the page, or to a component on it. The browser opens the connection when the page loads. Override the callbacks you need:
add(new WebSocketBehavior() {
@Override
protected void onMessage(WebSocketRequestHandler handler, TextMessage message) {
chat.add(message.getText());
handler.add(messages);
}
});
handler.add(...) re-renders components in the browser, exactly as with an AjaxRequestTarget; the components need a markup id. handler.push(String) sends a plain text message instead, which your JavaScript receives; see WebSocket JavaScript API. appendJavaScript and prependJavaScript work as well.
Push an update from a background thread
Code outside a request cannot touch components. It sends a message object, and the page’s components turn it into an update.
-
Define the message as a class implementing
IWebSocketPushMessage:public record JobProgress(int percent) implements IWebSocketPushMessage { } -
Remember where to send it. The connection is identified by the application name, the session id and a key for the page.
onConnectgives you all three:add(new WebSocketBehavior() { @Override protected void onConnect(ConnectedMessage message) { jobs.start(new ProgressReporter(message.getApplication().getName(), message.getSessionId(), message.getKey())); } }); -
In the background thread, broadcast the message to that connection:
public record ProgressReporter(String applicationName, String sessionId, IKey key) implements Serializable { public void report(int percent) { Application application = Application.get(applicationName); var registry = WebSocketSettings.Holder.get(application).getConnectionRegistry(); new WebSocketPushBroadcaster(registry).broadcast( new ConnectedMessage(application, sessionId, key), new JobProgress(percent)); } } -
Receive it in the page or a component and update the components:
@Override public void onEvent(IEvent<?> event) { super.onEvent(event); if (event.getPayload() instanceof WebSocketPushPayload payload && payload.getMessage() instanceof JobProgress progress) { percent = progress.percent(); payload.getHandler().add(progressBar); } }WebSocketBehavior.onPush(handler, message)receives the same messages, if you prefer to handle them in the behavior.
A connection that has closed in the meantime is skipped without an error. To show that a component is being recomputed while the server works, see Block the page while a request runs.
Push to many pages at once
WebSocketPushBroadcaster also has broadcastAll(application, message) for every connection of the application, broadcastAllInSession(application, sessionId, message) for one user’s pages, and broadcastAllMatchingFilter(application, filter, message) with a lambda that selects connections by session id and key:
broadcaster.broadcastAllMatchingFilter(application,
(sessionId, key) -> key instanceof PageIdKey, new PriceChanged(product));
By default the broadcast runs in the calling thread, one page after the other. WebSocketSettings.setWebSocketPushMessageExecutor moves it to other threads.
Send raw text without a page
When the browser renders the data itself, for example a chart that receives a stream of numbers, send text straight to the connection and skip the page:
IWebSocketConnection connection = registry.getConnection(application, sessionId, key);
if (connection != null && connection.isOpen()) {
connection.sendMessageAsync(json);
}
For many messages from the browser, a WebSocketResource handles them without taking the page lock. Register it with getSharedResources().add("prices", new PriceResource()) in init() and add new BaseWebSocketBehavior("prices") to the page. It has the same callbacks as WebSocketBehavior, but cannot re-render components.