WebSocket differentiates between different clients (HttpSession and @PathParam)

introduce

When using websocket to create multi-person instant messaging tools, it is inevitable to encounter a problem of how to distinguish different clients. To solve this problem is to solve such a problem: how to pass the userId of the current logged-in user to the server? Because different clients represent different users, it is natural to distinguish different clients by acquiring userId from different clients. After searching for information and experimenting, I found two feasible ways to obtain client userId. One is to get the current user by taking the value in HttpSession from Server, and the other is to attach the user's value directly to the client when establishing the connection.

Development environment and tools

    MyEclipse,Tomcat8.0
    WebSocket

Get the HttpSession value

When we complete the user login function, the user login successfully, then put the current user into HttpSession, which is a very common practice, this part of the code is as follows (the framework is Spring MVC, not detailed, the specific code should be based on the framework you use):

if(Objects.equals(userDetail.getUserDetailPassword(), userPassword)){

//If the current user logs in successfully, place the user object in the current User of httpSession
                httpSession.setAttribute("currentUser",user);
                resoult = "success";
            }

The key to the next question is how to get the User object in Server, which is put into HttpSession. It's certainly not possible to get it directly, instead of selling the key, we put the code directly.
Note that the structure is as follows:

Create a new GetHttpSessionConfigurator class, which reads as follows:

import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import javax.websocket.server.ServerEndpointConfig.Configurator;

public class GetHttpSessionConfigurator extends Configurator{
    @Override
    public void modifyHandshake(ServerEndpointConfig sec,HandshakeRequest request, HandshakeResponse response) {
        HttpSession httpSession=(HttpSession) request.getHttpSession();
        sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
    }
}

Then add a sentence to the commentary in Server:

@ServerEndpoint(value="/server/",configurator=GetHttpSessionConfigurator.class)

At this point, we can already use it.

@OnOpen
        public void onOpen(Session session, EndpointConfig config){
            HttpSession httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
            }

Get the httpSession object, and then take out the user object stored in the current User directly.

However, here I have a problem: in principle, the user object I retrieved from the httpsession acquired by the server is the object that is now establishing a connection with the server, because the operation of this situation must be to login first, then establish a connection directly, but in practice, multi-user login at the same time is not necessarily the case. Because login is an operation initiated by the client, and establishing connection is also an operation initiated by the client, not to mention whether the two operations are closely connected, even if they are closely connected, returning the login result from the server to the client to initiate the connection from the client to the server by returning the login result from the server (which has been put into the current User object at this time) will consume a lot of money because of network reasons. Fixed time. Then an awkward thing happens: at this time, another user is also logged in, and completed the login authentication operation during the previous two operations, then the user object taken out after the establishment of the first user connection is not the user but the second user, which is a mess. This approach is equivalent to user A saying to the server, remember, I'm A, and then later, I'm going to establish a connection. I just told you the name of the person. If B tells the server that I'm B when A leaves, then the server will treat A as B.
At present, I can't verify what I said above. If I misunderstood HttpSession, that would be another matter. If there is a misunderstanding, please ask God to correct it.
So it feels like the second method is more reliable.

@ PathParam Gets User Objects

This method is to put userId in the application for establishing a connection when establishing a connection, so that it won't be messed up: because after user A logged in successfully, I passed user object back to user A, and then user A took his userId to say to the client that I want to establish a connection, I am A, and the server will naturally make no mistake. The implementation method is as follows:
The service-side annotations are as follows:

@ServerEndpoint(value="/server/{userId}")

The method parameters are as follows:

@OnOpen
        public void onOpen(@PathParam("userId")String userId,Session session)

When the server establishes the connection request, the path is as follows (cp is in jsp:)

<c:set var="cp" value="${pageContext.request.contextPath}" />

CurrtUser is the value we put into httpSession when we logged in successfully. This value will not be refreshed even if other users logged in because it is stored in their own browser.

ws = "ws://localhost:8080" + "${cp}" + "/server"+"/${currentUser.userId}";

In this way, the server can get the current connected user.

Distinguish different clients

After we can get different user's userId, we can differentiate users by doing the following operations on the server side, see the comments for details. (To highlight the point, the code is streamlined and used only to demonstrate how different users can be distinguished)

    public class Server {
     //To store the Server object corresponding to each client, you can consider using Map instead of key as user ID.
     private static CopyOnWriteArraySet<Server> server = new CopyOnWriteArraySet<Server>();
     //Represents a connection session with a user through which data is sent to the client
     @SuppressWarnings("unused")
     private Session session;
     //User id
     private String userId;
     //Routing table for session binding of user id and websocket
     @SuppressWarnings("rawtypes")
     private static Map routeTable = new HashMap<>();
     /**
         * Connection Establishment Successful Call Method
         * @param session  Optional parameters. Session is a connection session with a client through which data is sent to the client
         */
        @SuppressWarnings("unchecked")
        @OnOpen
        public void onOpen(@PathParam("userId")String userIds,Session session){
            this.session = session;
            //Get the id of the current logged-in user
            this.userId=userIds;
            //Binding user id and session to routing table
            //After binding, session can be acquired from id elsewhere, and then two users can chat privately.
            routeTable.put(userId, session);
        }
        //The rest of the code goes away.

This is probably the case. This is the result of my own experiments, which I have searched for a lot of information, hoping to help you.
Study hard and make progress every day. Come on!

Tags: Session MyEclipse Spring network

Posted on Mon, 25 Mar 2019 19:48:28 -0400 by yasir_memon