WebSocket Handshake - Unexpected response code 200 - AngularJs and Spring Boot - java

When I tried to establish websocket communication between AngularJS app and Spring Boot I'm getting the error: Error during websocket handshake - Unexpected response code: 200.
Here is my JS code:
function run(stateHandler, translationHandler, $websocket) {
stateHandler.initialize();
translationHandler.initialize();
var ws = $websocket.$new('ws://localhost:8080/socket'); // instance of ngWebsocket, handled by $websocket service
ws.$on('$open', function () {
console.log('Oh my gosh, websocket is really open! Fukken awesome!');
});
ws.$on('/topic/notification', function (data) {
console.log('The websocket server has sent the following data:');
console.log(data);
ws.$close();
});
ws.$on('$close', function () {
console.log('Noooooooooou, I want to have more fun with ngWebsocket, damn it!');
});
}
And here is my Java code:
WebsocketConfiguration.java
#Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry)
{
stompEndpointRegistry.addEndpoint("/socket")
.setAllowedOrigins("*")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic");
}
WebsocketSecurityConfiguration.java
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages
// message types other than MESSAGE and SUBSCRIBE
.nullDestMatcher().authenticated()
// matches any destination that starts with /rooms/
.simpDestMatchers("/topic/tracker").hasAuthority(AuthoritiesConstants.ADMIN)
.simpDestMatchers("/topic/**").permitAll()
// (i.e. cannot send messages directly to /topic/, /queue/)
// (i.e. cannot subscribe to /topic/messages/* to get messages sent to
// /topic/messages-user<id>)
.simpTypeMatchers(SimpMessageType.MESSAGE, SimpMessageType.SUBSCRIBE).denyAll()
// catch all
.anyMessage().denyAll();
}
Does anyone have an idea how to fix this problem?
Thank you in advance!

I had a similiar problem, I was testing my websocket connection using an chrome plugin (Simple WebSocket Client) and was trying to connect to ws://localhost:8080/handler/ which is defined in my code as registry.addEndpoint("/handler").setAllowedOrigins("*").withSockJS(); but unexpected error 200 was occuring. I've fixed this by appending /websocket to my client request string on the chrome extension, so what you could try is to change in your JS file the following line:
var ws = $websocket.$new('ws://localhost:8080/socket');
to
var ws = $websocket.$new('ws://localhost:8080/socket/websocket');
I dont know the reason why this fixed it in my case i just randomly stumbled upon it, if some1 could clarify it more it would be really nice :)

Can you try this WebsocketConfiguration configuration:
#Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry)
{
stompEndpointRegistry.addEndpoint("/socket").setAllowedOrigins("*");
stompEndpointRegistry.addEndpoint("/socket").setAllowedOrigins("*").withSockJS();
}
so you have both websocket and SockJS configurations?

You need to use a sockJS client if you configure it to use sockJS on your websocket endpoint.

Related

Spring boot Websockets Handshake failed due to invalid Upgrade header: null

