websocket handhsake exception: 200 - spring-websocket

I am using springboot to setup my websocket endpoint and angularJs to connect to my endpoint. which i have done before and it worked fine that time. but now when i do it in new project. its giving me hand shake error. below is mycode:
Websocket config
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chatService").setAllowedOrigins("*").addInterceptors(new HttpSessionHandshakeInterceptor());
}
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setApplicationDestinationPrefixes("/app");
registry.enableSimpleBroker("/chat");
}
}
angularJs code:
var socket = new WebSocket('ws://192.168.225.133:9191/chatService');
// console.log(socket);
stompClient = Stomp.over(socket);
stompClient.connect({}, function(frame) {
if(frame.command=="CONNECTED"){
//alert(frame);
//showLoader(false);
stompClient.subscribe('http://localhost:9191/chat/messages', function(response) {
var data = JSON.parse(response.body);
chat.gif=false;
console.log("data_json...");
if(data.to==$scope.admin.id)
$scope.getMessagesById(data.from,1);
else
$scope.getMessagesById(data.to,1);
console.log(data);
$scope.myImgs=null;
});
}
else{
var r = confirm("Could not connect! Retry?");
if (r == true) {
connect();
}
}
});
}

in my project there was a client forward controller. disabling that fixed the issue.

Related

Spring Websocket: how to subscribe to a topic and fetch the last message?

I have the following spring websocket stomp client:
var client = new StandardWebSocketClient();
WebSocketStompClient stompClient = new WebSocketStompClient(client);
MappingJackson2MessageConverter converter = new MappingJackson2MessageConverter();
converter.setObjectMapper(mapper);
stompClient.setMessageConverter(converter);
StompSessionHandler sessionHandler = new StompSessionHandler() {
#Override
public void afterConnected(final StompSession session, final StompHeaders connectedHeaders) {
session.subscribe("/topic/senha", this);
}
...
#Override
public void handleFrame(final StompHeaders headers, final Object payload) {
//get notification here
}
};
stompClient.connect(uri.toString(), sessionHandler);
Server:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(final MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(final StompEndpointRegistry registry) {
registry.addEndpoint("/ws");
}
}
I get notified in handleFrame about new messages.
But the topic has messages in it's history.
I want to fetch the last message/state from the topic when connected. How to do it?

keycloak backchannel logout spring session that use redis as session store

