How to use STOMP heartbeats in web and spring? - spring

Now I'm using stomp to connect from web to server.
I know STOMP has a heartbeat mechanism after v1.1, also I've already set it successfully. But I don't know how to detect/catch PING/PONG frame and handle it.
The code in web like this:
this.stomp = Stomp.client(this.wsUrl);
this.stomp.heartbeat.outgoing = 5000;
this.stomp.heartbeat.incoming = 5000;
this.stomp.connect({}, (frame: any) => {
// ...
}, (error: any) => {
// ...
});
The code in server like this. we use spring stomp.
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic")
.setTaskScheduler(new DefaultManagedTaskScheduler())
.setHeartbeatValue(new long[] {5000, 5000});
config.setApplicationDestinationPrefixes("/signal");
}
I can see PING/PONG frame printed in the console panel in chrome.

Related

Flutter websocket disconnect listening

In Flutter, I wanna listen to websocket disconnect event, how to achieve that?
The websocket connect will be drop when app goes to background, I still not found a method to let it continuesly running in background (does anyone have solution?), So I have to detect if a websocket connect is lost or something, so that I can re-connect when lost connection.
Pls help if anyone knows how to achieve that.
You can find out if websocket is closed by implementing onDone callback. See the example below:
_channel = IOWebSocketChannel.connect(
'ws://yourserver.com:port',
);
///
/// Start listening to new notifications / messages
///
_channel.stream.listen(
(dynamic message) {
debugPrint('message $message');
},
onDone: () {
debugPrint('ws channel closed');
},
onError: (error) {
debugPrint('ws error $error');
},
);
Hope that helps.
If your server closes the connection just use pinginterval like this
ws.pingInterval = const Duration(seconds: 5);
onDone should be called.
basic ping pong is enough.
Other answers around SO and the web suggest that you can't just keep sockets open in the background (which seems reasonable, you'd be keeping open network connections that may affect battery life). Depending on your use case, you might be better looking at Push Notifications or something that checks on a schedule.
How to keep iphone ios xmpp connection alive while in the background?
Websocket paused when android app goes to background
https://www.quora.com/How-do-I-keep-Socket-IO-running-in-the-background-on-iOS
WebSocketChannel channel = WebSocketChannel.connect(uri );
Stream stream = channel.stream;
stream.listen((event) {
print('Event from Stream: $event');
},onError: (e){
Future.delayed(Duration(seconds: 10)).then((value) {
connectAndListen();
},);
},
onDone: (() {
Future.delayed(Duration(seconds: 10)).then((value) {
connectAndListen();
},);
})
);
I recommend you to use this multiplatform websocket package https://pub.dev/packages/websocket_universal , there you can even track all WS events happening (and even built-in ping measurment if you need any):
import 'package:websocket_universal/websocket_universal.dart';
/// Example works with Postman Echo server
void main() async {
/// Postman echo ws server (you can use your own server URI)
/// 'wss://ws.postman-echo.com/raw'
/// For local server it could look like 'ws://127.0.0.1:42627/websocket'
const websocketConnectionUri = 'wss://ws.postman-echo.com/raw';
const textMessageToServer = 'Hello server!';
const connectionOptions = SocketConnectionOptions(
pingIntervalMs: 3000, // send Ping message every 3000 ms
timeoutConnectionMs: 4000, // connection fail timeout after 4000 ms
/// see ping/pong messages in [logEventStream] stream
skipPingMessages: false,
/// Set this attribute to `true` if do not need any ping/pong
/// messages and ping measurement. Default is `false`
pingRestrictionForce: false,
);
/// Example with simple text messages exchanges with server
/// (not recommended for applications)
/// [<String, String>] generic types mean that we receive [String] messages
/// after deserialization and send [String] messages to server.
final IMessageProcessor<String, String> textSocketProcessor =
SocketSimpleTextProcessor();
final textSocketHandler = IWebSocketHandler<String, String>.createClient(
websocketConnectionUri, // Postman echo ws server
textSocketProcessor,
connectionOptions: connectionOptions,
);
// Listening to webSocket status changes
textSocketHandler.socketHandlerStateStream.listen((stateEvent) {
// ignore: avoid_print
print('> status changed to ${stateEvent.status}');
});
// Listening to server responses:
textSocketHandler.incomingMessagesStream.listen((inMsg) {
// ignore: avoid_print
print('> webSocket got text message from server: "$inMsg" '
'[ping: ${textSocketHandler.pingDelayMs}]');
});
// Listening to debug events inside webSocket
textSocketHandler.logEventStream.listen((debugEvent) {
// ignore: avoid_print
print('> debug event: ${debugEvent.socketLogEventType}'
' [ping=${debugEvent.pingMs} ms]. Debug message=${debugEvent.message}');
});
// Listening to outgoing messages:
textSocketHandler.outgoingMessagesStream.listen((inMsg) {
// ignore: avoid_print
print('> webSocket sent text message to server: "$inMsg" '
'[ping: ${textSocketHandler.pingDelayMs}]');
});
// Connecting to server:
final isTextSocketConnected = await textSocketHandler.connect();
if (!isTextSocketConnected) {
// ignore: avoid_print
print('Connection to [$websocketConnectionUri] failed for some reason!');
return;
}
textSocketHandler.sendMessage(textMessageToServer);
await Future<void>.delayed(const Duration(seconds: 30));
// Disconnecting from server:
await textSocketHandler.disconnect('manual disconnect');
// Disposing webSocket:
textSocketHandler.close();
}

