Spring WebFlux as a new module introduced in Spring 5.0 which provides a new programming model for developers, most of the features that existed in Spring WebMVC have been ported to the WebFlux stack, including WebSocket support.
WebSocket is a standalone spec defined in RFC6455, which provides bi-direction non-blocking communication between clients and server-side.
In this post, we will start creating a simple chat application which uses Spring WebFlux based WebSocket APIs to build the server-side, and uses Angular as a client to communicate with the server-side. Initially, we will use a Reactor specific Sink as the message queue, and then we will switch to use the trailable cursor on the capped collections in MongoDB to simplify the work.
As introduced in my original post , Spring WebFlux embraces ReactiveStreams spec, heavily depends on Project Reactor . The WebSocket API in Spring WebFlux is not so rich as the one in Spring WebMVC, eg. it lacks general controller support and there is no way to adapt the STOMP protocol. In the Google result of “spring webflux websocket”, you will find most of the solutions are based on the Reactor ‘s Processor, eg. How To Build a Chat App Using WebFlux, WebSockets & React is a great article to introduce the usage of WebSocket in Spring WebFlux, for more info about the UnicastProcessor
and other processors in Reactor, check How to use Processor in Reactor Java from Manh Phan.
Firstly let’s create the server side. Generate a project skeleton using Spring Initializr.
Hit the Generate button to download the generated archive, and extract it into your local disk.
Make sure you have installed the latest JDK (AdoptOpenJDK is highly recommended) 14, then import the source codes into your favorite IDE, eg. Intellij IDEA. IDEA will resolve the dependencies and build the project automatically.
To enable WebSocket in Spring WebFlux application, just declare a simple WebSocketHandlerAdapter
bean.
@Bean
fun handlerAdapter(): WebSocketHandlerAdapter = WebSocketHandlerAdapter()
And set up the WebSocket endpoints in a HandlerMapping
bean.
@Bean
fun webSocketMapping(mapper: ObjectMapper): HandlerMapping? {
val map = mapOf("/ws/messages" to ChatSocketHandler(mapper))
val simpleUrlHandlerMapping = SimpleUrlHandlerMapping().apply {
urlMap = map
order = 10
}
return simpleUrlHandlerMapping
}
Here we will use a custom ChatSocketHandler
to receive from and send message to the endpoint /ws/messages. WebSocket supports text and binary based payload in the message, here we only use text message, we will convert our message to json string by Jackson ObjectMapper
.
Let’s have a look at the complete codes of ChatSocketHandler
.
class ChatSocketHandler(val mapper: ObjectMapper) : WebSocketHandler {
val sink = Sinks.replay<Message>(100);
val outputMessages: Flux<Message> = sink.asFlux();
override fun handle(session: WebSocketSession): Mono<Void> {
println("handling WebSocketSession...")
session.receive()
.map { it.payloadAsText }
.map { Message(id= UUID.randomUUID().toString(), body = it, sentAt = Instant.now()) }
.doOnNext { println(it) }
.subscribe(
{ message: Message -> sink.next(message) },
{ error: Throwable -> sink.error(error) }
);
return session.send(
Mono.delay(Duration.ofMillis(100))
.thenMany(outputMessages.map { session.textMessage(toJson(it)) })
)
}
fun toJson(message: Message): String = mapper.writeValueAsString(message)
}
#angular #spring #reactive-programming #websocket