I am new to both Spring Boot and Websockets.
I am comfortable with Java and have read a few things on Websockets and Spring Boot framework.
I need to communicate with a Web socket and get the data in an existing Spring Boot web app.
Can anyone let me know where I can start and any good online resources ? I did Google, but most of the examples are difficult for me to grasp in a short time. If possible, also explain it to me conceptually.
Thanks in advance
To create a basic websockeet applicaiton in spring boot
You will need to:
- First, Enable Websocket support, For example by using:
#Controller
public class GreetingController {
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(1000); // simulated delay
return new Greeting("Hello, " + message.getName() + "!");
}
}
- Second, Create a message-handling controller:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").withSockJS();
}
}
- Create a browser client
function connect() {
var socket = new SockJS('/gs-guide-websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/greetings', function (greeting) {
showGreeting(JSON.parse(greeting.body).content);
});
});
}
I think you can start with this example, https://spring.io/guides/gs/messaging-stomp-websocket/
Check out my answer to a similar question:
SockJS Java Client Implementation for non-web application
Within, you can find an example of a client able to send/receive websocket data.
Related
I created a websocket communication between my Front-End which is coded in Vuejs and my Back-end which is coded in Spring Boot. Any user would authenticate in the first place ( the authentication is done without the websockets and a JWT token is passed to the user ) .And then a candidate would start a test (it's an e-learning platform) and the questions would be passed through a full-duplex flow with websockets. The client which is the Vuejs instance would submit the answer and the back-end would give him the next question. Well, the entire flow works. But I couldn't figure out how to secure the websockets. I haven't used neither STOMP neither SockJS for this and actually I haven't found a single ressource about this.
What I'd like to do is make a check on the user's role before giving him the permission to connect onto the websocket.
If the user has the privileges to access then the connection is successful
else it's a forbidden error (403).
Here is my code, I've used the Spring Boot websocket-starter annotations #ÒnOpen,#OnMessage, #OnClose and #OnError.
Those are some pieces of Code that may help out in finding the solution :
WebsocketConfiguration :
#Configuration
public class WebsocketConfiguration {
#Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
WebSocket.java(Contains the logic):
I removed the logic so that it doesn't become too much code to read.
public class WebSocket {
private Session session;
#Autowired
QuestionRepository questionRepository;
private static CopyOnWriteArraySet<WebSocket> webSockets =new CopyOnWriteArraySet<>();
private static Map<String,Session> sessionPool = new HashMap<String,Session>();
#OnOpen
public void onOpen(Session session, #PathParam(value="tentativeId")String tentativeId) {
}
#OnClose
public void onClose() {
webSockets.remove(this);
System. out. println ("[websocket message] disconnected, total:"+webSockets. size ());
}
#OnMessage
public void onMessage(String message,#PathParam(value="tentativeId") String tentativeId) throws JsonMappingException, JsonProcessingException {
}
// This is a broadcast message.
public void sendAllMessage(String message) {
for(WebSocket webSocket : webSockets) {
System.out.println ("[websocket message] broadcast message:"+message);
try {
webSocket.session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// This is a single message
public void sendOneMessage(String tentativeId, String message) {
Session session = sessionPool.get(tentativeId);
if (session != null) {
try {
session.getAsyncRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
}
I have a spring-boot service that provides a STOMP endpoint like so
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/entityUpdates").withSockJS();
}
}
In the Angular frontend, I use this package to connect to the socket:
const url = 'ws://localhost:8080/entityUpdates';
const webSocketClient = new Client({ brokerURL: url });
webSocketClient.activate();
This throws following errors:
WebSocket connection to 'ws://localhost:8080/entityUpdates' failed:
My best guess is that the URL is somehow wrong, or I missed something when I registered the STOMP endpoint, but I can't figure out what it would be.
I'm implementing a simple socket like this :
#MessageMapping("/hello")
#SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
Thread.sleep(1000); // simulated delay
return new Greeting("Hello, " + message.getName() + "!");
}
From the client-side :
function connect() {
var socket = new SockJS('/gs-guide-websocket');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/greetings', function (greeting) {
showGreeting(JSON.parse(greeting.body).content);
});
});
}
And the websocket configuration
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket").withSockJS();
}
}
Now I would like to apply authentication for the socket so that not all clients can connect to server. I may look like
var socket = new SockJS('/gs-guide-websocket?token= a JWT token'); //or sth similar
from the client-side.
Let just assume that I can hide the token from people who inspect the frontend code, how do I get that token and verify it from the server side ? (Provided that I have the function to verify the JWT already)
Or is there any better way of implementing security for socket connection that you can suggest ?
I have followed Quetion1 and Quetion2 from stack overflow to send messages to specific client, based on its sessionId but could not find success.
Below is my sample RestController class
#RestController
public class SpringSessionTestApi {
#Autowired
public SimpMessageSendingOperations messagingTemplate;
#MessageMapping("/messages")
public void greeting(HelloMessage message, SimpMessageHeaderAccessor headerAccessor) throws Exception {
String sessionId = (String) headerAccessor.getSessionAttributes().get("SPRING.SESSION.ID");
messagingTemplate.convertAndSendToUser(sessionId,"/queue/test",message, createHeaders(sessionId));
}
private MessageHeaders createHeaders(String sessionId) {
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(sessionId);
headerAccessor.setLeaveMutable(true);
return headerAccessor.getMessageHeaders();
}
}
Session Id: when client sends createSession request, new spring sessionId is generated and same is stored in MongoDB as well. After that when client sends web socket connect request, same sessionId is received which was stored in mongoDb as expected. Till This everything is working fine.
Now my job is to send response back to the client based on the sessionId.
For that I have below web socket class:
#Configuration
#EnableScheduling
#EnableWebSocketMessageBroker
public class WebSocketConfig extends
AbstractSessionWebSocketMessageBrokerConfigurer<ExpiringSession> {
#Override
protected void configureStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/messages");
}
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/queue");
registry.setApplicationDestinationPrefixes("/app");
}
}
and the sample client code that I am using to connect is:
function connect() {
stompClient = Stomp.client('ws://localhost:8016/messages');
stompClient.debug = null;
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/user/queue/test', function (greeting) {
console.log("Hello "+greeting);
console.log("Greeting body "+JSON.parse(greeting.body));
});
});
}
Please help, Where I am doing wrong in this?
Thanks in Advance!
If you are using /user channel as you do, try to pass the user as stated here.
#MessageMapping("/messages")
public void greeting(HelloMessage message, SimpMessageHeaderAccessor headerAccessor, Principal principal)
throws Exception {
messagingTemplate.convertAndSendToUser(principal.getName(), "/queue/test", message);
}
I've found a full workable Spring Stomp Chat project in git, the link is here. You can refer to it.
https://gist.github.com/theotherian/9906304
I am building a Stateless Spring (4.2.4.RELEASE) Solution using STOMP over Websockets with SockJS and a Rest Endpoint using JWT to connect mobile devices with Full Duplex communication. I am using Tomcat 8.0.33 as a Web Server and testing using html with sockjs javascript client. The stomp protocol works fine using the http fallback but I can't make it using only a websocket protocol. I tried CORS in many ways but I am not sure that is a Tomcat Problem or just bad spring configuration. I tested my html even in the same domain and port and SockJS is still falling back into xhr or iframes.
WebScoketConfig.java
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer
{
#Override
public void registerStompEndpoints(StompEndpointRegistry registry)
{
RequestUpgradeStrategy upgradeStrategy = new TomcatRequestUpgradeStrategy();
registry.addEndpoint("/ws").setHandshakeHandler(new DefaultHandshakeHandler(upgradeStrategy))
.setAllowedOrigins("*").withSockJS().setSessionCookieNeeded(false)
.setStreamBytesLimit(512 * 1024)
.setHttpMessageCacheSize(1000)
.setDisconnectDelay(30 * 1000);
}
#Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
registration.taskExecutor().corePoolSize(50);
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry)
{
registry.enableSimpleBroker("/queue/", "/topic/");
// registry.enableStompBrokerRelay("/queue/", "/topic/");
registry.setApplicationDestinationPrefixes("/myapp");
}
public void configureWebSocketTransport(WebSocketTransportRegistration registration) {
registration.setMessageSizeLimit(500 * 1024);
registration.setSendBufferSizeLimit(1024 * 1024);
registration.setSendTimeLimit(20000);
}
}
WebSecurityConfig.java
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
{
#Override
protected void configure(HttpSecurity http) throws Exception
{
http.csrf().disable()
.authorizeRequests()
.antMatchers("/**").permitAll();
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
}
}
I solved my problem, actually the code was good but the antivirus (Kaspersky) was closing the connection on my client browser just after opened. It forces SockJS to fallback into a different strategy. I tested the client with the antivirus turned off and the Websocket transport was beautifully running. Tested on mac & linux as well.