Struts send response from AsyncContext - asynchronous

I am trying to do long polling in a struts web application. I start an AsyncContext inside an ActionSupport action method, do some time-consuming work async, and then would like to send the SUCCESS response to struts.
I know that I can do PrintWriter pw = asyncContext.getResponse().getWriter(); and write a raw response, but I would like to somehow signal struts to proceed with the predefined result in struts.xml. Is this possible?
<action name="myAction" method="action1" class="myActionClass">
<result name="success" type="redirectAction">
/pages/myPage.jsp <!-- I want to run this from async --->
</result>
</action>
In non-async action I can simply return SUCCESS and struts takes care of everything, but I am having trouble with achieving a similar effect with async action. This is what I have so far:
public void action1() {
HttpServletRequest req = ServletActionContext.getRequest();
HttpServletResponse res = ServletActionContext.getResponse();
final AsyncContext asyncContext = req.startAsync(req, res);
asyncContext.start(new Runnable() {
public void run() {
// Some time-consuming polling task is done here
asyncContext.complete();
// Can I somehow proceed to predefined struts result from here?
}
});
}

Currently it seems cannot be done clearly. I am working if I can import this support to Struts but for now, I have a hack which works. I extended StrutsExecuteFilter as below:
package me.zamani.yasser.ww_convention.utils;
import org.apache.struts2.dispatcher.PrepareOperations;
import org.apache.struts2.dispatcher.filter.StrutsExecuteFilter;
import org.apache.struts2.dispatcher.filter.StrutsPrepareFilter;
import org.apache.struts2.dispatcher.mapper.ActionMapping;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
/**
* Created by user on 8/31/2017.
*/
public class MYStrutsAsyncExecuteFilter extends StrutsExecuteFilter {
public final int REQUEST_TIMEOUT = 240000;//set your desired timeout here
private ExecutorService exe;
#Override
public void init(FilterConfig filterConfig) throws ServletException {
int size = 41;//set your desired pool size here
exe = Executors.newFixedThreadPool(
size,
new ThreadFactory() {
public Thread newThread(Runnable r) {
return new Thread(r, "My Struts Async Processor");
}
}
);
super.init(filterConfig);
}
#Override
public void doFilter(final ServletRequest req, final ServletResponse res, final FilterChain chain) throws IOException, ServletException {
final HttpServletRequest request = (HttpServletRequest) req;
final HttpServletResponse response = (HttpServletResponse) res;
if (excludeUrl(request)) {
chain.doFilter(request, response);
return;
}
// This is necessary since we need the dispatcher instance, which was created by the prepare filter
if (execute == null) {
lazyInit();
}
final ActionMapping mapping = prepare.findActionMapping(request, response);
//if recursion counter is > 1, it means we are in a "forward", in that case a mapping will still be
//in the request, if we handle it, it will lead to an infinite loop, see WW-3077
final Integer recursionCounter = (Integer) request.getAttribute(PrepareOperations.CLEANUP_RECURSION_COUNTER);
if (mapping == null || recursionCounter > 1) {
boolean handled = execute.executeStaticResourceRequest(request, response);
if (!handled) {
chain.doFilter(request, response);
}
} else {
/* I ADDED THESE */
final AsyncContext context = req.startAsync();
context.setTimeout(REQUEST_TIMEOUT);
context.addListener(new AsyncListener() {
public void onComplete(AsyncEvent asyncEvent) throws IOException {
}
public void onTimeout(AsyncEvent asyncEvent) throws IOException {
context
.getResponse()
.getWriter().write("Request Timeout");
}
public void onError(AsyncEvent asyncEvent) throws IOException {
context
.getResponse()
.getWriter().write("Processing Error");
}
public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
}
});
exe.execute(new ContextExecution(context, mapping));
}
}
private boolean excludeUrl(HttpServletRequest request) {
return request.getAttribute(StrutsPrepareFilter.class.getName() + ".REQUEST_EXCLUDED_FROM_ACTION_MAPPING") != null;
}
#Override
public void destroy() {
exe.shutdown();
super.destroy();
}
class ContextExecution implements Runnable {
final AsyncContext context;
ActionMapping mapping;
public ContextExecution(AsyncContext context, ActionMapping mapping) {
this.context = context;
this.mapping=mapping;
}
public void run() {
try {
execute.executeAction((HttpServletRequest) context.getRequest(),
(HttpServletResponse) context.getResponse(), mapping);
context.complete();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
then
<filter>
<filter-name>struts2asyncexecute</filter-name>
<filter-class>me.zamani.yasser.ww_convention.utils.MYStrutsAsyncExecuteFilter</filter-class>
<async-supported>true</async-supported>
</filter>
then put your desired async actions in a specific package and exclude them from Strut's original filter but map them to above filter in your web.xml.
I'm working to improve this to be more configurable and clear then import to Struts.
Could you please test in your app? and please feel free to let me know any idea.

Related

Liferay 6.2 spring mvc portlet to apply filter to #ResourceMapping

My Application is on Liferay 6.2 with spring mvc portlets.
I have filter for cross site scripting(XSS) for action and render mapping.
#RenderMapping , #RequestMapping and #ActionMapping
<filter>
<filter-name>MyFilter</filter-name>
<filter-class><pkg>.MyActionFilter</filter-class>
**<lifecycle>RENDER_PHASE</lifecycle>
<lifecycle>ACTION_PHASE</lifecycle>**
</filter>
<filter-mapping>
<filter-name>MyFilter</filter-name>
<portlet-name>*</portlet-name>
</filter-mapping>
But I am using #ResourceMapping for ajax call. where I am using HttpServletRequest to get the parameter value
#ResourceMapping(value = "ajaxOperation")
public void inboundOperationsAdd(ResourceRequest resourceRequest, ResourceResponse resourceResponse)
throws Exception
{
HttpServletRequest httpServletRequest = PortalUtil
.getHttpServletRequest(resourceRequest);
HttpServletRequest httpRequest= PortalUtil
.getOriginalServletRequest(httpServletRequest);
and getting the parameter value from httprequest
String parameterValue = httpRequest.getParameter("paraName");
Now I want to introduce same cross site scripting(XSS) filter for #ResourceMapping.
I tried <lifecycle>RESOURCE_PHASE</lifecycle> but it is not working.
Please guide me how to do it.
Updated on 26th Aug
the filter working for #RenderMapping , #RequestMapping and #ActionMapping
from portlet.xml
<filter>
<filter-name>FERenderActionFilter</filter-name>
<filter-class>sg.gov.frontier.filter.FERenderActionFilter</filter-class>
<lifecycle>RENDER_PHASE</lifecycle>
<lifecycle>ACTION_PHASE</lifecycle>
</filter>
<filter-mapping>
<filter-name>FERenderActionFilter</filter-name>
<portlet-name>*</portlet-name>
</filter-mapping>
And the filter
import java.io.IOException;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.filter.ActionFilter;
import javax.portlet.filter.FilterChain;
import javax.portlet.filter.FilterConfig;
import javax.portlet.filter.RenderFilter;
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
public class FERenderActionFilter implements RenderFilter, ActionFilter {
private static Log log = LogFactoryUtil.getLog(FERenderActionFilter.class);
#Override
public void init(FilterConfig filterConfig)
throws PortletException {
}
#Override
public void destroy() {
}
#Override
public void doFilter(RenderRequest request, RenderResponse response, FilterChain chain)
throws IOException, PortletException {
chain.doFilter(new FERenderRequestWrapper(request), response);
}
#Override
public void doFilter(ActionRequest request, ActionResponse response, FilterChain chain)
throws IOException, PortletException {
chain.doFilter(new FEActionRequestWrapper(request), response);
}
}
and the Wrapper 1
import javax.portlet.ActionRequest;
import javax.portlet.filter.ActionRequestWrapper;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document.OutputSettings;
import org.jsoup.safety.Whitelist;
import sg.gov.frontier.common.filter.FEParameterCleanup;
public class FEActionRequestWrapper extends ActionRequestWrapper {
public FEActionRequestWrapper(ActionRequest request) {
super(request);
}
#Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (value == null) {
return null;
}
return FEParameterCleanup.cleanXSS(value);
}
#Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values == null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = FEParameterCleanup.cleanXSS(values[i]);
}
return encodedValues;
}
}
and the Wrapper 2
import javax.portlet.RenderRequest;
import javax.portlet.filter.RenderRequestWrapper;
import sg.gov.frontier.common.filter.FEParameterCleanup;
public class FERenderRequestWrapper extends RenderRequestWrapper {
public FERenderRequestWrapper(RenderRequest request) {
super(request);
}
#Override
public String getParameter(String name) {
String value = super.getParameter(name);
if (value == null) {
return null;
}
return FEParameterCleanup.cleanXSS(value);
}
#Override
public String[] getParameterValues(String name) {
String[] values = super.getParameterValues(name);
if (values == null) {
return null;
}
int count = values.length;
String[] encodedValues = new String[count];
for (int i = 0; i < count; i++) {
encodedValues[i] = FEParameterCleanup.cleanXSS(values[i]);
}
return encodedValues;
}
}

