In Elm, how can i open a websocket based on user input? - websocket

I have a broker that accepts websocket connections, and routes messages to that connection based on url query parameters.
I thought about writing a client (in Elm) that accepts input from the user, then opens a websocket connection based on that input. How would i do this using the (high level) Websocket module (if possible)?

This can easily be achieved using the Websocket module. Since subscriptions are updated when the model is updated, it is just a matter of yielding a list of subscriptions created by Websocket.listen. If a user action adds a value to the model, that value can be used to create a new subscription.
type alias Model =
{ servers: List Url }
subscriptions model =
Sub.batch (List.map (\url -> Websocket.listen url ...) model.servers)

Related

How to correctly implement security on a websocket session?

I want to implement an asynchronous mechanism using websockets.
Here's the idea:
The client performs a REST call
The server returns a "subscribingID" and starts a background process
The client registers as subscriber on this topic (suppose 12232442 is the id):
this.stompClient.subscribe('/callback/12232442', (messageOutput) => {
let mess = JSON.parse(messageOutput.body);
console.log(mess);
});
Once done the server simply sends the message and closes the connection:
stompSession.send("callback/12232442", new MessageOutput());
It should work but here's the catch: how can I be sure that another client can't simply subscribe to an ID that exists but does not belong to them?
Also, is there any built-in mechanism to achieve this?
When the server receives a REST request for a subscription ID, you can store the newly generated ID in a Subscription HashMap.
In order to do processing when a new subscription request comes you can implement a custom StompEventHandler, like so
#Controller
public class StompEventHandler{
#EventListener
public void handleSubscription(SessionSubscribeEvent event) {
//Get incoming sessionDetails from event.
//get the destination.
// Validate that the destination is present in Subscription HashMap
// and also that no client maps to the topic id.
// Based on the result either send the message or send Unauth message to
client.
}
}
Documentation
Note that you have to store details about session ID of the client as well for this. Instead of broadcasting the message to /topic/callback/<your_id>, you would need to send the message to destination like so: /user/queue/callback/<your_id>. For sending to a destination as such you would need to use simpMessagingTemplate.convertAndSendToUser(username, destination, payload, Headers)
Good Read for this
So since you are sending messages to only a particular session of a particular user, your messages are confidential.
If you want to ensure that you do not even have the subscription from the client you can send an UNSUBSCRIBE message to the client in the StompEventHandler class. This would force unsubscribe the client.
Good Read for this

How do establish a connection to a channel in feathersjs?

I'm new to node and to feathersjs, and for my first app, I'm trying to have different parts of it communicate using channels. I understand the operations and how they're used, but I don't understand how to establish a connection to a channel in the first place.
For example, here's some code from the official documentation:
app.on('login', (payload, { connection }) => {
if(connection && connection.user.isAdmin) {
// Join the admins channel
app.channel('admins').join(connection);
// Calling a second time will do nothing
app.channel('admins').join(connection);
}
});
Where does "connection" come from? There is no built-in function (unless I'm missing something obvious) in feathersjs to do this.
Thanks!
Channel is used in feathers to achieve real time.
In the server you need to configure socketio. Then it also requires the client to be connected to the server via socketio.
Where does "connection" come from?
connection is a js object that represents the connection the user has established by logging in.
Try doing a console.log(connection) to see what it contains.
connection is in this case passed by the Feathers framework in the function call to the function that you have quoted.
Once you have obtained this connection object then you can use it for adding the user to a channel, and many other things.

Does spring-websocket STOMP support SUBSCRIBE id?

The STOMP spec says that SUBSCRIBE MUST have id header.
https://stomp.github.io/stomp-specification-1.2.html#SUBSCRIBE_id_Header
SUBSCRIBE id Header
Since a single connection can have multiple open subscriptions with a
server, an id header MUST be included in the frame to uniquely
identify the subscription. The id header allows the client and server
to relate subsequent MESSAGE or UNSUBSCRIBE frames to the original
subscription. Within the same connection, different subscriptions MUST
use different subscription identifiers.
However, in spring's example https://spring.io/guides/gs/messaging-stomp-websocket/, it doesn't specify an id when subscribing destination.
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);
});
});
}
In spring's API, the SimpMessageSendingOperations.convertAndSendToUser doesn't support an id header explicitly.
My question is how to specify id header when sending a message to client?
I don't think you can use a Subscription ID to send a message to a specific client. Stomp defines this ID and Spring's implementation uses it internally to create messages to every client subscribed to the destination address. Therefore, the Subscription ID is transparent in the Stomp communication... You can specify it in the client side or let Stomp JS (STOMP Over WebSocket) create a unique one.
If you subscribe to a destination prefixed with "/user/" and use org.springframework.messaging.simp.SimpMessagingTemplate#convertAndSendToUser or org.​springframework.​messaging.​simp.​annotation.SendToUser to send a message to a single client, what Spring does is register and create a subscription to a custom session based destination based on the original destination. In another words, from Spring's Javadoc:
When a user attempts to subscribe, e.g. to
"/user/queue/position-updates", the "/user" prefix is removed and a
unique suffix added based on the session id, e.g.
"/queue/position-updates-useri9oqdfzo" to ensure different users can
subscribe to the same logical destination without colliding.
When sending to a user, e.g.
"/user/{username}/queue/position-updates", the "/user/{username}"
prefix is removed and a suffix based on active session id's is added,
e.g. "/queue/position-updates-useri9oqdfzo".
See http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/messaging/simp/user/DefaultUserDestinationResolver.html
See https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-user-destination
EDITED:
You can't use the subscription ID to send a message direct to it's subscribed client, but you can use the client's session ID. According to here, you could use the user's name to send him a message. But you would need authenticated sessions with a Principal on it. Or you can force the destination's session ID in the header of the message, avoiding the internal step to discover it, as shown here.
private void sendMessageToUser(String destinationSessionId, String message) {
SimpMessageHeaderAccessor headerAccessor = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
headerAccessor.setSessionId(destinationSessionId);
headerAccessor.setLeaveMutable(true);
messagingTemplate.convertAndSendToUser(destinationSessionId, "/subscribe/private", message, headerAccessor.getMessageHeaders());
}
Doing like this, without a Principal in the session, I couldn't use #SendToUser annotation.

