We built some non-trivial WebSocket abstractions around Jetty 8's
WebSockets. I found the API to be intuitive since it matched RFC 6455
pretty closely. We have been delaying an upgrade to Jetty 9 because it
appears to be a lot of effort to rewrite our WebSocket library based on the
new Jetty WebSocket abstractions.

If you don't mind helping out, I would love to share a bit of how we use
Jetty and to try to come up with a reasonable path for migration to Jetty
9. I apologize in advance for the length of this email, but this is a
pretty large system.


The quick summary is that there are three changes that are giving us the
most trouble:

* The WebSocket.OnFrame interface, with the ability to return false in
order to delegate to Jetty's usual handling. There appears to be no
comparable mechanism in Jetty 9's WebSockets' annotated methods.

* Changing WebSocket.Connection to a Session callback parameter. It's
unclear whether I can use the Session outside the scope of a callback
method.

* The WebSocketServlet's doWebSocketConnect(HttpServletRequest req, String
protocol) method. We actually passed off the `req` to Spring in order to
make our mapped method calls, and we can no longer do this very easily, as
Jetty 9 no longer passes an HttpServletRequest.


The (simplified) detailed usage follows:

We use Spring @RequestMapping-annotated methods for our HTTP requests, and
I created a similar abstraction for WebSocket. To use a WebSocket, one can
define a method like this in a @Controller-annotated class:

@WebSocketMapping("/upload/receiveFiles.ws")
public WebSocketHandler receiveFiles(
        @ModelAttribute("user") User user,
        @RequestParam("upload") int uploadId) {
    return new FileUploadHandler(...);
}


A WebSocketHandler is internally defined, providing the following fields
and methods:

WSConnection connection;
void onOpen();
void onClose();
interface HandleData {
    void onDataMessage(Data data);
}
interface HandleText {
    void onTextMessage(String msg);
}
interface HandleBinary {
    void onBinaryMessage(byte[] data, int start, int end);
}


All in all, this is pretty similar to Jetty 8's callback methods, and we'd
like to retain them. WSConnection is a thin wrapper around Jetty 8's
WebSocket.Connection (which is also gone now), providing:

void sendData(Object obj);
void sendText(Object msg);
void sendBinary(byte[] data, int start, int end);


You'll notice a separate type, that we call "Data". This allows us to use
Google's Gson library to serialize an object to JSON, and then to
deserialize it into a Javascript object on the frontend, and vice versa in
the other direction.

We accomplish this by having a "WebSocketAdapter" that maps our WebSocket
interface to Jetty 8's, implementing WebSocket, WebSocket.OnTextMessage,
and WebSocket.OnFrame.


Our WebSocketAdapter.onFrame method is the most interesting and is the
hardest to replicate using Jetty 9. We handle Binary messages in the
obvious way: by simply calling WebSocketHandler.onBinaryMessage and
returning true. Text message processing, however, proceeds as follows:

* Initial state: The message must be either "TEXT" or "DATA", continuing to
the associated state and returning true (we've handled the message).

* TEXT state: Returns false for all of the frames of the next text message
so that Jetty will call onMessage. Of course, the onMessage implementation
calls our  WebSocketHandler.onTextMessage callback). When the message is
complete, return to Initial.

* JSON state: Aggregate all frames efficiently so that they can later be
deserialized from the resulting JSON. When the message is complete, we
return to Initial state and call the WebSocketHandler.onDataMessage with
the aggregated data.

So, it is important to us to be able to distinguish between two different
types of text messages, and moreover, we use this mechanism to send very
large JSON objects over the WebSocket.

I don't expect this particular change to be very difficult to implement,
but the interface has taken a small step back for my usage in this case.


Incidentally, we also send periodic KEEPALIVE text messages from a server
thread to the client. Our client-side WebSocket library just drops them,
but they ensure that the connection stays open during long computations. I
haven't looked a lot into the replacement for WebSocket.Connection
(Session), but I'm really hoping that I can send data across the Session
outside the scope of the callback method. If not, the KEEPALIVE mechanism
becomes much more difficult to implement.


The harder change to deal with is our WebSocketServlet implementation. At
server startup, we register all the methods annotated with
@WebSocketMapping, binding them to the appropriate address. When
doWebSocketConnect(HttpServletRequest req) is called, we fetch the
appropriate method, and we use Spring's

    HandlerMethodInvoker.invokeHandlerMethod(method, controller, req,
modelMap)

in order to actually call the method. This is really nice because it allows
us to use the same @RequestParam and @ModelAttribute parameters on our
@WebSocketMapping-annotated methods that we use on normal Spring
@RequestMapping methods.

Now that Jetty 9 does not provide the HttpServletRequest to the
WebSocketServlet, it no longer appears to be possible to leverage Spring in
order to call our @WebSocketMapping-annotated methods.


In general, Jetty 9's WebSocket API seems to be an improvement for using
the API directly. Using annotations and things is clearly in the vein of
Spring and Hibernate and other popular Java libraries. When trying to build
on top of it, though, I find myself wishing for an API more like Jetty 8,
where I can just implement some interfaces and build my own abstractions on
top of WebSocket.

Is there any hope going forward for a similar low-level, RFC 6455-like API
that was provided in Jetty 8? Alternatively, am I doing things in a way
that would be much more easily implemented by following another pattern?

Thank you very much to anyone who may have made it this far.

Cheers,
Brandon
_______________________________________________
jetty-users mailing list
[email protected]
https://dev.eclipse.org/mailman/listinfo/jetty-users

Reply via email to