FirebaseMessagingService singleton? Controlling the instance

I'm having a problem with use the FirebaseMessagingService. The FirebaseMessagingService works fine. I receive the messages and the notifications. But I need send the message to Activity opened, and invoke a method in activity.
I tried create a listener, but when receive a message, is created a new instance and the listener be null. I understand that FirebaseMessagingService is instanciated when have a message to receive. So I thought in singleton and listener together, don't work, the listener keep null.
Someone have a idea how I can send a message to activity opened?
I don't think any listeners are needed for this. Once you get message in onMessageReceived(), just broadcast it using LocalBroadcastmanager. and receive that broadcast in your activity.
Below is the code snippet to achieve what you want:
MyFirebaseMessagingService.java
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
Intent intent = new Intent(**Action**);
intent.putExtra("Some Payload", message.getBody());
mLocalBroadcastManager.sendBroadcast(intent);
}
MainActivity.java
private LocalBroadcastManager mLocalBroadcastManager;
private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
}
};
#Override
protected void onStart() {
super.onStart();
registerBroadcastReceiver();
}
#Override
protected void onStop() {
super.onStop();
unregisterBroadcastReceiver();
}
private void registerBroadcastReceiver() {
mLocalBroadcastManager = LocalBroadcastManager.getInstance(this);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(**Action**);
mLocalBroadcastManager.registerReceiver(mBroadcastReceiver, intentFilter);
}
private void unregisterBroadcastReceiver() {
mLocalBroadcastManager.unregisterReceiver(mBroadcastReceiver);
}
Be care full though not to exceed limitations of data sizes that can be passed using intents
refer here
https://code.google.com/p/android/issues/detail?id=5878
I understood your question with out any code. Here is pretty much a turn key solution for you that should work. I use this method a lot to pass data between services and activities.
The first thing you need to to is convert the data you want to pass to something that can be passed through an intent.
Strings are easy to work with so convert to string and put in an intent. On the activity side using onCreate and onNewItent you can receive this data no problem. Then convert it back how ever you wish. See code below for an example.
Working with broadcast receivers has the possibilities of giving you data leaks if the receiver is not unRegistered. It will happen if you app crashes and the unRegister is not told to shutdown.
In your FirebaseMessagingService class
import android.os.AsyncTask;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Map;
/**
* Created by acopp
* Date: 12/31/2016.
* Time: 1:41 PM
* You have permission to use this file for any reason that is not for evil doing
*/
public class FBMService extends FirebaseMessagingService {
static String TAG = "FBMService";
static String FBMServiceAction = "FBMService.Action";
#Override
public void onMessageReceived(RemoteMessage remoteMessage) {
MainActivity.passIntent(this,FBMServiceAction,getString(remoteMessage));
}
String getString(RemoteMessage message){
Map<String, String> messageData = message.getData();
JSONObject j = new JSONObject();
for (String key : messageData.keySet()) {
String value = messageData.get(key);
try {
j.put(key, value);
} catch (JSONException e) {
e.printStackTrace();
}
}
return j.toString();
}
}
Activity Class
//In your activity class
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* Created by acopp
* Date: 12/31/2016.
* Time: 1:41 PM
* You have permission to use this file for any reason that is not for evil doing
*/
public class MainActivity extends Activity {
private String TAG = "MainActivity";
//Call this from FBMService to start your activity or if your activity is start to receive a new intent
static void passIntent(Context context, String action, String messageDataString) {
Intent intent = new Intent(context, MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("action", action);
intent.putExtra("message", messageDataString);
context.startActivity(intent);
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "onCreate");
intentHandler(getIntent());
}
#Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
Log.d(TAG, "onNewIntent");
intentHandler(intent);
}
//Use an intent handler to manage onNewIntent and onCreate the same way
void intentHandler(Intent intent) {
if (intent.hasExtra("action")) {
String action = intent.getStringExtra("action");
if(action.equals(FBMService.FBMServiceAction)){
if (intent.hasExtra("message")) {
String messageDataString = intent.getStringExtra("message");
new iterEat().execute(messageDataString);
}
}
}
}
//Convert your string to a HashMap in the background off the main thread
class iterEat extends AsyncTask<String,Void,Map<String, String> > {
#Override
protected Map<String, String> doInBackground(String... rm) {
String messageDataString = rm[0];
try{
return fromString(messageDataString);
}catch (NullPointerException e){
return null;
}
}
#Override
protected void onPostExecute(Map<String, String> s) {
//Your data is pooped out here
Map<String, String> messageData = s;//PLOP
}
}
Map<String, String> fromString(String jsonString) throws NullPointerException{
try {
Map<String, String> messageData = new HashMap<>();
JSONObject j = new JSONObject(jsonString);
Iterator<String> i = j.keys();
while(i.hasNext()){
String key = i.next();
String value = j.getString(key);
messageData.put(key,value);
}
return messageData;
} catch (JSONException e) {
throw new NullPointerException("Didn't work");
}
}
}