RxJs webSocket not able to connect to a websocket server implemented with socket.io

I have a simple websocket server built following this example
import { createServer, Server } from 'http';
import * as express from 'express';
import * as socketIo from 'socket.io';
export class MobileObjectServer {
public static readonly PORT:number = 8081;
private app: express.Application;
private server: Server;
private io: socketIo.Server;
private port: string | number;
constructor() {
this.createApp();
this.config();
this.createServer();
this.sockets();
this.listen();
}
private createApp() {
this.app = express();
}
private createServer() {
this.server = createServer(this.app);
}
private config() {
this.port = process.env.PORT || MobileObjectServer.PORT;
}
private sockets() {
this.io = socketIo(this.server);
}
private listen() {
this.server.listen(this.port, () => {
console.log('Running server on port %s', this.port);
});
this.io.on('connect', (socket: any) => {
console.log('Connected client on port %s.', this.port);
socket.on('message', m => {
console.log('[server](message): %s', JSON.stringify(m));
this.io.emit('message', m);
});
socket.on('disconnect', () => {
console.log('Client disconnected');
});
});
}
public getApp(): express.Application {
return this.app;
}
}
This code runs smoothly. I can launch the websocket server on my machine and I can connect if I use the socket.io-client library.
Now I would like to connect to such server from a client using the webSocket and WebSocketSubject facilities provided by RxJs but I am encountering some basic problems just trying to connect.
If I do
import { webSocket } from 'rxjs/observable/dom/webSocket';
webSocket('http://localhost:8081')
nothing happens, no connection is established.
If I use 'ws://localhost:8081' as connection string then I get an error like this
WebSocketSubject.js:142 WebSocket connection to 'ws://localhost:8081/' failed: Connection closed before receiving a handshake response
I am sure I am making a very basic mistake, but I have currently no clue on where.
It is more or less impossible to connect to a socket.io server with pure web sockets. Socket.io adds a lot on top of the Websocket standard that is not compatible with pure web sockets. if you want to skip using socket.io on the client side I would advice using some other library server side, one example would be ws

Spring WebSocket - notifying subscribers is not working

I'm trying to get Spring Websockets (Spring 4) to work in the project that I've been working on. So far I have opening Websocket, adding subscribers and sending messages.
Since yesterday I've been trying to figure out why my endpoint doesn't process the message that is being send by stomp client included in my HTML. There's actually no error in the console as well (ok, I'm not 100% sure what "connected to server undefined" here means).
Here's output from Chrome console:
Opening Web Socket...
Web Socket Opened...
>>> CONNECT
accept-version:1.1,1.0
heart-beat:10000,10000
<<< CONNECTED
version:1.1
heart-beat:0,0
user-name:xxx#yyy.com
connected to server undefined
Connected: CONNECTED
user-name:xxx#yyy.com
heart-beat:0,0
version:1.1
>>> SUBSCRIBE
id:sub-0
destination:/my-project/notify-open-documents/1
Sending message : {"documentId":"1"}
>>> SEND
destination:/my-project/ws/lock-document
content-length:19
{"documentId":"1"}
and here's my code:
Configuration:
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/notify-open-documents");
registry.setApplicationDestinationPrefixes("/ws");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
stompEndpointRegistry.addEndpoint("/lock-document");
stompEndpointRegistry.addEndpoint("/lock-document").withSockJS();
}
}
The controller:
#Controller
public class DocumentWebsocketController {
#MessageMapping("/lock-document")
#SendTo("/notify-open-documents/{id}")
public Response response(#DestinationVariable("id") Long id, Message message) {
return new Response(message.getDocumentId());
}
}
and STOMP related part of my HTML:
<script type="text/javascript">
$(document).ready(function() {
connect();
});
var stompClient = null;
function connect() {
var socket = new SockJS('<spring:url value="/lock-document"/>');
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
console.log('Connected: ' + frame);
stompClient.subscribe('<spring:url value="/notify-open-documents/${id}"/>', function (messageOutput) {
console.log('Receiving message: ' + JSON.parse(messageOutput.body));
});
sendName();
});
}
function disconnect() {
if (stompClient !== null) {
stompClient.disconnect();
}
console.log("Disconnected");
}
function sendName() {
console.log("Sending message : " + JSON.stringify({'documentId' : "${id}" }));
stompClient.send('<spring:url value="/ws/lock-document"/>', {}, JSON.stringify({'documentId': "${id}"}));
}
</script>
I'm really running out of ideas what might be wrong, my browser supports WebSockets, from what I see in logs, WebSocket is being open properly, the message is being send, but what I can't figure out is why my Controller is unable to process the incoming messages.
From your HTML snippet I see you are using the spring:url tag to generate STOMP destinations. The spring:url tag adds the context path which doesn't make sense for a STOMP destination.
The application destination prefix you are configuring is /ws and the broker destination prefix /notify-open-documents, none of these will match your context path which is /my-project (from your console output). Remove the context path from your subscription destinations and when sending messages (not from the SockJS URL):
stompClient.send('/ws/lock-document', {}, JSON.stringify({'documentId': "${id}"}));

