Define Websocket Server Endpoints using Liferay Websocket Whiteboard

Define Websocket Server Endpoints using Liferay Websocket Whiteboard

JSR 356 is the Java API for Websocket. It defines the standard way to create Websocket client and server endpoits in Java. In this blog we are going to describe how you can register websocket server endpoints in a OSGi container using Liferay Websocket Whiteboard

What is Liferay Websocket Whiteboard?

The Liferay Websocket Whiteboard is a simple module which allows you to define new Websocket server endpoints as regular OSGi services.

We are still going through the peer review process so, at the time of this writing, some of the features could not be in master branch yet.

How do you register a Websocket server endpoint with Liferay Websocket Whiteboard?

Include dependencies

First of all you will need to add the Liferay Websocket Whiteboard dependency to your project.

For now we have not published the dependencies in our nexus, so you need to create the needed jars from the source code (run gradle install in the websocket folder).

In the near future, once we publish the artifacts to our public Nexus repo, you will just need to add the dependencies in your build file, as shown below:

com.liferay:com.liferay.websocket.whiteboard:1.0.0
com.liferay:com.liferay.websocket.whiteboard.spifly.fragment:1.0.0

Configure your Liferay OSGi Container

If you are going to define your new websocket server endpoint in Liferay 7, anything special needs to be done, all the required infrastrcuture is already provided.

Configure your NonLiferay OSGi Container

If you are going to register your server endpoint in a standalone OSGi Container, you will need to register a javax.servlet.ServletContext service with the property websocket.active set to true.

Here you can see an example of how to register a ServletContext service with the property websocket.active set to TRUE:

Dictionary<String, Object> servletContextProps = new Hashtable<String, Object>();
servletContextProps.put("websocket.active", Boolean.TRUE);

bundleContext.registerService(ServletContext.class, servletContext, servletContextProps);

Define server endpoints

Now your are ready to create your first Websocket server endpoint. For now we don't support the Annotation-Driven Approach version, only the Interface-Driven Approach is supported.

If you want to  create a websocket Server endpoint you only need to register a OSGi Service for javax.websocket.Endpoint.class with the following properties:

org.osgi.htto.websocket.endpoint.path: required, the path for the websocket.

Here you can see an example of how to register a server Endpoint service using Declarative Services:

@Component(
    immediate = true,
    property = {"org.osgi.http.websocket.endpoint.path=/echo"},
    service = Endpoint.class
)
public class EchoWebSocketEndpoint extends Endpoint {
    @Override
    public void onOpen(final Session session, EndpointConfig endpointConfig) {
        session.addMessageHandler(
            new MessageHandler.Whole<String>() {
                @Override
                public void onMessage(String text) {
                    try {
                        RemoteEndpoint.Basic remoteEndpoint =
                            session.getBasicRemote();
                        remoteEndpoint.sendText(text);
                    }
                    catch (IOException ioe) {
                        throw new RuntimeException(ioe);
                    }
                }
            });
    }
}

See also

JSR 356

Websocket server endpoints

Blogs
Hi CRISTINA,

I've compiled your code and deployed to the new Liferay 7.0.2 GA3 server for a test running.

I'm having no error deploying the portlet which with the EchoWebSocketEndpoint class.

However, I cannot get my client connected to the endpoint no matter what sort of addresses I use.

Tried for:

var logSocket = new WebSocket("ws://localhost:8080/o/w4u-test-1.0-SNAPSHOT(this is the name of my project war)/echo");

or

var logSocket = new WebSocket("ws://localhost:8080/w4u-test-1.0-SNAPSHOT(this is the name of my project war)/echo");

still getting the 404 error.

And I'm getting:

21:59:51,260 WARN [http-apr-8080-exec-5][code_jsp:181] {code="404", msg="ProxyServlet: /o/w4u-test-1.0-SNAPSHOT/echo", uri=/o/w4u-test-1.0-SNAPSHOT/echo}

from the server console.

Any help will be appreciated.
Hi Samuel,

If you want to use this feature in 7.0.2 GA3, you should add this property to your portal-ext.properties file:

