I have been diligently getting all my code up to date for the release of Learning Spring Boot 2nd Edition this September. Digging into Chapter 8, WebSockets with Spring Boot, I realized I had a bigger challenge than expected.
I’m not sure you’re aware what this means. “Message” was a paradigm invented by Mark Fisher in the early days of Spring Integration with a nice little container class called Message<T> that included a payload and optional headers. This was handy when it came to enterprise service buses, but people spotted the paradigm as more universally applicable.
Hence, they put a messaging layer on top of WebSockets, making it super easy to pipe messages from clients to the server and back. Anywhere in the server side code, you can get your hands on a SimpMessagingTemplate and publish a message, targeted for either a server side or client side endpoint. The runtime would handle it all.
And none of this was available with Spring 5’s WebFlux-based WebSocket solution. As Rossen said, “think of it as streams of WebSocket messages.”
That was tricky.
So I dug in and started learning the API. In the reference docs, they show how to register a WebSocketHandler. You tie that API to a URL and inside the API, are handed a WebSocketSession. I kind of stared at this API for five minutes before noodling around with it.
I copy and pasted the code into my book’s code. Tweaked the JavaScript to hook up. Fired things up, and started sending in messages. And it worked.
Until I opened a second tab sporting a different WebSocket session. That’s when I noticed that the messages posted by one tab didn’t appear in the other.
And then I KNEW what they meant by “EchoServer”. Receiving the messages on a session and sending them right back ONLY WORKS ON THE SAME SESSION.
I shook my head, remembering the fact that Spring 4’s WebSocket configurations SHOWS you configuring a broker. That’s what is needed!
I already had a broker. It was right there.
You see, I had gambled on putting Spring Cloud Stream in my book when it was in its infancy. In Chapter 7, AMQP Messaging with Spring Boot, I kick things off with Spring AMQP’s RabbitTemplate, which is great for small stuff. As if often the case Spring’s template approach makes the AMQP APIs very pliable. They also adopted the Message paradigm from Spring Integration so you can either send your own POJOs, or you can do it Message<Pojo> style, which moves your abstraction up a level, making things simpler.
But Spring Cloud Stream is even better. It moves things up even higher. You aren’t thinking in terms of message brokers. Instead, you are thinking about chaining together streams of messages. (Which, by the way, dovetails PERFECTLY with Reactor).
And I was already doing that!
Now I’m no genius. This is stuff that had I just spoken with Rossen and Marius (lead for Spring Cloud Stream) they would have pointed out in no time. But there is that thrill of discovering something for yourself that is unbeatable.
So I hammer away at a service that listens for WebSocket messages and pipes them into a broker via Spring Cloud Stream. (Thanks Artem for showing me to do THAT with Reactor!) I code another Spring service that listens for a stream of messages coming from the broker and pipes them out to a WebSocket stream.
And I’m done. In maybe 20 minutes. (I did goof up by not pointing these two servers at the same AMQP exchange).
Poof! (Plus, the concept feels totally righteous. Streams of messages, flowing through the system.)
That’s when another realization hits me. When I previously drafted this chapter, I attempted to use Spring WebSocket’s RabbitMQ option, but never could get it to properly bind to my RabbitMQ instance. Sadly, I switched to their in memory broker. This meant the solution wouldn’t work if you ran multiple instances in the cloud.
THIS SOLUTION WILL!
Because it’s piping stuff right into RabbitMQ, it will work perfectly. (Partly thanks to Consumer Groups).
To wrap up the chapter, I even went so far as to show off SimpMessagingTemplate’s sendToUser API. It nicely lets you send a message to a single user. I coded a little “@bob Did you get this?” magic, where it would parse the @’d user, and then convertAndSendToUser(parsedUser). Well, I had none of that API, remember?
How can I pull this off? Must be too much, right? Wrong! Since every message is traveling to everyone, and it’s using the Message<T> construct, it takes no effort to add a header including the original user’s name.
The broker-to-client service can simply parse the message and compare against the user or themselves, and decided whether or not to let it on through. (Send a copy to both parties!) It’s basically an extra filter layered on top, which is why Reactor makes this type of thing so easy to apply.
And I know this is rock solid not because of what I coded. But because of the powerful underlying concepts orchestrated by Rossen, Mark Fisher, Artem, Gary, and Marius.
Can’t wait to apply security filtering in the next chapter to these WebSocket streams.
0 Comments