Getting 'The ConnectionId is in the incorrect format' when hybrid app connect to SignalR Hub

I have done a lot of research online but can't find the solution. I have created a SignalR hub on the WebAPI backend and enabled the CORS feature.
I also have a client (Ionic) app try to connect to the SignalR hub for real-time chat. The problem is that when the Hybrid app tries to connect to the SignalR hub, I am getting
The ConnectionId is in the incorrect format.
I am getting OnDisconnected Event on the SignalR Hub BUT not the OnConnected Event!
This is my client code (ionic):
localhost:64965/signalr is my SignalR hub
SignalR Proxy also generated on localhost:64965/signalr/hubs
var signalr_chatHub = $.connection.ChatHub;
signalr_chatHub.client.welcomeMessage = function (message) {
console.log('WelcomeMessage', message);
};
$.connection.hub.url = "http://localhost:64965/signalr";
$.connection.hub.logging = true;
$.connection.hub.start().done(function () {
console.log('signal connection connected');
}).fail(function (err) {
console.log('Could not Connect!', err);
});
Chrome Console Errors:
public class ChatHub : Hub<IChat>
{
public override Task OnConnected()
{
var connectionId = Context.ConnectionId;
return base.OnConnected();
}
public override Task OnDisconnected(bool stopCalled)
{
var connectionId = Context.ConnectionId;
return base.OnDisconnected(stopCalled);
}
public override Task OnReconnected()
{
return base.OnReconnected();
}
public void WelcomeMessage()
{
Clients.All.NewMessage("Welcome");
}
}
Cordova by any chance?
The reason this happens is because ripple creates a "cross domain proxy", this grabs all generated uri's from signalR and incorrectly encodes parts.
fix is: set ripple, settings => cross domain proxy to "disabled"
below is an example of a proxied request, note the http%3a8080...:
http://localhost:4400/ripple/xhr_proxy?tinyhippos_apikey=ABC&tinyhippos_rurl=http%3A//localhost%3A8080/signalr/start%3Ftransport%3DwebSockets%26clientProtocol%3D1.5%26connectionToken%3DAQAAANCMnd8BFdERjHoAwE%252FCl%252BsBAAAA7b4HHv1ZFkq9Xe5qXnUfYwAAAAACAAAAAAAQZgAAAAEAACAAAACU%252BVDaQ6ENxgEPcm8Tjmr39SnBszhmBjUib5UoPoXKxgAAAAAOgAAAAAIAACAAAADoJqphu6%252BQ48B4d6J6QxbK%252FqKemI3%252FJiDfnCJRKtDMuDAAAACd3g9DsBiiG3CFNcDf0maC534kevbjNczDyFFCNSHeZB%252BNfX%252FkAXX74kYLEEUeqYNAAAAAvtEsnhNjbThhsJd0L7EN%252FNsTuK7M3ijALDGtP161hI2iobBj7%252FcItg%252FQmADPDOWlKIl7SgsRXU1dLXoOumpv%252Fw%253D%253D%26connectionData%3D%255B%257B%2522name%2522%253A%2522myhub%2522%257D%255D%26_%3D1494802918927
This bug took 5 hours of my life, damn them tiny hippos.

Cross origin SignalR connection stops after negotiate

