Skip to content

Support for websockets - #279

Closed
decebals wants to merge 2 commits into
masterfrom
web_socket
Closed

Support for websockets#279
decebals wants to merge 2 commits into
masterfrom
web_socket

Conversation

@decebals

@decebalsdecebals commented Jun 3, 2016

Copy link
Copy Markdown
Member

This is a functional POC/Draft for #275 .

I will start with a tiny demo (echo application using websocket).

First, in the Application class I added a new method with the signature:

publicvoidaddWebSocket(Stringpath, WebSocketHandlerwebSocketHandler);

WebSocketHandler is an interface (functional) that contains one method

voidonMessage(WebSocketContextwebSocketContext, Stringmessage);

and some default methods.

To add an echo based on websocket I must write:

// add web socketaddWebSocket("/ws/echo", (webSocketContext, message) -> {
try {
webSocketContext.sendMessage(message);
} catch (IOExceptione) {
e.printStackTrace();
}
});

If you need more control you can override other methods available in WebSocketHandler:

addWebSocket("/ws/echo", newWebSocketHandler() {
@OverridepublicvoidonMessage(WebSocketContextwebSocketContext, Stringmessage) {
System.out.println("TestWebSocket.onMessage");
System.out.println("message = " + message);
try {
webSocketContext.sendMessage(message);
} catch (IOExceptione) {
e.printStackTrace();
}
}
@OverridepublicvoidonMessage(WebSocketContextwebSocketContext, byte[] data, intoffset, intlength) {
System.out.println("TestWebSocket.onMessage");
}
@OverridepublicvoidonOpen(WebSocketContextwebSocketContext) {
System.out.println("TestWebSocket.onOpen");
}
@OverridepublicvoidonClose(WebSocketContextwebSocketContext, intcloseCode, Stringmessage) {
System.out.println("TestWebSocket.onClose");
}
@OverridepublicvoidonTimeout(WebSocketContextwebSocketContext) {
System.out.println("TestWebSocket.onTimeout");
}
@OverridepublicvoidonError(WebSocketContextwebSocketContext, Throwablet) {
System.out.println("TestWebSocket.onError");
}
});

I use a standard Route to serve the index.html file that contains the script block with the websocket client side:

GET("/", routeContext -> {
try {
routeContext.send(IoUtils.toString(WebSocketApplication.class.getResourceAsStream("/index.html")));
} catch (IOExceptione) {
e.printStackTrace();
}
});
// ORDirectoryHandlerdirectoryHandler = newDirectoryHandler("/", newFile("src/main/resources"));
GET(directoryHandler.getUriPattern(), directoryHandler);

The content of index.html file is:

<!DOCTYPE html><html><head><title>Echo WebSocket</title><metacharset="UTF-8"><metaname="viewport" content="width=device-width"></head><body><inputid="message-box" autofocusonkeypress="send(event)"/><!-- server responses get written here --><divid="messages"></div><!-- script to utilise the WebSocket --><scripttype="text/javascript">varmessages=document.getElementById("messages");// create a new instance of the webSocketvarwebSocket=newWebSocket("ws://localhost:8338/ws/echo");webSocket.onopen=function(){writeResponse("Connection opened!");};webSocket.onmessage=function(evt){writeResponse(evt.data);};webSocket.onclose=function(){writeResponse("Connection closed!");};/** * Sends the value of the text input to the server */functionsend(evt){// check for ENTER key pressedif(evt.keyCode!=13){return;}varmessageBox=document.getElementById("message-box");// read the text from the message box and send it to server via websocketwebSocket.send(messageBox.value);// clear the message boxmessageBox.value="";}functionwriteResponse(text){messages.innerHTML+="<br/>"+text;}</script></body></html>

The last step is to "inject" the WebSocketFilter in the Pippo launcher:

Pippopippo = newPippo() {
/* * Change PippoFilter with a subclass that enables WebSocket */@OverrideprotectedPippoFiltercreatePippoFilter(Applicationapplication) {
PippoFilterpippoFilter = newJettyWebSocketFilter();
pippoFilter.setApplication(application);
returnpippoFilter;
}
};

I must admit that I don't like this manual approach but for the moment is OK. Probably the correct approach is to detect if the application contains websocket routes and only in this situation we must change automatically the PippoFilter with WebSocketFilter.

This PR comes with:

  • the new websocket package in pippo-core (contains only three tiny interface and an abstract class)
  • the websocket support for JettyServer (pippo-jetty module)

Below I will add some implementation details.
First, I think it's easy to add support for Undertow and Tomcat. We must implement WebSocketFilter, WebSocketConnection and WebSocketProcessor for each server.

I added an useful AbstractWebSocketFilter that contains common logic for all websocket filters.
Were two option related to how to "inject" the websocket in Server:

  • create WebSocketFilter that extends PippoFilter
  • modify each WebServer implementation

I chose the first variant because I can use the new created filter in web.xml. With variant two, the websocket support is available only for applications that use Pippo with an embedded web server.

TODO:

  • inject WebSocketFilter automatically in Pippo launcher
  • use uriPattern as first parameter in Application.addWebSocket; now it's not possible to pass query parameters to websocket; the path parameter is static. My idea to resolve this task is to extract from DefaultRouter the part that read the parameters from a Request in a separate class - the DefaultRouter class s a little big so Single Responsability pattern for this class is welcome
  • add WebSocketContext.broadcastMessage(), for this we must register/unregister all WebSocketConnection(via onOpen and onClose methods) in a list

Any advice, question is welcome.

@coveralls

Copy link
Copy Markdown

Coverage Status

Coverage decreased (-0.3%) to 10.759% when pulling ce09af1 on web_socket into 04bc007 on master.

@coveralls

Copy link
Copy Markdown

Coverage Status

Coverage decreased (-0.3%) to 10.726% when pulling 76ace52 on web_socket into 04bc007 on master.

@gitblit

Copy link
Copy Markdown
Collaborator

I haven't looked at the code yet but your write up sounds exciting!

@decebals

Copy link
Copy Markdown
MemberAuthor
  • inject WebSocketFilter automatically in Pippo launcher

Here is a problem.
My idea was to add a new method in Application:

publicbooleanhasWebSocketHandlers() {
return !webSocketHandlers.isEmpty();
} 

Then in Pippo we can modify the content on createPippoFilter with the new code:

protectedPippoFiltercreatePippoFilter(Applicationapplication) {
PippoFilterpippoFilter; if (application.hasWebSocketHandlers()) {
// pippoFilter = new ABCWebSocketFilter();
} else {
// pippoFilter = new PippoFilter();
}
pippoFilter.setApplication(application);
returnpippoFilter;
}

The problem is that the websocket is added on Application.onInit() method but the Filter is already created in that point, it is to late to change something.

I will try to find another solution.

@decebals

Copy link
Copy Markdown
MemberAuthor

I will submit another PR, from another branch (web_socket_2) because the current branch is difficult to merge.

@decebalsdecebals mentioned this pull request Apr 20, 2017
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@decebals@coveralls@gitblit