I am using spring boot 2.1.6 RELEASE, trying to use Stomp websockets for push notifications. I have taken reference from here : https://github.com/netgloo/spring-boot-samples/tree/master/spring-boot-web-socket-user-notification
Things work fine in my local. When deployed to server with an HTTPS connection, all I see is this in the log.
Handshake failed due to invalid Upgrade header: null
and on the browser
Websocket.js:6 WebSocket connection to 'wss://dev.myserver.in/ws/055/chbvjkl4/websocket' failed
I went through dozens of stackoverflow posts and almost everyone is using proxy server. I am not using any proxy servers. (Please let me know if I should be using one and why)
The code snippets:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws").withSockJS();
}
}
Here is how I have allowed the websocket requests for now
#Override
public void configure(WebSecurity web) throws Exception {
// Tell Spring to ignore securing the handshake endpoint. This allows the
// handshake to take place unauthenticated
web.ignoring().antMatchers("/ws/**");
}
The Push notification service which will be invoked on a particular action:
#Service
public class PushNotificationService {
#Autowired
private SimpMessagingTemplate simpMessagingTemplate;
/**
* Send notification to users subscribed on channel "/user/queue/notify".
* The message will be sent only to the user with the given username.
*
* #param notification The notification message.
* #param username The username for the user to send notification.
*/
public void sendPushNotification(Notifications notification, String username) {
simpMessagingTemplate.convertAndSendToUser(username, "/queue/notify", notification);
return;
}
}
On the front end:
function connect() {
// Create and init the SockJS object
var socket = new SockJS('/ws');
var stompClient = Stomp.over(socket);
// Subscribe the '/notify' channel
stompClient.connect({}, function(frame) {
stompClient.subscribe('/user/queue/notify', function(notification) {
notify(JSON.parse(notification.body));
});
});
And here is the notify
function notify(message) {
let notificationTitle = message.status;
let body = message.createdOn;
let link = message.url;
if(Notification.permission === "granted") {
showPushNotification(notificationTitle,body,link);
}
else {
Notification.requestPermission(permission => {
if(permission === 'granted') {
showPushNotification(notificationTitle,body,link);
}
});
}
}
if you not use like nginx proxy, you should configure spring boot support https.

STOMP destination url vs endpoint url

The following code is from spring mvc documentation:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/portfolio");
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/topic");
}
}
#Controller
public class GreetingController {
#MessageMapping("/greeting") {
public String handle(String greeting) {
return "[" + getTimestamp() + ": " + greeting;
}
}
The client connects to http://localhost:8080/portfolio to establish WebSocket connection, I wonder what's the exact url of client sending request?
http://localhost:8080/portfolio/app
or
http://localhost:8080/app?
and in actual WebSocket frame, does the destination header contain relative url like /app, /topic or absolute url?
[Android] https://github.com/NaikSoftware/StompProtocolAndroid
[Spring] https://docs.spring.io/spring/docs/5.1.9.RELEASE/spring-framework-reference/web.html#websocket-stomp
Just set the end point by using
addEndpoint("/portfolio");
Use the following Url to connect to websocket
ws://localhost:8080/portfolio
But remember you have to connect to socket only once and after that just invoke the endpoints without URL. Beacause socket is streamline connection and you have to establish connection only once.
setApplicationDestinationPrefixes("/app");
Above line will set the end point /app using this you can only publish over the socket. However all who has subscribed to this topic will get notified.
enableSimpleBroker("/topic");
Broker are responsible for handling subscribe and publish for both as they listen and send data in dual way means publish and subscribe both unlike /app.
private var mStompClient: StompClient? = null
mStompClient = Stomp.over(Stomp.ConnectionProvider.OKHTTP, "ws://localhost:8080/portfolio")
Connect to websocket using the above line. since we have to connect to socket end point only once write this in singleton.
val response = stomp.topic("/topic")
.subscribe { topicMessage -> }
Now above line will subscribe to your socket client means anytime you pushed the data from /topic this will this response variable will notified.
stompClient.send(StompMessage(StompCommand.SEND,
listOf(StompHeader(StompHeader.DESTINATION, "/topic")),
gson.toJson(myDataModel)))?
.subscribe()
Using above line you will you will you will send data to the socket which is specified as /topic.
#MessageMapping("/action")
fun performDeviceAction(#Payload myDataModel: MyDataModel) {}
Use the above line to receive the data from client on socket /action
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new SocketTextHandler(), "/user");
}
In order to tell Spring to forward client requests to the endpoint , we need to register the handler. Above snipplet will register a client.
Use below link and download source code for more information
https://www.javainuse.com/spring/boot-websocket

What SockJs client url to use when serving UI on webpack-dev-server?

I have the following code on the client side:
var sock = new SockJS('http://localhost:8443/quote-socket');
const stompClient = Stomp.over(sock);//connect using your client
stompClient.connect({}, onConnected, onError);
function onConnected() {
stompClient.subscribe('/topic/prices', payload => {
console.log('DATA COMING!');
console.log(payload);
});
stompClient.send("ws/quote/FB",
{},
JSON.stringify({})
);
}
function onError(error) {
console.log('there is an error');
console.log(error);
}
And this is the Spring boot (i.e. server) side:
#Override
#MessageMapping("/quote/{symbol}")
#SendTo("/topic/prices")
public String getSingleQuote(#DestinationVariable("symbol") String symbol, HttpServletResponse response) {
return "Hello";
}
And this is the web socket configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/ws");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/quote-socket").setAllowedOrigins("http://localhost:8080").withSockJS();
}
}
I can see the following logs in the browser's console:
Opening Web Socket...
Web Socket Opened...
>>> CONNECT
accept-version:1.0,1.1,1.2
heart-beat:10000,10000
Received data
<<< CONNECTED
heart-beat:0,0
version:1.2
content-length:0
connected to server undefined
>>> SUBSCRIBE
id:sub-0
destination:/topic/prices
>>> SEND
destination:ws/quote/FB
content-length:2
But the controller method doesn't get called, and I don't see the payload on the browser side. What is the problem here?
Update 1
I found out that it have to use SockJs like this:
var sock = new SockJS('api/quote-socket');
But during development I run the UI dev server (webpack-dev-server) under a different port, which means I need to use ws protocol, and as SockJS doesn't support it I have to use StopJS.client:
var url = "ws://localhost:8443/api/quote-socket";
var stompClient = Stomp.client(url);
Is there a way to use ws on SockJs? How should we with different UI server port during development, which seems to be pretty common.