I use keycloak as a Central Authentication Service for (single sign on/out) feature.
I have app1, app2, app3. app1 and app2 is monothetic application. app3 use spring session (use redis as session store),
All feature work fine. But I use the back channel to logout for SSO(single sign out) feature, that's works for app1 and app2. But it not work for this app3.
I wonder how to back channel logout application that use spring session
The keycloak admin url invoke when client user send a logout request to it.I find that KeycloakAutoConfiguration#getKeycloakContainerCustomizer() inject WebServerFactoryCustomizer for add KeycloakAuthenticatorValve, and that Valve
use CatalinaUserSessionManagement, but it have not any info about redis as its session store. So I add a customizer for enhence the Valve.
first i set the order of the autoconfig, because extra customizer must be callback after it.
#Slf4j
#Component
public class BeanFactoryOrderWrapper implements DestructionAwareBeanPostProcessor {
#Override
public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException {
}
#Override
public boolean requiresDestruction(Object bean) {
return true;
}
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("getKeycloakContainerCustomizer")) {
Object wrapRes = this.wrapOrder(bean);
return wrapRes;
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
private Object wrapOrder(Object bean) {
log.info("rewrite keycloak auto config customizer Order for next custom");
final WebServerFactoryCustomizer origin = (WebServerFactoryCustomizer) bean;
return new KeycloakContainerCustomizerWithOrder(origin);
}
}
class KeycloakContainerCustomizerWithOrder implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory>, Ordered {
private final WebServerFactoryCustomizer origin;
public KeycloakContainerCustomizerWithOrder(WebServerFactoryCustomizer origin) {
this.origin = origin;
}
#Override
public void customize(ConfigurableServletWebServerFactory factory) {
origin.customize(factory);
}
#Override
public int getOrder() {
return Ordered.LOWEST_PRECEDENCE - 1;
}
}
I extra RedisIndexedSessionRepository, and set it to proxy object
#Slf4j
#Configuration
#RequiredArgsConstructor
class ContainerConfig {
private final RedisIndexedSessionRepository sessionRepository;
#Bean
public WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> getKeycloakContainerCustomizerGai() {
return configurableServletWebServerFactory -> {
if (configurableServletWebServerFactory instanceof TomcatServletWebServerFactory) {
TomcatServletWebServerFactory container = (TomcatServletWebServerFactory) configurableServletWebServerFactory;
container.getContextValves().stream().filter(ele -> ele.getClass() == KeycloakAuthenticatorValve.class).findFirst().map(ele -> (AbstractKeycloakAuthenticatorValve) ele).ifPresent(valve -> {
try {
final Field field = AbstractKeycloakAuthenticatorValve.class.getDeclaredField("userSessionManagement");
field.setAccessible(true);
final CatalinaUserSessionManagement origin = (CatalinaUserSessionManagement) field.get(valve);
field.set(valve, new CatalinaUserSessionManagementGai(origin, sessionRepository));
} catch (Exception e) {
log.error("enhence valve fail");
}
});
}
};
}
}
#Slf4j
class CatalinaUserSessionManagementGai extends CatalinaUserSessionManagement {
private final CatalinaUserSessionManagement origin;
private final RedisIndexedSessionRepository sessionRepository;
public CatalinaUserSessionManagementGai(CatalinaUserSessionManagement origin, RedisIndexedSessionRepository sessionRepository) {
this.origin = origin;
this.sessionRepository = sessionRepository;
}
public void login(Session session) {
origin.login(session);
}
public void logoutAll(Manager sessionManager) {
origin.logoutAll(sessionManager);
}
public void logoutHttpSessions(Manager sessionManager, List<String> sessionIds) {
for (String sessionId : sessionIds) {
logoutSession(sessionManager, sessionId);
}
}
protected void logoutSession(Manager manager, String httpSessionId) {
try {
final Method method = CatalinaUserSessionManagement.class.getDeclaredMethod("logoutSession", Manager.class, String.class);
method.setAccessible(true);
method.invoke(origin,manager,httpSessionId);
} catch (Exception e) {
log.error("session manager proxy invoke error");
}
// enhence part
sessionRepository.deleteById(httpSessionId);
}
protected void logoutSession(Session session) {
try {
final Method method = CatalinaUserSessionManagement.class.getDeclaredMethod("logoutSession", Session.class);
method.setAccessible(true);
method.invoke(origin,session);
} catch (Exception e) {
log.error("session manager proxy invoke error");
}
}
public void sessionEvent(SessionEvent event) {
origin.sessionEvent(event);
}
}
that work for me

Reply back to exactly same client who connected and called with SignalR

