The WebSocket Protocol enables two-way communication between a client and server. The protocol consists of an opening handshake followed by basic message framing, layered over TCP. The goal of this technology is to provide a mechanism for browser-based applications that need two-way communication with servers that does not rely on opening multiple HTTP connections.
Ninja v6.2.0+ includes comprehensive support for WebSockets. While designed to work with the standard Java WebSocket standard (JSR-356), Ninja includes a number of useful features beyond JSR-356 to make working with web sockets simpler.
Make sure you're running Ninja 6.2.0+. Ninja WebSockets will work out-of-the-box in the Jetty standalone. If running your Ninja application as a WAR. Ninja WebSockets will also work in any servlet container that supports JSR-356. Ninja's implementation has been tested against Jetty 9.3.15+, Tomcat 7.0.81+, and Wildfly 10+.
If running as a WAR in a servlet container, you'll need to swap your GuiceFilter
and use ninja.servlet.NinjaServletFilter
. Or you can simply use Ninja's
automatic servlet configuration via its ServletContainerInitializer
support
and omit even having a web.xml
.
All WebSocket endpoints will be configured in Ninja's router. A new pseudo
HTTP method of WS
indicates a route is for a WebSocket. The controller
method that handles it will be able to accept/reject the handshake and negotiate
the protocol and extensions. If you've ever used standard JSR-356 WebSockets
you'll appreciate how much easier Ninja's implementation is.
public class Routes implements ApplicationRoutes { @Override public void init(Router router) { router.WS().route("/echo").with(EchoWebSocket::handshake); router.WS().route("/chats/{id}").with(ChatWebSocket::handshake); } }
Note that you'll be slightly limited with the URI you can use depending
on what's providing your underlying Ninja WebSocket support. If using standard
JSR-356 WebSockets (almost always the case), then you'll be able to use simple path
parameters such as /chat/{id}
, but no regexes such as /chat/{id: .*}
.
The declaring class of the handshake method you specify in your route will
become the WebSocket endpoint. If you're using JSR-356 WebSockets then that
means your controller class will need to implement the javax.websocket.Endpoint
interface. See the code below for a full example.
You will want to make sure you do not mark your WebSocket class as a @Singleton
.
Unless you know what you are doing, its better to create a new instance for each
WebSocket session that is created.
Your controller/endpoint will be created using Guice – so the @Inject
annotation
will be honored when instantiating your endpoint.
One of the issues with Java's JSR-356 standard is that you have very little control with the initial handshake request and little access to the HTTP headers sent by the client. This design flaw would make using Ninja's session or context impossible.
Ninja fixes this design flaw with JSR-356 by using a two-step process to
complete the handshake. When a client initiates a WebSocket request,
Ninja will detect the attempt, instantiate your WebSocket endpoint, and call
your handshake
controller method (like any standard HTTP GET request). You
can then accept/reject the handshake request or save any variables you'll want
to access later once the WebSocket session is established.
public class ChatWebSocket extends Endpoint implements MessageHandler.Whole<String> { public Result handshake(Context context, WebSocketHandshake handshake) { // negotiate the protocol (not always used by clients) handshake.selectProtocol("chat"); // process handshake, save whatever you need, tell ninja to proceed // or return a failure result to reject the request return Results.webSocketContinue(); } @Override public void onOpen(Session session, EndpointConfig config) { // jsr-356 stuff here... }
If your handshake
method returns a status code of 101 then Ninja will
let the underlying HTTP container proceed with upgrading the connection to a
WebSocket. If the upgrade is successful, Ninja guarantees that the instance
created for your handshake will be the same one that will receive the onOpen
event.
Once the handshake completes and a WebSocket session is established, you will then handle processing like any other JSR-356 implementation. There are any number of resources on the Internet for examples.