Websocket best practice for groups chat / one websocket for all groups or one websocket per group?

I have to implement a chat application using websocket, users will chat via groups, there can be thousands of groups and a user can be in multiple groups. I'm thinking about 2 solutions:
[1] for each group chat, I create a websocket endpoint (using camel-atmosphere-websocket), users in the same group can subscribe to the group endpoint and send/receive message over that endpoint. it means there can be thousands of websocket endpoints. Client side (let's say iPhone) has to subscribes to multiple wbesocket endpoints. is this a good practice?
[2] I just create one websocket endpoint for all groups. Client side just subscribes to this endpoint and I manage the messages distribution myself on server: get group members, pick the websocket of each member from list of connected websockets then write the message to each member via websocket.
Which solution is better in term of performance and easy to implement on both client and server?
Thanks.
EDIT 2015-10-06
I chose the second approach and did a test with jetty websocket client, I use camel atmosphere websocket on server side. On client side, I create websocket connections to server in threads. There was a problem with jetty that I can just create around 160 websocket connections (it means around 160 threads). The result is that I almost see no difference when the number of clients increases from 1 to 160.
Yes, 160 is not a big number, but I think I will do more test when I actually see the performance problem, for now, I'm ok with second approach.
If you are interested in the test code, here it is:
http://www.eclipse.org/jetty/documentation/current/jetty-websocket-client-api.html#d0e22545
I think second approach will be better to use for performance. I am using the same for my application, but it is still in testing phase so can't comment about the real time performance. Now its running for 10-15 groups and working fine. In my app, there is similar condition like you in which user can chat based on group. I am handling the the group creation on server side using node.js. Here is the code to create group, but it is for my app specific condition. Just pasting here for the reference. Getting homeState and userId from front-end. Creating group based on the homeState. This code is only for example, it won't work for you. To improve performance you can use clustering.
this.ConnectionObject = function(homeState, userId, ws) {
this.homeState = homeState;
this.userId = userId;
this.wsConnection = ws;
},
this.createConnectionEntry = function(homeState, userId,
ws) {
var connObject = new ws.thisRefer.ConnectionObject(homeState, userId,
ws);
var connectionEntryList = null;
if (ws.thisRefer.connectionMap[homeState] != undefined) {
connectionEntryList = ws.thisRefer.connectionMap[homeState];
} else {
connectionEntryList = new Array();
}
connectionEntryList.push(connObject);
console.log(connectionEntryList.length);
ws.thisRefer.connectionMap[homeState] = connectionEntryList;
ws.thisRefer.connecteduserIdMap[userId] = "";
}
Browsers implement a restriction on the numbers of websocket that can be opened by the same tab. You can't rely on being able to create as many connection as possible. Go for solution #2

How to reply to unauthenticated user in Spring 4 STOMP over WebSocket configuration?

I'm experimenting with Spring 4 WebSocket STOMP application. Is there a way to reply to a single unauthenticated user on condition that each user has unique session ID? Right now I can only either broadcast a message or send it directly to an authenticated user.
#Controller
public class ProductController {
#MessageMapping("/products/{id}")
#SendTo("/topic") // This line makes return value to be broadcasted to every connected user.
public String getProduct(#DestinationVariable int id) {
return "Product " + id;
}
}
You can assign an anonymous identity to incoming users. There are two ways to do it.
One, you can configure a sub-class of DefaultHandshakeHandler that overrides determineUser and assigns some kind of identity to every WebSocketSession. This requires 4.0.1 by the way (currently build snapshots are available) that will be released on Monday Jan 23, 2014.
Two, the WebSocket session will fall back on the value returned from HttpServletRequest.getUserPrincipal on the handshake HTTP request. You could have a servlet Filter wrap the HttpServletRequest and decide what to return from that method. Or if you're using Spring Security which has the AnonymousAuthenticationFilter, override its createAuthentication method.
#SendToUser("/products") should result in a message to destination "/user/{username}/products". That message will be handled by the UserDestinationMessageHandler, which transforms the destination to "/products-user{sessionId}" and re-sends the message.
So I'm not quite sure what "/user/products-user0" is. It surprises me in two ways. First if it starts with "/user" then that's the destination before the transformation and should be followed by the user name (i.e. "/user/{username}/products").
The fact that it ends with "-user0" makes it look like the destination after the transformation but then it shouldn't start with "/user". In any case the 0, 1 in that case would be the WebSocket session id. What server is this?

Resources