I am having a two tier application, with one being Windows Form application and other being Web Application with MVC.
Desktop Application has a SignalR Hub area which manages all client connected to it from Web App.
Hub Class
public delegate void ClientConnectionEventHandler(string clientId);
public delegate void ClientNameChangedEventHandler(string clientId, string newName);
public delegate void ClientInitializeEventHandler(string clientId);
public class StockTickerHub:Hub
{
static ConcurrentDictionary<string, string> _users = new ConcurrentDictionary<string, string>();
public static event ClientConnectionEventHandler ClientConnected;
public static event ClientConnectionEventHandler ClientDisconnected;
public static event ClientNameChangedEventHandler ClientNameChanged;
public static event ClientInitializeEventHandler ClientInitialized;
//Called when a client is connected
public override Task OnConnected()
{
_users.TryAdd(Context.ConnectionId, Context.ConnectionId);
ClientConnected?.Invoke(Context.ConnectionId);
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
string username;
_users.TryRemove(Context.ConnectionId, out username);
ClientDisconnected?.Invoke(Context.ConnectionId);
return base.OnDisconnected(stopCalled);
}
public void SetUserName(string userName)
{
_users[Context.ConnectionId] = userName;
ClientNameChanged?.Invoke(Context.ConnectionId, userName);
}
public void InitializeGrid()
{
ClientInitialized?.Invoke(Context.ConnectionId);
}
}
When web client connects to Desktop App, it's being added and connected.
Inherited Hub Class
public class ClientGateway
{
private BindingList<ClientItem> _clients = new BindingList<ClientItem>();
frmMasterTicker frm;
public ClientGateway()
{
//Register to hub events
StockTickerHub.ClientConnected += StockTickerHub_ClientConnected;
StockTickerHub.ClientNameChanged += StockTickerHub_ClientNameChanged;
StockTickerHub.ClientDisconnected += StockTickerHub_ClientDisconnected;
StockTickerHub.ClientInitialized += StockTickerHub_ClientInitialized;
}
private void StockTickerHub_ClientInitialized(string clientId)
{
InitializeGrid(clientId);
}
private void StockTickerHub_ClientDisconnected(string clientId)
{
var client = _clients.FirstOrDefault(x => x.Id == clientId);
if (client != null)
{
_clients.Remove(client);
}
}
private void StockTickerHub_ClientNameChanged(string clientId, string newName)
{
//Update client's name if it's available
var client = _clients.FirstOrDefault(x => x.Id == clientId);
if (client != null)
{
client.Name = newName;
SetOperationLogMessage.AddLogMessage(this.ToString(), "", $"Client name changed. Id:{clientId}, Name:{newName}");
SendTestMessage();
}
}
private void StockTickerHub_ClientConnected(string clientId)
{
//Add client to the list
_clients.Add(new ClientItem() { Id = clientId, Name = clientId });
SetOperationLogMessage.AddLogMessage("ClientGateway", "StockTickerHub_ClientConnected", $"Client connected:{clientId}");
}
public void SendTestMessage()
{
var hubContext = GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>();
hubContext.Clients.All.addMessage("Ticker Server", "Hello handshake from server.");
}
public void InitializeGrid(string connectionid)
{
if (_clients.Count > 0)
{
frm = (frmMasterTicker)Helper.GetOpenForm("frmMasterTicker");
//string msg = "Hello from server at " + DateTime.Now.ToString();
string msg = JsonConvert.SerializeObject(frm.GetInitializeDataFromGrid());
var hubContext = GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>();
hubContext.Clients.Client(connectionid).initializeGrid(msg);
}
}
public void SendTickerData(object lstStock)
{
if (_clients.Count > 0)
{
//string msg = "Hello from server at " + DateTime.Now.ToString();
string msg = JsonConvert.SerializeObject(lstStock);
var hubContext = GlobalHost.ConnectionManager.GetHubContext<StockTickerHub>();
hubContext.Clients.Client("").getTickerData(msg);
}
}
}
Web Page Code
//Connect to SignalR server and get the proxy
function connect() {
$.connection.hub.url = url;
simpleHubProxy = $.connection.stockTickerHub;
if (simpleHubProxy) {
$.connection.hub.start().done(function () {
writeToLog("Connected...");
simpleHubProxy.server.setUserName("RMSAdmin");
RequestGridInitialData();
})
.fail(function () {
alert("Can't connect");
})
;
simpleHubProxy.client.addMessage = function (name, message) {
writeToLog(name + ":" + message);
}
simpleHubProxy.client.initializeGrid = function (message) {
dtSource = JSON.parse(message);
$("#grid").data("kendoGrid").dataSource.data(dtSource);
}
simpleHubProxy.client.getTickerData = function (message) {
writeToLog(message);
}
$.connection.hub.disconnected(function () {
writeToLog("Server disconnected.");
});
$.connection.hub.reconnecting(function () {
writeToLog("Server reconnecting...");
});
$.connection.hub.reconnected(function () {
writeToLog("Server reconnected...");
});
$.connection.hub.error(function (error) {
console.log('SignalR error: ' + error)
});
}
}
connect();
I can get the connection id from Hub.
Now I have a Windows Form. In which, I want to send data to exactly the same client who connected recently. I have a list of all clients connection with id. But within that, who connected recently and to whom I need to send data from Form, I am unable to do the progress with.
Following is a code try inside a Windows Form, which works, but it sends data to all connected client.
hubContext.Clients.All.getTickerData(JsonConvert.SerializeObject(tmpStock));
I want to send data only to that client who connects recently. How should I do that?
In the piece of code where you are sending the windows form data, you can call:
hubContext.Clients.Caller.getTickerData(JsonConvert.SerializeObject(tmpStock));
The .Caller will send the message to the client that invoked the method.
You can read more about it, in the oficial microsoft documentation, here: https://learn.microsoft.com/en-us/aspnet/signalr/overview/guide-to-the-api/hubs-api-guide-server#selectingclients
Edit: Since you don't have access to the .Caller method outside the hub you need to save the ClientId inside the hub and pass it the the outside class... SO there you will be able to call the caller client by id:
hubContext.Clients.Client(clientId).getTickerData(JsonConvert.SerializeObject(tmpStock));

Asp.net SignalR not working while deployed in Azure

