I'm trying to develop a Web socket library for Processing.org, and I want to use the Tyrus implementation.
I have managed to make the client work without any issues. The problems arrive around the Web socket server - it simply seems unnecessarily complex, and cumbersome to create a server (I have developed servers in both Python, and node.js previously without any real issues).
In essence I want to have a simple Web socket server class I can instantiate in Processing. The way I imagine it to be is as follows (a Processing sketch):
import websockets.*;
WSServer ws;
void setup(){
ws= new WSServer(this,"localhost",8025); //this=PApplet (Processing specific detail)
ws.sendMessage("A message"); //How I want to send messages to all clients
}
void draw(){
}
//Event listener for incoming messages
void webSocketServerEvent(String msg){
println(msg);
}
At first it is not necessary to focus on specifics like sub-protocols and the like. I simply want a Processing developer to be able to spin up a simple web socket server, stating the fully qualified URI in the constructor, the port, and define the onMessage eventlistener method.
I have now been trying to solve this for a few days, and I simply can't get my head around it. When using the Tyrus Annotated endpoint approach I would say that the code is quite clean, and understandable, but everything seems completely sandboxed. I can't introduce my own constructor with the parameters I want, and I can't figure out how to set the server URI programatically. When I can't introduce my own existing objects in the server endpoint, I can't propagate messages to my event listener method from above (webSocketServerEvent). Additionally, the URI needs to be hardcoded as a parameter in the annotation (@ServerEndpoint(value = "/game")), which is a nice feature, but to me this would only be useful in very specific situations, and not the common use case.
My preliminary conclusion on the annotation approach is that it is very sandboxed, and primarily developed for standalone situations, where there is no need to be integrated into an other Java project. I'm fully aware that this conclusion can't be correct, which is exactly why I'm asking you guys to help me understand how to go about the issue.
I have also been looking into the programmatic approach to Tyrus, and is seems like it is actually possible to configure your server to your needs. But this is where it becomes completely confusing for me - this is where the entire Java Web socket approach becomes unnecessarily complex and cumbersome (comparing to other languages).
First of all, I have a class extending Endpoint, then I need to create a new class implementing ServerApplicationConfig, and I finally need to create a ServerEndpointConfig. I then need to have a main class somewhere else, to actually initiate the entire stack. I do recognise that this is typical Java behavior, but why do I need to create these completely arbitrary configuration classes and objects? I now run into an other complexity on defining the server endpoint URI, since I now have to define the base URI and port in the instantiation of the web socket server, and I then need to add a suffix in the ServerEndpointConfig (ServerEndpointConfig.Builder.create(MyEndpoint.class, "/websocket")), meaning that I now have two places to define a single URI.
On top of the above I now run into the same issues as with the annotation approach regarding the class that extends Endpoint. This class defines all the events like onOpen, onClose etc., which is quite nice from a devision of code perspective. The issue is that it seems like I can't provide my own constructor here as well. When instantiating the web socket server I can only refer to the Endpoint class and not provide any user data. I have found two different examples of this:
container.connectToServer(MyClient.class, URI.create(uri));
Server server = new Server("localhost", 8025, "/websockets", WordgameServerEndpoint.class);
As a final remark to the programmatic endpoint, it seems completely unnecessary to force the definition of the onMessage event to happen through an addMessageHandler that often times seems to be called from within onOpen:
@Override
public void onOpen(final Session session, EndpointConfig ec) {
session.addMessageHandler(new MessageHandler.Whole<String>() {
@Override
public void onMessage(String text) {
try {
session.getBasicRemote().sendText(text);
} catch (IOException ex) {
Logger.getLogger(MyEndpoint.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
I have tried quite a few different approaches now, and my preliminary code is as follows:
package websockets;
import java.lang.reflect.Method;
import javax.websocket.CloseReason;
import javax.websocket.DeploymentException;
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
import org.glassfish.tyrus.server.Server;
import processing.core.PApplet;
public class WebsocketServer3 {
private PApplet parent;
private Method webSocketServerEvent;
private Session userSession = null;
public WebsocketServer3(PApplet parent, String domain, int port, String uri){
this.parent=parent;
this.parent.registerMethod("dispose", this);
Server server = new Server(domain, port, uri, WebsocketServerInstance.class);
try {
server.start();
} catch (DeploymentException e) {
e.printStackTrace();
}
try {
webSocketServerEvent = parent.getClass().getMethod("webSocketServerEvent", String.class);
} catch (Exception e) {
// no such method, or an error.. which is fine, just ignore
}
}
public void dispose(){
// Anything in here will be called automatically when
// the parent sketch shuts down. For instance, this might
// shut down a thread used by this library.
}
public void sendMessage(String mes){
userSession.getAsyncRemote().sendText(mes);
}
@ServerEndpoint("/")
public class WebsocketServerInstance {
@OnOpen
public void onOpen(Session session) {
userSession=session;
System.out.println("HI!!");
}
@OnMessage
public void onMessage(String message, Session session) {
if (webSocketServerEvent != null) {
try {
webSocketServerEvent.invoke(parent, message);
} catch (Exception e) {
System.err.println("Disabling webSocketServerEvent() because of an error.");
e.printStackTrace();
webSocketServerEvent = null;
}
}
}
@OnClose
public void onClose(Session session, CloseReason closeReason) {
System.out.println(String.format("Session %s closed because of %s",
session.getId(), closeReason));
}
}
}
I have tried to make the ServerEndpoint into an inner class, which doesn't work (I know there exists an other post on this issue, but I haven't been able to solve my issue from that post). The reason why I post the above code, is to provide an example of how I would like the coding procedure to be.
All-in-all, I'm quite confused about how to create a simple web socket server with Tyrus (or any of the other Java implementations for that matter - the one I got furthest with is the one from TallNate, but there seems to be some issues with that library as well - it's quite understandable, but the issues arise when i call the run method - different discussion though).
My overall question is if anyone could point me in a direction, on how to solve my problem?
Thanks in advance
Lasse