I have an MVC 5 app serving up views, and a Web API 2 app as the service layer (.NET 4.5). The Web API app uses SignalR 2.1.2 to return progress as it's processing POSTs to the service API. The two are deployed to different domains, so I've set up cross origin support as per the asp.net tutorial article.
[assembly: OwinStartup(typeof (Startup))]
namespace MyApp.Service
{
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Map("/signalr", map =>
{
//worry about locking it down to specific origin later
map.UseCors(CorsOptions.AllowAll);
map.RunSignalR(new HubConfiguration());
});
//now start the WebAPI app
GlobalConfiguration.Configure(WebApiConfig.Register);
}
}
}
WebApiConfig.cs also contains its own CORS declaration.
namespace MyApp.Service
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
//controller invocations will come from the MVC project which is deployed to a
//different domain, so must enable cross origin resource sharing
config.EnableCors();
// Web API routes
config.MapHttpAttributeRoutes();
//Snip other controller dependency initialisation
}
}
}
I've defined a simple hub class with no server-side API (it's only to allow the server to push to the clients, not for the clients to call into).
namespace MyApp.Service.Hubs
{
[HubName("testresult")]
public class TestResultHub : Hub
{
}
}
Since I'm going cross-domain AND the hub is not exposing any server side API, I'm not bothering to use a generated JS proxy.
The relevant bits of the JS that set up the signalr hub connection is: (remember this is being served up from the MVC app, which does not have any signalr support (except jquery-signalr-{version}.js of course))
function TestScenarioHandler(signalrHubUrl) {
var self = this;
//Snip irrelevant bits (mostly Knockout initialisation)
self.signalrConnectionId = ko.observable();
var hubConnection = $.hubConnection(signalrHubUrl, { useDefaultPath: false });
var hubProxy = hubConnection.createHubProxy("testresult");
hubProxy.on("progress", function(value) {
console.log("Hooray! Got a new value from the server: " + value);
});
hubConnection.start()
.done(function() {
self.signalrConnectionId(hubConnection.id);
console.log("Connected to signalr hub with connection id " + hubConnection.id);
})
.fail(function() {
console.log("Failed to connect to signalr hub at " + hubConnection.url);
});
}
Going cross-origin like this, Firefox network traffic shows (and I've confirmed Chrome shows the same thing) a GET to
http://****service.azurewebsites.net/signalr/negotiate?clientProtocol=1.5&connectionData=[{"name":"testresult"}]&_=1424419288550
Notice that the name matches the value of the HubName attribute on my hub class.
This GET returns HTTP 200, the response gives me a JSON payload containing a ConnectionId, ConnectionToken, and a bunch of other fields that suggests everything's ok. The HTTP response also has the Access-Control-Allow-Origin: header set to the domain that the GET originated from. All up it looks good, except that's where the traffic stops.
But the JS console prints "Failed to connect to signalr hub at http://****service.azurewebsites.net/signalr"
To verify I'm not doing anything too stupid, I've added signalr support and a basic hub to the MVC app (so no cross origin required), and changed the $.hubConnection() and hubConnection.createProxy() calls accordingly. When I do that, browser traffic shows the same /signalr/negotiate?... GET (obviously not cross origin any more), but then also GETs to /signalr/connect?... and /signalr/start?.... The JS console also prints a success message.
So in summary;
CORS is enabled on the service layer, and the signalr /negotiate GET returns 200, what appears to be a valid connection id, and the expected Access-Control-Allow-Origin: header. This suggests to me that the server-side CORS support is behaving itself correctly, but the signalr connection does not succeed.
When I reconfigure so the signalr connection is NOT cross origin, everything works as expected.
WTF am I missing or doing wrong?! Some conflict between HttpConfiguration.EnableCors() and IAppBuilder.UseCors(CorsOption) perhaps?
Solved it. I had changed the map.UseCors(CorsOptions.AllowAll) to pass in a CorsPolicy object instead, and set SupportsCredentials to false, having read elsewhere that Access-Control-Allow-Origin: * is incompatible with access-control-allow-credentials: true.
private static readonly Lazy<CorsOptions> SignalrCorsOptions = new Lazy<CorsOptions>(() =>
{
return new CorsOptions
{
PolicyProvider = new CorsPolicyProvider
{
PolicyResolver = context =>
{
var policy = new CorsPolicy();
policy.AllowAnyOrigin = true;
policy.AllowAnyMethod = true;
policy.AllowAnyHeader = true;
policy.SupportsCredentials = false;
return Task.FromResult(policy);
}
}
};
});
public void Configuration(IAppBuilder app)
{
app.Map("/signalr", map =>
{
map.UseCors(SignalrCorsOptions.Value);
map.RunSignalR(new HubConfiguration());
});
//now start the WebAPI app
GlobalConfiguration.Configure(WebApiConfig.Register);
}
Setting SupportCredentials to true results in the Access-Control-Allow-Origin header being rewritten with the actual origin (not *) and access-control-allow-credentials: true in the response.
And now it works.
For me following settings did good job
services.AddCors(c =>
{
c.AddPolicy("AllowCCORSOrigin", options => options
.WithOrigins("http://localhost:3000")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
);
});

Resources