Spring Websocket convertAndSendToUser doesn't work - spring

I successfully subscribe to a queue, but when sending a message, another user does not receive it
message broker configuration:
override fun configureMessageBroker(config: MessageBrokerRegistry) {
config.enableSimpleBroker("/queue", "/topic")
config.setApplicationDestinationPrefixes("/app")
}
How i send message
messageTemplate.convertAndSendToUser(recipientId.toString(), "/queue/chat", message)
Creating custom user principal on ws connection
override fun preSend(message: Message<*>, channel: MessageChannel): Message<*>? {
val accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor::class.java)
if (accessor != null && StompCommand.CONNECT == accessor.command) {
val customPrincipal = CustomPrincipal(userId)
val authentication = CustomAuthToken(customPrincipal, roles)
accessor.user = authentication
return MessageBuilder.createMessage(message.payload, accessor.messageHeaders)
}
return message
}
}
On client side i connect socket
this.stompClient.connect(headers, () => {
this.stompClient.subscribe(headers, "/user/queue/chat", (message) => {
console.log("connected");
console.log(message);
});
});
When i connect to stomp i see in browser console next message
<<< CONNECTED
user-name:1
heart-beat:0,0
version:1.2
content-length:0
user-name is my actual id for this user
I subscribe user with id 1 and user with id 2 to same queue, when i send a message from user 1 to user 2 second user doesn't receive it.
Did i miss something?

After 5 hours of debugging Spring i found how to fix this issue
Just set accessor (in preSend method) immutable
accessor.setLeaveMutable(false)
Still don't fully understand why this happend, would be useful if anyone could explain it
I didn't find anything in documentation about this

Related

AWS Websocket doesnt receive previous message until new message is sent

Most of the time the messages are passed normally, but a couple messages in particular arent recieved until the recieving client sends a message. This happens everytime for specific methods/messages, but not at all for others.
Example: user1 sends a message, user2 then sends a message to receive message from user1.
Related Material
Deleted question: websocket receives previous message only when new message is sent
Github issue: webSocket client does not receive messages before sending...
We ran into this issue and the solution had to do with how we wrote our promises. We initially used the sample code provided by Amazon
https://github.com/aws-samples/simple-websockets-chat-app/blob/master/sendmessage/app.js#L26
const postCalls = connectionData.Items.map(async ({ connectionId }) => {
try {
await apigwManagementApi.postToConnection({ ConnectionId: connectionId, Data: postData }).promise();
} catch (e) {
if (e.statusCode === 410) {
console.log(`Found stale connection, deleting ${connectionId}`);
await ddb.delete({ TableName: TABLE_NAME, Key: { connectionId } }).promise();
} else {
throw e;
}
}
});
And I'm pretty sure having an async function as a map function doesn't work properly or reliably (for whatever reason. maybe this is documented somewhere), so we changed it to a simple for loop and it fixed the issue.
for(const connection of connectionData.Items) {
const connectionId = connection.connectionId;
...same logic goes here
}

with spring boot rsocket capture the cancel frame type

I have a spring boot rsocket implementation where if a client cancels or closes their rsocket request then I want to cancel other subscription registrations on the server.
In the logs on the spring boot server I can see that a cancel message is sent or received:
WARN i.r.t.n.s.WebsocketServerTransport$1 [reactor-http-nio-3] received WebSocket Close Frame - connection is closing
INFO r.u.Loggers$Slf4JLogger [reactor-http-nio-3] cancel()
How do I capture and handle this cancel signal?
I tried cancel endpoints but these don't capture the signal:
#MessageMapping("cancel")
Flux<Object> onCancel() {
log.info("Captured cancel signal");
}
or
#ConnectMapping("cancel")
Flux<Object> onCancel2() {
log.info("Captured cancel2 signal");
}
This question on cancel subscriptions is possibly related, and this question on detecting websocket disconnection
To capture the cancel signal you can use subscribe to onClose() event.
In your controller
#Controller
class RSocketConnectionController {
#ConnectMapping("client-id")
fun onConnect(rSocketRequester: RSocketRequester, clientId: String) {
// rSocketRequester.rsocket().dispose() //to reject connection
rSocketRequester
.rsocket()
.onClose()
.subscribe(null, null, {
log.info("{} just disconnected", clientId)
//TODO here whatever you want
})
}
}
Your client needs to send the SETUP frame properly to invoke this #ConnectMapping. If you use rsocket-js you need to add a payload like this:
const client = new RSocketClient({
// send/receive JSON objects instead of strings/buffers
serializers: {
data: JsonSerializer,
metadata: IdentitySerializer
},
setup: {
//for connection mapping on server
payload: {
data: 'unique-client-id', //TODO you can receive this data on server side
metadata: String.fromCharCode("client-id".length) + "client-id"
},
// ms btw sending keepalive to server
keepAlive: 60000,
.....
}
});
It was not a well set out question. The answer is that
INFO r.u.Loggers$Slf4JLogger [reactor-http-nio-3] cancel()
is seen by a FluxSink that was setup from the original #MessageMapping endpoint.
For example:
#MessageMapping("hello")
Flux<Object> hello(#Payload String message) {
return myService.generateWorld(message);
}
In myService class
public Flux<Object> generateWorld(String hello) {
EmitterProcessor<Object> emitter = EmitterProcessor.create();
FluxSink<Object> sink = emitter.sink(FluxSink.OverflowStrategy.LATEST);
// doing stuff with sink here
sink.next(stuff());
// This part will handle a cancel from the client
sink.onCancel(() -> {log.info("********** SINK.onCancel ***********");});
return Flux.from(emitter));
}
The sink.onCancel() will handle a cancel of the flux to the hello endpoint, from the client.

