Unlike other java ee frameworks, wffweb doesn't have any direct dependency over java ee classes. And, websocket implementation is different in
different servers and i.e. not all servers follow JSR 356 specification.
So, we have to configure wffweb if its server client communication feature needs to be used.
There are three main configurations.
1. Configure websocket,
2. set up a session listener and
3. set up a BrowserPage
.
Of course, once we have configured BrowserPage
,
we have to provide it by any servlet/rest.
1. Setting up websocket for wffweb
There must be a websocket url set up with your server, that websocket url should not be used by others. It should be dedicated for wffweb. The websocket should support sending and receiving binary data because wffweb and client communication is done via a binary protocol called wff binary message. when the websocket connection is opened, it must be informed to the wffweb as follows
//webSocket session
List wffInstanceIds = session.getRequestParameterMap().get("wffInstanceId");
String instanceId = wffInstanceIds.get(0);
BrowserPage browserPage = BrowserPageContext.INSTANCE.webSocketOpened(instanceId);
//or String.valueOf(session.hashcode()) if session.getId() is not available
browserPage.addWebSocketPushListener(session.getId(), new WebSocketPushListener() {
@Override
public void push(ByteBuffer data) {
try {
session.getBasicRemote()
.sendBinary(data);
} catch (Exception e) {
throw new PushFailedException(e.getMessage(), e);
}
}
});
The above code setting a listener so that wffweb can push messages.
When the websocket is closed, it should be informed to wffweb as follows,
List wffInstanceIds = session.getRequestParameterMap().get("wffInstanceId");
String instanceId = wffInstanceIds.get(0);
//session.getId() is the id given while adding websocket listener
BrowserPageContext.INSTANCE.webSocketClosed(instanceId, session.getId());
When the websocket receives a message, it should be forwarded to wffweb as follows.
List wffInstanceIds = session.getRequestParameterMap().get("wffInstanceId");
String instanceId = wffInstanceIds.get(0);
BrowserPage browserPage = BrowserPageContext.INSTANCE.getBrowserPage(instanceId);
browserPage.webSocketMessaged(message);
Here, wffInstanceId
is a unique id generated by BrowserPage
instance, it can be taken by browserPage.getInstanceId
method.
Each instance of a BrowserPage
will have its own unique instanceId
.
If you want to keep the
HttpSession
active as long as the websocket connection is alive, you have to do
the following. In
OnOpen
, get the httpSession object and set
httpSession.setMaxInactiveInterval(-1);
and in
OnClose
reset the session time out as
httpSession.setMaxInactiveInterval(60 * 30);
(the same value which is given in web.xml).
An http request to a dummy url may need to be made afterwards.
So if the websocket doesn't make a connection again within the given time, the
httpSession will be timed out. This can also avoid keeping a heart
beat request to the server to keep the httpSession alive. This is
the solution for the issue explained in the description of this ticket.
Refer this fully configured code from sample project. Or checkout this demo project.
2. Setting up session listener for wffweb
When the http session is closed, it must be informed to wffweb as follows.
@WebListener
public class SessionListener implements HttpSessionListener {
@Override
public void sessionCreated(HttpSessionEvent sessionEvent) {
// NOP for wffweb
}
@Override
public void sessionDestroyed(HttpSessionEvent sessionEvent) {
BrowserPageContext.INSTANCE
.httpSessionClosed(sessionEvent.getSession().getId());
}
}
BrowserPage
represents UI page (window) of a browser. In a single page application, there will be only one BrowserPage
.
public class IndexPage extends BrowserPage {
@Override
public String webSocketUrl() {
//the websocket url you have configured for wffweb
return "ws://yourdomain.com/wffwebdemoproject/ws-for-index-page";
}
@Override
public AbstractHtml render() {
//keep this as a separate class so as to
//change its different portion
Html indexPageLayout = new Html(null) {{
new Head(this);
new Body(this) {{
new Div(this) {{
new H1(this) {{
new NoTag(this, "こんにちは WFFWEB");
}};
}};
}};
}};
return indexPageLayout;
}
}
Whatever changes made to
indexPageLayout
will automatically be reflected to the client browser. So, we can
keep it as a separate class for maintainability.
Once we have created a
BrowserPage
, we have to add it to
BrowserPageContext
.
BrowserPageContext
is the context which holds all
BrowserPage
instances. Adding a
BrowserPage
instance in to
BrowserPageContext
may be done inside a servlet because we need to create instance of
BrowserPage
only when there is a request from new session. i.e. we need to have
only one instance of the
IndexPage
per session. It may be added as follows
@WebServlet("/index")
public class IndexPageServlet extends HttpServlet {
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html;charset=utf-8");
try (OutputStream os = response.getOutputStream();) {
HttpSession session = request.getSession();
String instanceId = (String) session
.getAttribute("indexPageInstanceId");
BrowserPage browserPage = null;
if (instanceId != null) {
browserPage = BrowserPageContext.INSTANCE
.getBrowserPage(instanceId);
// if the server is restarted browserPage could be null here,
// so you could save this instance to db after addBrowserPage
// method
// and retried from db using browserPage.getInstanceId()
}
if (browserPage == null) {
browserPage = new IndexPage();
BrowserPageContext.INSTANCE.addBrowserPage(session.getId(),
browserPage);
session.setAttribute("indexPageInstanceId",
browserPage.getInstanceId());
}
browserPage.toOutputStream(os, "UTF-8");
}
}
}
You can download a demo project from here
For any technical assistance mail to tech-support@webfirmframework.com
Subscribe on youtube for technical videos