How to wait cancellation of task after Service#cancel?

Look at this example:
package sample;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.stage.Stage;
public class Main extends Application {
//NOTICE: This is class in **other file** (here is just for example)
private static class MyService extends Service {
#Override
protected Task createTask() {
return new Task() {
#Override
protected Object call() throws Exception {
System.out.println("Service: START");
while(true) {
System.out.println("Service: ITERATION");
// Thread.sleep(3000); // This raise InterruptedException after cancel, but how about such code (it won't raise exception):
for(long i = 0; i < 1_000_000_000; i++) {
}
if (isCancelled())
break;
}
System.out.println("Service: END");
return null;
}
};
}
}
#Override
public void start(Stage primaryStage) throws Exception {
MyService myService = new MyService();
myService.start();
Thread.sleep(5000);
myService.cancel();
System.out.println(myService.getState()); // Here is `CANCELLED` already but task isn't finished yet.
// <--- How to wait cancellation of Task here?
System.out.println("This command must be called after `Service: END`");
Platform.exit();
}
public static void main(String[] args) {
launch(args);
}
}
As you known call of Service#cancel doesn't wait cancellation of Task. So, I want to block main thread and await cancellation of Task. How can I do it?
P.S.
Looks like Service doesn't provide any callback/event handler to check real cancellation of Task. Is it right?
By default, Service.cancel() interrupts the Task. So an InterruptedException must be raised and your task will be terminated (forcefully).
One thing you could do is to store the created task in a global variable in your MyService class and override the cancel method like this:
class MyService extends Service {
private Task t;
#Override
public boolean cancel() {
if (t != null) {
return t.cancel(false);
} else {
return false;
}
}
#Override
protected Task createTask() {
t = new Task() { /* ... */ };
return t;
}
}
The rest will be easy. Add a change listener to the service state property (or use setOnCanceled() method) and do whatever you want to do after the state change, in the callback.
Never block the FX Application Thread.
The Service class does indeed define a setOnCancelled(...) method, which you use to register a callback:
myService.setOnCancelled(event -> {
System.out.println("Service was cancelled");
});
Note that when you cancel a Service, it will interrupt the thread if it is blocked. So if you don't catch the InterruptedException it will not exit the call method normally. This is why you don't see the "END" message.
Full example code:
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Service;
import javafx.concurrent.Task;
import javafx.stage.Stage;
import javafx.util.Duration;
public class ServiceCancellationTest extends Application {
//NOTICE: This is class in **other file** (here is just for example)
private static class MyService extends Service<Void> {
#Override
protected Task<Void> createTask() {
return new Task<Void>() {
#Override
protected Void call() throws Exception {
System.out.println("Service: START");
while(! isCancelled()) {
System.out.println("Service: ITERATION");
try {
Thread.sleep(3000);
} catch (InterruptedException interrupted) {
System.out.println("Task interrupted");
}
if (isCancelled())
break;
}
System.out.println("Service: END");
return null;
}
};
}
}
#Override
public void start(Stage primaryStage) throws Exception {
MyService myService = new MyService();
myService.start();
myService.setOnCancelled(event -> {
System.out.println("In cancelled callback: "+myService.getState()); // Here is `CANCELLED` already but task isn't finished yet.
});
// You should never block the FX Application Thread. To effect a pause,
// use a pause transition and execute the code you want in its
// onFinished handler:
PauseTransition pause = new PauseTransition(Duration.seconds(5));
pause.setOnFinished(event -> {
myService.cancel();
System.out.println("After calling cancel: "+myService.getState());
System.out.println("This command must be called after `Service: END`");
Platform.exit();
});
pause.play();
}
public static void main(String[] args) {
launch(args);
}
}