User specific publish (subscriptions)

Here's a small issue I faced and couldn't find much info in the documentation. I am trying to create private chat messages. We have the following code to subscribe a user to a topic:
export const resolvers = {
Subscription: {
somethingChanged: {
subscribe: () => pubsub.asyncIterator('chat_messages'),
},
},
}
and to publish
pubsub.publish('chat_messages', { somethingChanged: { sender_id: 1, receiver_id: 2, message: 'test' }});
I have used onConnect to verify that the user is authenticated
const server = new ApolloServer({
typeDefs,
resolvers,
subscriptions: {
onConnect: (connectionParams, webSocket) => {
...
if (!authenticated) throw error
...
},
},
...
})
This works well when I want to subscribe users to a particular topic for example. But how do I implement, private user to user communication? I have tried the withFilter but can't seem to implement user specific authorization(with respect to a message) checks.
Here is a demo: https://github.com/mrdulin/apollo-server-express-starter/tree/master/src/subscription/demo-1
With these features:
jwt based auth for websocket connection
User channel which means who can receive message, who can not.
There are some conceptions you need know:
there are two types user: requestUser and subscribeUsers(include requestUser)
you should write the code in filterFn, for who can receive the message which requestUser send.
For example:
There are three subscribe users: s1(client-1), s2(client-2), s3(client-3)
When a request user(client-4) send a message(maybe mutation), you can get subscribe users and request users through context argument of filterFn.
According to these two type users' informations. You can write your own bussiness logic in filterFn to decide who can receive message, who can't.
P.S. beside context, you can get variables argument in filterFn from client. That will give more information to decide who can receive message and who can't
Sorry for my English!

Await signalr message in client

Is it possible for a SignalR client send a message to the server and then to await for a seperate message (not a return value) from the server?
The theory;
Client1 send message1 to Server and "waits" for the response.
Server processes some logic
Server sends message2 to Client1 and Client1 executes the waiting code.
Call to the server:
$.connection.myhub.server.myRequest(id).done(()==>{
// myRequest is done;
// the server has received the request but hasn't processed it yet.
// I want put some *async* code here to do something when the server has triggered $.connection.myhub.client.myResponse(id, someParam);
});
Callback to the client:
$.connection.myhub.client.myResponse(originalId, somePassedBackValue);
Can I use Async/Await, or wrap this in a Promise somehow?
If this isn't acheivable in SignalR are there anyother socket libraries that might be used instead?
You can do something, like the following:
Imagine you have a client that joins a group, updates a table and then notifies the client that it has joined.
Client
msgHub.server.joinGroup(id).done(function () {
console.log("Joined Group");
serverCallFinished = true;
})
msgHub.client.displayTable = function (table) {
display(table);
}
Hub
public async Task JoinGroup(string practiceId)
{
try
{
await Groups.Add(Context.ConnectionId, practiceId);
//Add new table
var table = new Table(practiceId)
await UpdateTable("SomeGroup", table);
}
catch (Exception e)
{
throw;
}
}
public async Task UpdateInfo(string groupName, string table)
{
//await some logic
Clients.Group(groupName).updateTable(table);
}
Update info will call the client with message2 in this case a table that it wants to display to the client. When it finishes the it will return from its awaited state by JoinGroup which will return and alert that a new user has joined a group.

myWebSocketSubject.multiplex(..).subscribe().unsubscribe() closes connection, event further observers exists

The following code will close the connection, event further observers exists on the myWebSocketSubject:
myWebSocketSubject.Observable.webSocket('ws://mysocket');
myWebSocketSubject.subscribe();
myWebSocketSubject.multiplex(..).subscribe().unsubscribe()
// the connection closed now
My expectation was, that the connection gets closed with the last unsubscribe() call (and not with the first one).
Use Case
If I get it right, with the multiplex(..) operator, on create and complete a message is send to the socket, which e.g. allows to un-/subscribe on server side to specific event.
My preferred Web Socket service could therefore look like as below. There exists only one connection, and this single connection provides several streams. On first subscription to the web socket the connection gets created; and with the last unsubscribe call the connection gets closed. For each data-stream a un-/subscribe message is sent once.
I haven't found a solution to use the WebSocketSubject.multiplex(..) method...
Preferred Example Web Socket Service
export class WebSocketService {
connection: WebSocketSubject<any>;
constructor() {
this.connection = Observable.webSocket<any>(_createConfig())
}
dataStream(eventType: string): Observable<WebSocketMessage> {
return connection.multiplex(
() => new WebSocketMessage("WebSocket.Subscribe." + eventType),
() => new WebSocketMessage("WebSocket.Unsubscribe." + eventType),
message => (message.type == eventType)
)
.retry() // reconnect on error and send subscription messages again
.share(); // send messages on last/fist un-/subscribe on this stream
}
// ...
}
export class WebSocketMessage {
type: string;
data: any;
constructor(command: string, data?:any) {
this.type = command;
this.data = data || undefined;
}
}
I have written the following test case which fails...
it('should able to handle multiple subscriptions', () => {
const subject = Observable.webSocket(<any>{url: 'ws://mysocket'});
const sub1 = subject.subscribe();
const sub2 = subject.subscribe();
const socket = MockWebSocket.lastSocket;
socket.open();
sinon.spy(socket, 'close');
sub1.unsubscribe();
// Fails, because the socket gets closed on first unsubscribe
expect(socket.close).have.not.been.called;
sub2.unsubscribe();
expect(socket.close).have.been.called;
});
If I get it right the share operator would do the trick. But after using the operator, the multiplex method is not available.
Thanks for any feedback, input, ...!

Resources