Read also:
WebSocket integration by Arjan Tijms
JSF 2.3 - Explicitly open/close a websocket channel
JSF 2.3 - Conditionally open/close a websocket channel
JSF 2.3 - Firing one-time push when the web socket channel has been opened
JSF 2.3 - Multiple File Upload with HTML 5, AJAX and upload progress bar via web sockets
Starting with JSF 2.3-m05 we can take advantage of a brand new feature - register a web socket push connection in client side. Thanks to the JSF team (especially to Bauke Scholtz (aka BalusC)) this feature is available in today milestone via <f:websocket/> tag.
WebSocket integration by Arjan Tijms
JSF 2.3 - Explicitly open/close a websocket channel
JSF 2.3 - Conditionally open/close a websocket channel
JSF 2.3 - Firing one-time push when the web socket channel has been opened
JSF 2.3 - Multiple File Upload with HTML 5, AJAX and upload progress bar via web sockets
Starting with JSF 2.3-m05 we can take advantage of a brand new feature - register a web socket push connection in client side. Thanks to the JSF team (especially to Bauke Scholtz (aka BalusC)) this feature is available in today milestone via <f:websocket/> tag.
In this post, let's see a minimal
usage of <f:websocket/> tag.
In JSF page, we need to add the <f:websocket/> tag with its two
required attributes:
·
channel
- This is javax.el.ValueExpression
that must be evaluated to String
and it represents the name of the web socket channel. A channel name is
restricted to alphanumeric characters, hyphens, underscores and periods. A
channel can have multiple open web sockets, and each of these sockets will
receive the same push notification from the server.
·
onmessage
- This is javax.el.ValueExpression
that must be evaluated to String
and it represents the a JavaScript listener function that is automatically invoked
when a push notification is received from the server.
The signature of the listener function for onmessage is of type:
function fooListener(message, channel, event)
{
// message - the message pushed by the server
// channel - the channel name
// event - the raw MessageEvent instance
}
So, a simple <f:websocket/>
tag usage will look like this:
<f:websocket
channel="clock" onmessage="socketListener" />
<div
id="clockId"></div>
<script
type="text/javascript">
function socketListener(message, channel,
event) {
document.getElementById("clockId").innerHTML += message +
"<br/>";
}
</script>
By default, when we start the application, the web socket is automatically
connected and open. As long as the document is open the web socket is open. When
the document is unloaded the web socket is automatically closed. In the web
socket is initially successfully connected but the connection is closed as a
result of e.g. a network error or server restart, JSF will try to auto-reconnect
it at increasing intervals.
Now, let's focus on the server side. Here we have to take into account
the push messages mechanism. This mechanism is based on javax.faces.push.PushContext interface
and javax.faces.push.Push
API.
First, you need to know that by default the web socket is application
scoped. This means that the managed bean that can push messages to this web
socket must be in application scope (annotated with @ApplicationScope). In this case, the
push message can be sent by all users and the application itself.
Furthermore, you have to inject PushContext via @Push annotation on the given channel name in
any CDI/container managed artifact. For example:
@Inject
@Push(channel =
"clock")
private
PushContext push;
Finally, we need to write an action method capable to push messages to
web socket via PushContext.
For example:
public void
clockAction(){
Calendar now = Calendar.getInstance();
String time = now.get(Calendar.HOUR_OF_DAY) +
":" +
now.get(Calendar.MINUTE) +
":" +
now.get(Calendar.SECOND);
LOG.log(Level.INFO, "Time: {0}",
time);
push.send(time);
}
Let's glue everything together. First, the JSF page:
<h:body>
<h:form>
<h:commandButton value="Clock"
action="#{pushBean.clockAction()}">
<f:ajax />
</h:commandButton>
</h:form>
<f:websocket channel="clock"
onmessage="socketListener" />
<hr/>
<div id="clockId"></div>
<script
type="text/javascript">
function socketListener(message, channel, event) {
document.getElementById("clockId").innerHTML += message +
"<br/>";
}
</script>
</h:body>
Next, our simple CDI bean:
@Named
@ApplicationScoped
public class
PushBean implements Serializable {
private static final Logger LOG =
Logger.getLogger(PushBean.class.getName());
@Inject
@Push(channel = "clock")
private PushContext push;
public void clockAction(){
Calendar now = Calendar.getInstance();
String time = now.get(Calendar.HOUR_OF_DAY) +
":" +
now.get(Calendar.MINUTE) +
":" + now.get(Calendar.SECOND);
LOG.log(Level.INFO, "Time: {0}",
time);
push.send(time);
}
}
In order to avoid an error as in figure below (from Payara), we need to add a fake endpoint:
As BalusC pointed out, this fake endpoint should look like below:
import javax.websocket.Endpoint;
import javax.websocket.EndpointConfig;
import javax.websocket.Session;
public class FakeEndpoint extends Endpoint {
@Override
public void onOpen(Session session, EndpointConfig config) {
// https://java.net/jira/browse/WEBSOCKET_SPEC-240
}
}
Update provided by Arjan Tijms
"Nevertheless, the GlassFish/Tyrus seems to be the only implementation that do not support adding an EndPoint dynamically, so JSF team have added an SPI jar for Tyrus in GlassFish, that negates the need for the fake end point. See https://github.com/jsf-spec/mojarra/tree/master/spi. As far as we know the fake endpoint is also only needed for Tyrus, not for the other WebSocket implementations (such as the one in Tomcat)."
Finally, the m05 requires the following settings in web.xml:
<context-param>
<param-name>javax.faces.ENABLE_CDI_RESOLVER_CHAIN</param-name>
<param-value>true</param-value>
</context-param>
<context-param>
<param-name>javax.faces.ENABLE_WEBSOCKET_ENDPOINT</param-name>
<param-value>true</param-value>
</context-param>
Done! The complete application was tested under Payara server and it is available here.
I imagined websocket support in more declarative way similar to Ajax. For instance, jsf component listens to a server side event upon which rerenders on the server and updates on the client - the same way f:ajax works but event is triggered not from the client but from the server. In this case websocket is just a one way among others to implement such behaviour, component rendering is kept in one place and Javascript is not need to be written in most cases.
RăspundețiȘtergereWowwwowowowow!nice!!
RăspundețiȘtergereAcest comentariu a fost eliminat de autor.
RăspundețiȘtergere@Aleksandr: there's no JSF state available at the moment the push message is sent. It's easier to interpret web sockets as "server side triggers to do something in client side". The client side in turn can use e.g. <o:commandScript>, <p:remoteCommand> or the future <h:commandScript> to perform complex UI updates.
RăspundețiȘtergere