module.framework.system.packages.extra=\
com.ibm.crypto.provider,\
com.ibm.db2.jcc,\
com.microsoft.sqlserver.jdbc,\
com.mysql.jdbc,\
com.p6spy.engine.spy,\
com.sun.security.auth.module,\
com.sybase.jdbc4.jdbc,\
oracle.jdbc,\
org.postgresql,\
org.apache.naming.java,\
org.hsqldb.jdbc,\
org.mariadb.jdbc,\
sun.misc,\
sun.net.util,\
sun.security.provider,\
javax.websocket;version="1.1.0",\
javax.websocket.server;version="1.1.0"

You can found a full example with 7.0.0.GA3 in https://github.com/cgoncas/liferay-websocket-echo

Thanks!!
Thank you very much for your prompt reply, Cristina.
By following the example you've posted, we can now get it to work.

One thing worth mentioning is that since now the endpoint is defined under OSGi root instead of the context of each portlet. Every time the project deployed to the server, it will not get unregistered and registered again with the portlet context itself. So every time we deploy the project we're getting a "Multiple Endpoints may not be deployed to the same path" exception. I saw there was a judgement in your code already:https://github.com/cgoncas/liferay-portal/blob/WEBSOCKET_LIFERAY_SUPPORT/modules/apps/foundation/websocket/websocket-whiteboard/src/main/java/com/liferay/websocket/whiteboard/internal/WebSocketEndpointTracker.java#L79. But I've no idea why is this not working properly under the tomcat bundle we're using.

Except for the exception above, the end point worked well as before.
Thanks a lot for the feedback, Samuel. We will try to fix this issue as soon as possible.

If you want to track it, just watch the issue https://issues.liferay.com/browse/LPS-69232
Hi, I got this error when I forget leading /o/.. in org.osgi.http.websocket.endpoint.path property of endpoint component class.
Hi,

Is there any way to use web socket in liferay 7 without declaring this property in portal-ext file. Actually I am developing plug and play module where user don't need to rely on adding this property and restart the server.

Anything can be done so that I just deploy my module and everything gets set ?

Regards,
Jaydip
Does anybody getting this same error when module is started again ?
Unble to register WebSocket endpoint class ... for path /../
javax.websocket.DeploymentException: Multiple Endpoints may not be deployed to the same path [/../] : existing endpoint was class javax.websocket.Endpoint and new endpoint is class javax.websocket.Endpoint
at org.apache.tomcat.websocket.server.WsServerContainer.addEndpoint(WsServerContainer.java:224)
at com.liferay.websocket.whiteboard.internal.WebSocketEndpointTracker.addingService(WebSocketEndpointTracker.java:124)
at com.liferay.websocket.whiteboard.internal.WebSocketEndpointTracker.addingService(WebSocketEndpointTracker.java:45)
at org.osgi.util.tracker.ServiceTracker$Tracked.customizerAdding(ServiceTracker.java:941)
at org.osgi.util.tracker.ServiceTracker$Tracked.customizerAdding(ServiceTracker.java:1)
at org.osgi.util.tracker.AbstractTracked.trackAdding(AbstractTracked.java:256)
I am too getting the same error as below while deploying into the fresh server. I am using the Liferay whiteboard 1.0.1.

javax.websocket.DeploymentException: Multiple Endpoints may not be deployed to the same path [/o/ka/socket] : existing endpoint was class javax.websocket.Endpoint and new endpoint is class javax.websocket.Endpoint/

Can anyone have fix for this ?

Regards,
Jaydip
It seems like the issue has resolved in liferay7 GA4. While also getting the "javax.websocket.DeploymentException: Multiple Endpoints" with liferay7 tomcat GA3 I do not get the error with GA4.

In both cases the websocket whiteboard v1.0.1 was in use.
Have anybody tried running this from a real instance (not local) with domain/apache in-front of tomcat ? Seems not working for me, only on localhost environment
Hi,

is basically working (not tested thoroughly yet)
What I does is setup extra Listen port for websocket communication

Example:

Listen 88
<VirtualHost *:88>
ServerName xyz
ProxyPass / ws://localhost:8080/
</VirtualHost>

and load wstunnel module directive
LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so

Tested in CentOS 7
Hi Milan, that makes sense thanks so much but now getting 'Upgrade' header is missing, response is not returning proper websocket header, but it's using port 80, did you make any difference using 80 compared to port 88 ?
Hi Alejandro, the point of this configuration was to separate standard http communication to 80 port and websocket communication to 88 on frontend side. Only this worked for me with apache. But now I found better solution with nginx instead of apache as proxy server. All work togheter on single 80 port. Just set different rules for different paths in nginx config.