I am using Asp.Net signal for sending user specific notification. Everything working fine in debug mode using visual studio but the same breaks while deployed to Azure.
I am using redis cache.
Startup.cs
using Microsoft.AspNet.SignalR;
using Microsoft.Owin;
using Owin;
[assembly: OwinStartup(typeof(NotifSystem.Web.Startup))]
namespace NotifSystem.Web
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
GlobalHost.DependencyResolver.UseStackExchangeRedis(new RedisScaleoutConfiguration("mySrver:6380,password=password,ssl=True", "YourServer"));
app.MapSignalR();
}
}
}
My Hub Class:
using Microsoft.AspNet.SignalR;
using NotificationHub.Models.Hubs;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace NotificationHub.Hubs
{
public class NotificationHub : Hub
{
private static readonly ConcurrentDictionary<string, UserHubModels> Users =
new ConcurrentDictionary<string, UserHubModels>(StringComparer.InvariantCultureIgnoreCase);
//private NotifEntities context = new NotifEntities();
//Logged Use Call
public void GetNotification()
{
try
{
string loggedUser = Context.User.Identity.Name;
//Get TotalNotification
//string totalNotif = LoadNotifData(loggedUser);
//Send To
UserHubModels receiver;
if (Users.TryGetValue(loggedUser, out receiver))
{
var cid = receiver.ConnectionIds.FirstOrDefault();
var context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
context.Clients.Client(cid).broadcaastNotif();
}
}
catch (Exception ex)
{
ex.ToString();
}
}
//Specific User Call
public void SendNotification(string SentTo,string Notification)
{
try
{
//Get TotalNotification
//string totalNotif = LoadNotifData(SentTo);
//Send To
UserHubModels receiver;
if (Users.TryGetValue(SentTo, out receiver))
{
var cid = receiver.ConnectionIds.FirstOrDefault();
var context = GlobalHost.ConnectionManager.GetHubContext<NotificationHub>();
context.Clients.Client(cid).broadcaastNotif(Notification);
}
}
catch (Exception ex)
{
ex.ToString();
}
}
private string LoadNotifData(string userId)
{
return userId;
int total = 0;
//var query = (from t in context.Notifications
// where t.SentTo == userId
// select t)
// .ToList();
total = 6;
return total.ToString();
}
public override Task OnConnected()
{
string userName = Context.User.Identity.Name;
string connectionId = Context.ConnectionId;
var user = Users.GetOrAdd(userName, _ => new UserHubModels
{
UserName = userName,
ConnectionIds = new HashSet<string>()
});
lock (user.ConnectionIds)
{
user.ConnectionIds.Add(connectionId);
if (user.ConnectionIds.Count == 1)
{
Clients.Others.userConnected(userName);
}
}
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
string userName = Context.User.Identity.Name;
string connectionId = Context.ConnectionId;
UserHubModels user;
Users.TryGetValue(userName, out user);
if (user != null)
{
lock (user.ConnectionIds)
{
user.ConnectionIds.RemoveWhere(cid => cid.Equals(connectionId));
if (!user.ConnectionIds.Any())
{
UserHubModels removedUser;
Users.TryRemove(userName, out removedUser);
Clients.Others.userDisconnected(userName);
}
}
}
return base.OnDisconnected(stopCalled);
}
}
}
Javascript Code:
var hub = $.connection.notificationHub;
hub.client.broadcaastNotif = function (notification) {
setTotalNotification(notification)
};
$.connection.hub.start()
.done(function () {
console.log("Connected!");
hub.server.getNotification();
})
.fail(function () {
console.log("Could not Connect!");
});
});
function setTotalNotification(notification) {
if (notification) {
GetUnreadNotificationCount();
$('#m_topbar_notification_icon .m-nav__link-icon').addClass('m-animate-shake');
$('#m_topbar_notification_icon .m-nav__link-badge').addClass('m-animate-blink');
}
else {
$('#m_topbar_notification_icon .m-nav__link-icon').removeClass('m-animate-shake');
$('#m_topbar_notification_icon .m-nav__link-badge').removeClass('m-animate-blink');
}
}
I have enabled Websocket for that particular App Service.
Cross user notification sending is not successful it only works if the logged in user sends notification to himself only.
Update:
I checked that while a logged in user is doing an activity so that the notification goes to that particular user then it works. Like if a user user1 sends a notification to user1 then there is no issue.
We had same problem with our Azure SignalR redis BackPlane POC.
But We tried redis with No SSL port then the Azure SignalR redis BackPlane started working fine. Please check the screenshot Below. Now since the enviorment is self contained we do not need it even HTTPS. We are managing it by Resource Groups and Port Whitelisting.