How to quickly stop seda in camel

I have a camel route with a splitter (using streaming) that sends messages to a seda queue to be processed. When I'm trying to stop the application gently, the seda queue doesn't stop immediately, it is processing all the messages before finally shutting down.
What can I do to stop it right away?
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.camel.Exchange;
import org.apache.camel.Processor;
import org.apache.camel.builder.ExpressionBuilder;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.main.Main;
public class MySedaShutdownTest extends RouteBuilder {
#Override
public void configure() throws Exception {
onException(Exception.class)
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
System.out.println("exception");
}
});
from("timer:myTimer?repeatCount=1")
.split(ExpressionBuilder.beanExpression(new MySplitter(), "myIterator"))
.streaming()
.to("seda:mySeda");
from("seda:mySeda")
.throttle(1)
.process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
System.out.println("processing: " + exchange.getIn().getBody()
+ "; app status: " + exchange.getContext().getStatus());
}
});
}
public static class MySplitter {
public Iterator<String> myIterator() {
List<String> values = new ArrayList<String>();
for (int i = 0; i < 10; i++) {
values.add("string nr : " + i);
}
System.out.println("in myIterator");
return values.iterator();
}
}
public static void main(String[] a) throws Exception {
final Main main = new Main();
new Thread(new Runnable() {
#Override
public void run() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println("invoking shutdown");
main.shutdown();
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
System.out.println("starting app");
main.enableHangupSupport();
main.addRouteBuilder(new MySedaShutdownTest());
main.run();
}
}
There is a purgeQueue method on the SedaEndpoint. So you can get the endpoint and call this method. You can also access it from JMX.
A bit related we have this ticket for improvement
https://issues.apache.org/jira/browse/CAMEL-5911
And I logged a ticket for this
https://issues.apache.org/jira/browse/CAMEL-6405
Just add string below as first line of your configure() method:
getContext().getShutdownStrategy().setTimeout(1);
It will reduce shutdown timeout from 300 seconds (default) to 1
See more info on controlling start-up and shutdown of routes.

Is PlayN websocket implementation in Java supposed to be working?

According to the platform status, it should. I am trying to accomplish a basic Hello World proof of concept but I can't get my Java client connect to my server with PlayN. As soon as I call createWebSocket(), the onClose() gets called. However, I was able to connect to my server using a standard html page.
Client code:
WebSocket s = PlayN.net().createWebSocket("ws://localhost:8080/test", new Net.WebSocket.Listener(){
public void onClose() {System.out.println("close");};
public void onDataMessage(ByteBuffer msg) {System.out.println("data");};
public void onError(String reason) {System.out.println("error");};
public void onOpen() {System.out.println("open");};
public void onTextMessage(String msg) { System.out.println("text");};
});
Here is sample WebSocket Servlet implementation on Jetty:
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocketFactory;
public class SampleServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
private WebSocketFactory wsFactory;
private final Set<SampleWebSocket> members = new CopyOnWriteArraySet<SampleWebSocket>();
#Override
public void init() throws ServletException {
wsFactory = new WebSocketFactory( new WebSocketFactory.Acceptor() {
#Override
public boolean checkOrigin( final HttpServletRequest request, final String origin ) {
// Allow all origins
return true;
}
#Override
public WebSocket doWebSocketConnect( final HttpServletRequest request, final String protocol ) {
return new SampleWebSocket();
}
} );
wsFactory.setBufferSize( 4096 );
wsFactory.setMaxIdleTime( 60000 );
}
#Override
protected void doGet( final HttpServletRequest request, final HttpServletResponse response ) throws IOException {
if ( wsFactory.acceptWebSocket( request, response ) ) {
return;
}
response.sendError( HttpServletResponse.SC_SERVICE_UNAVAILABLE, "Websocket only" );
}
public class SampleWebSocket implements WebSocket.OnTextMessage {
private volatile Connection connection;
public SampleWebSocket( ) {
}
#Override
public void onClose( final int closeCode, final String message ) {
members.remove( this );
System.out.println( "onClose: closeCode=" +closeCode+ ", message: '" +message+ "'" );
}
#Override
public void onOpen( final Connection connection ) {
this.connection = connection;
System.out.println( "onOpen: connection=" +connection );
members.add( this );
// Send sample binary message back
try {
connection.sendMessage( "Sample message" );
} catch ( final IOException e ) {
e.printStackTrace();
}
}
#Override
public void onMessage( final String data ) {
System.out.println( "onMessage: data=" +data );
// Relay message to other connected clients
for ( final SampleWebSocket member: members ) {
try {
member.connection.sendMessage( data );
} catch ( final IOException e ) {
e.printStackTrace();
}
}
}
}
}
I finally found the problem. Version 1.4 of PlayN uses Draft10 dy default. I have updated my local copy of PlayN to use : http://mvnrepository.com/artifact/com.netiq/websocket and I changed the default Draft to Draft17 and it could connect to Tomcat.

Resources