Search code examples
javawebsocketplayframework

How do I create WebSockets with Play framework 2.6?


I am trying to do the following with Play Framework 2.6:

  1. The browser targets the server and a WebSocket is created
  2. Later on (after some other request is performed), the servers sends a message to the browser via the WebSocket previously created

Point 1 can be easily done with a route:

public WebSocket socket() {
    return WebSocket.Text.accept(request -> {
        // Log events to the console
        Sink<String, ?> in = Sink.foreach(System.out::println);

        // Send a single 'Hello!' message and then leave the socket open
        Source<String, ?> out = Source.single("Hello!").concat(Source.maybe());

        return Flow.fromSinkAndSource(in, out);
    });
}

and the WebSocket can be saved server side.

  1. But then how can I send data via the WebSocket? (triggered server side)

This was easy to do with 2.5 but the documentation is not very helpful for Play 2.6.


Solution

  • I've managed to implement websocket with help of Akka actors. At first step define actor that will handle messages

    public class WebSocketActor extends AbstractActor {
    
        private final ActorRef out;
    
        @Inject
        public WebSocketActor(ActorRef out) {
            this.out = out;
        }
    
        @Override
        public Receive createReceive() {
            return receiveBuilder()
                    .match(String.class, message ->
                            out.tell("Sending message at " + LocalDateTime.now().toString(), self())
                    )
                    .build();
        }
    
        public static Props props(final ActorRef out) {
            return Props.create(WebSocketActor.class, out);
        }
    }
    

    This actor will be created per client. ActorRef out will send message to connected client. In this example response is send to client on each string message passed to WebSocketActor.

    Now define API endpoint to open access to websocket for clients. Define ActorFlow that will create new instance of actor on new connection

    public WebSocket ws() {
        return WebSocket.Text.accept(request -> ActorFlow.actorRef((out) -> WebSocketActor.props(out), actorSystem, materializer));
    }
    

    According to source code ActorFlow creates actors with flowActor name. So to send message to websockets somewhere in the code we can find actors by their path. This will broadcast message to all connected clients

    actorSystem.actorSelection("/user/*/flowActor").tell("Hello", ActorRef.noSender());
    

    Unfortunately I didn't find easy way to change ActorFlow default name but maybe this answer may help you play-scala-akka-websockets-change-actor-path.

    Also you can check play-java-websocket-example project from playframework examples.