Calling a Client Method on a Windows Service

I have a SignalR client in a Windows Service that successfully calls a Server method in an MVC app. First the Server Code:
public class AlphaHub : Hub
{
public void Hello(string message)
{
// We got the string from the Windows Service
// using SignalR. Now need to send to the clients
Clients.All.addNewMessageToPage(message);
// Send message to Windows Service
}
and
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
ConfigureAuth(app);
app.MapSignalR("/signalr", new HubConfiguration());
}
}
The Windows Service client is:
protected override async void OnStart(string[] args)
{
eventLog1.WriteEntry("In OnStart");
try
{
var hubConnection = new HubConnection("http://localhost.com/signalr", useDefaultUrl: false);
IHubProxy alphaProxy = hubConnection.CreateHubProxy("AlphaHub");
await hubConnection.Start();
await alphaProxy.Invoke("Hello", "Message from Service");
}
catch (Exception ex)
{
eventLog1.WriteEntry(ex.Message);
}
}
It sends a message to the MVC Server. Now I want to call the other way from server to client. The Client Programming Guide has the following code examples which will NOT work as this is not a desktop.
WinRT Client code for method called from server without parameters (see WPF and Silverlight examples later in this topic)
var hubConnection = new HubConnection("http://www.contoso.com/");
IHubProxy stockTickerHubProxy = hubConnection.CreateHubProxy("StockTickerHub");
stockTickerHub.On("notify", () =>
// Context is a reference to SynchronizationContext.Current
Context.Post(delegate
{
textBox.Text += "Notified!\n";
}, null)
);
await hubConnection.Start();
How can I call a method on the client?
The .NET client side code seems fine. You can simply get rid of Context.Post since your client is running inside of a Windows Service and doesn't need a SyncContext:
protected override async void OnStart(string[] args)
{
eventLog1.WriteEntry("In OnStart");
try
{
var hubConnection = new HubConnection("http://localhost.com/signalr", useDefaultUrl: false);
IHubProxy alphaProxy = hubConnection.CreateHubProxy("AlphaHub");
stockTickerHub.On("Notify", () => eventLog1.WriteEntry("Notified!"));
await hubConnection.Start();
await alphaProxy.Invoke("Hello", "Message from Service");
}
catch (Exception ex)
{
eventLog1.WriteEntry(ex.Message);
}
}
You can invoke the "Notify" callback from inside your AlphaHub on the server like so:
public class AlphaHub : Hub
{
public void Hello(string message)
{
// We got the string from the Windows Service
// using SignalR. Now need to send to the clients
Clients.All.addNewMessageToPage(message);
// Send message to the Windows Service
Clients.All.Notify();
}
Any client will be able to listen to these notifications since we are using Clients.All. If you want to avoid this, you need some way to authenticate your Windows Service and get its ConnectionId. Once you have that, you can send to the Windows Service specifically like so:
Clients.Client(serviceConnectionId).Notify();
Hope this helps.
Windows Service with self hosted SignalR
public partial class MyWindowsService : ServiceBase
{
IDisposable SignalR { get; set; }
public class SignalRStartup
{
public static IAppBuilder App = null;
public void Configuration(IAppBuilder app)
{
app.Map("/signalr", map =>
{
map.UseCors(CorsOptions.AllowAll);
var hubConfiguration = new HubConfiguration()
{
// EnableDetailedErrors = true
};
map.RunSignalR(hubConfiguration);
});
}
}
public MyWindowsService()
{
InitializeComponent();
}
protected override void OnStart(string[] args) { Start(); }
protected override void OnStop() { Stop(); }
public void Start()
{
SignalR = WebApp.Start<SignalRStartup>("http://localhost:8085/signalr");
CallToMvcJavascript();
}
public new void Stop()
{
SignalR.Dispose();
}
private void CallToMvcJavascript(){
GlobalHost.ConnectionManager.GetHubContext<MyHub>().Clients.All.addNotice(// object/data to send//);
}
}
The Hub in the Windows Service
public class MyHub : Hub
{
public void Send()
{
Clients.All.confirmSend("The service received the client message");
}
}
The Javascript
$.connection.hub.logging = true;
$.connection.hub.url = "http://localhost:8085/signalr";
var notices = $.connection.myHub;
notices.client.addNotice = function(notice) {
console.log(notice);
};
notices.client.confirmSend = function(msg) {
alert(msg);
};
$.connection.hub.start().done(function() {
$('#myTestBtn').on('click', function() {
notices.server.send();
});
});

Resources