Real time notifications in spring boot web socket

In my application, I need to send real time notifications to a specific user.
My WebSocketConfig class is as below,
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry.addEndpoint("/websocket-example")
.withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic");
}
}
Most of the time information will be sent by the server side. So I have not set the application destination.
In the client side, I am subscribing to the destination '/topic/user`,
function connect() {
var socket = new SockJS('/websocket-example');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/user', function (greeting) {
// showGreeting(JSON.parse(greeting.body).content);
console.log("Received message through WS");
});
});
}
In one of my RestController I have a method which broadcasts the message to all connected clients.
#GetMapping("/test")
public void test()
{
template.convertAndSend("/topic/user", "Hurray");
}
Until this part everything works fine. I receive the message and is logging to the console.
Now If I want to send the notification only to specific users, I have to use template.convertAndSendToUser(String user, String destination, String message). But I am not understanding what I should pass to the user parameter. Where and when will I get the user?
I went through couple of questions related to this, but I am not understanding the concepts clearly.
Before sending any messages to a user you need to authenticate it by the server first. There are different ways for doing this. Spring Security is a key phrase here
https://docs.spring.io/spring-security/site/docs/current/guides/html5/helloworld-boot.html
When authentication is completed you can simply get user name by calling:
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String currentPrincipalName = authentication.getName();
https://www.baeldung.com/get-user-in-spring-security
This username is part of a java.security.Principal interface. Each StompHeaderAccessor or WebSocket session object has an instance of this principle and you can get the username from it. it is not generated automatically. It has to be generated manually by the server for every session.
You can check here for more info about generating a unique id for every session.
then use like this:
#MessageMapping('/test')
public void test(SimpMessageHeaderAccessor sha)
{
String userName = sha.session.principal.name;
template.convertAndSend(userName, '/topic/user', "Hurray");
}

private messages spring websocket

I am learning spring websocket and I am stuck on how I can send messages to specific users using #DestinationVariable("username")
Here is my code
configuration
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketContextConfig extends AbstractSessionWebSocketMessageBrokerConfigurer<ExpiringSession> {
#Override
protected void configureStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-cfg").withSockJS();
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableStompBrokerRelay("/queue/","/topic","/exchange/")
.setRelayHost("localhost");
registry.setApplicationDestinationPrefixes("/app");
}
}
Controller
#MessageMapping("/chat.private.{username}")
public void filterPrivateMessage(#Payload Message message, #DestinationVariable("username") String username, Principal principal) {
this.simpMessagingTemplate.convertAndSendToUser(username, "/queue/chat.message", message);
}
the client code
var stomp = null;
stomp = Stomp.over(new SockJS("/ws-cfg"));
stomp.connect('guest', 'guest', function(frame) {
stomp.subscribe("/user/queue/chat.message", function (frame) {
dysplayMessage(JSON.parse(frame.body));
});
})
$("#sendMessage").click(function (event) {
event.preventDefault();
var message = $('#text').val();
var username="user#gmail.com";// i am lost in this part, i supose this must be the #DestinationVariable("username")
destination = "/app/chat.private."+to;
stomp.send(destination, {}, JSON.stringify({'text': message}));
$('#text').val("");
});
I am currently using websocket with spring security. How can I set the #DestinationVariable("username") on stomp.send method.
Thanks in advance.
Check out this Spring WebSocket Chat sample which has what you are looking for: https://github.com/salmar/spring-websocket-chat
The Destination Variable gets populated from the payload you are sending. In your case, you have to include it like this
stomp.send(destination, {}, JSON.stringify({'text': message, 'username' : 'User1'}));
One more observation is you don't seem to be setting the UserDestinationPrefix. you need to set it up for both the MessageBrokerRegistry and SimpMessagingTemplate.

Categories