Spring WEBSOCKET+STOMP+SOCKJS Real-time communication detailed
The relationship between the first and third people
The HTTP connection is a request-once response (response) and must be called synchronously. The WebSocket protocol provides the ability to achieve full-duplex communication through a socket. Once connected, a TCP connection is established, and subsequent clients interact with the server in a full-duplex manner, and the client can send messages to the service side, which can also send messages to the client.
SOCKJS is a simulation of WebSocket technology. To deal with the problem that many browsers do not support the WebSocket protocol, an alternative sockjs is designed. When the SOCKJS is opened and used, it will use the WebSocket protocol as the transport Protocol, and if the browser does not support the WebSocket protocol, it will choose a better protocol for communication in other scenarios.
-Service-side use:
Registry.addendpoint ("/endpointchat"). WITHSOCKJS ();
-Client use:
Load sockjs
<script src= "http://cdn.sockjs.org/sockjs-0.3.min.js" ></script>
var url = '/chat ';
var sock = new Sockjs (URL);
The URL processed by SOCKJS is "http://" or "https://", not "ws://" or "wss://"
//...
STOMP is a simple text protocol that is message oriented. WebSocket defines two types of transport information: text information and binary information. The types are determined, but their transmissions are not specified. Therefore, it is necessary to use a simple text transfer type to specify the transmission content, which can be used as the text Transfer Protocol in communication, that is, the high-level protocol in the interaction to define the interaction information.
The stomp itself can support the network transport protocol of the stream type: the WebSocket protocol and the TCP protocol.
Stomp also provides a stomp.js for browser clients to use the Stomp Message protocol to transmit the JS library.
The advantages of stomp are as follows:
(1) Do not need to build a set of custom message format
(2) Existing Stomp.js clients (used in browsers) can be used directly
(3) Can route information to the designated message location
(4) can directly use the mature stomp agent to broadcast such as: RABBITMQ, ActiveMQ II, configuration Websocketstompconfig
1. Sharing Session
Import org.springframework.context.annotation.Configuration;
Import Org.springframework.messaging.simp.config.MessageBrokerRegistry;
Import Org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
Import Org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
Import Org.springframework.web.socket.config.annotation.StompEndpointRegistry; /** * @EnableWebSocketMessageBroker Annotations indicate that this configuration class is configured not only with WebSocket, but also stomp messages based on proxies; */@Configuration @EnableWebSocketMes Sagebroker public class Websocketconfig extends Abstractwebsocketmessagebrokerconfigurer {/** * replication Registerstompendpo INTs () Method: Adds a service endpoint to receive client connections.
Registers the "/endpointchat" path as the STOMP endpoint. * This path differs from the destination path for sending and receiving messages, which is an endpoint where the client connects to the endpoint before subscribing to or posting the message to the destination, * that is, the user sends the request: Url= "/127.0.0.1:8080/endpointchat" and STOMP server To connect, and then forward to the subscription URL; */@Override public void Registerstompendpoints (Stompendpointregistry registry) {//Add a/endpoint Chat endpoint, the client can connect through this endpoint; Withsockjs is to add SOCKJS support REgistry.addendpoint ("/endpointchat"). WITHSOCKJS ();
/** * Replication of the Configuremessagebroker () method: * Configured a simple message agent, the popular point is to set the message connection request of the various specifications information.
* The message to send the application will be prefixed with a "/app". * * @Override public void Configuremessagebroker (Messagebrokerregistry registry) {//defines the prefix information for one (or more) client subscription addresses, which is the client receiving service
The prefix information for sending messages Registry.enablesimplebroker ("/queue", "/topic");
Defines the prefix of the service-side receive address, or the address prefix//registry.setapplicationdestinationprefixes ("/app") that the client sends a message to the server;
Point-to-Point use of the subscription prefix (on the client subscription path will be reflected), not set, the default is also/user///registry.setuserdestinationprefix ("/user/"); }
}
Note :
This configuration is based on the Springboot+shiro framework, Shiro maintains all sessions and is passed when the user logs in
Simpleauthenticationinfo info = new Simpleauthenticationinfo (user, password, getName ());
Register the user information as a principal. When a client connection Endpointchat successfully, Stomp registers principal with Java.security.Principal's default implementation class ( usernamefor Shiro in my system). Then return to the client. This username is important for point-to-point messaging, maintaining the same username (this username is a unique string) through the server and the client to achieve accurate push messages.
2. Custom matching rules
If you adopt a different schema and do not implement principal, you need to implement your own custom username rules, which must be done by implementing your own principal class, as follows:
@Configuration @EnableWebSocketMessageBroker public class Websocketconfig extends abstractwebsocketmessagebrokerconfigurer {@Override public void registerstompendpoints (stompendpointregistry
Registry) {Registry.addendpoint ("/endpointchat"). Sethandshakehandler (New Defaulthandshakehandler () {@Override Protected Principal Determineuser (serverhttprequest request, Websockethandler Wshandler, map<string, OBJECT&G T
attributes) {//key is the same token that the server and the client are consistent, typically with an account name or a user ID.
return new Myprincipal ("test");
}). Withsockjs (); @Override public void Configuremessagebroker (Messagebrokerregistry registry) {//defines the prefix information for one (or more) client subscription addresses, that is, the client receives the service
The service end sends the message prefix information registry.enablesimplebroker ("/queue", "/topic");
Defines the prefix of the service-side receive address, or the address prefix//registry.setapplicationdestinationprefixes ("/app") that the client sends a message to the server;
Point-to-Point use of the subscription prefix (on the client subscription path will be reflected), not set, the default is also/user///registry.setuserdestinationprefix ("/user/");
}/** * Custom principal * * Class Myprincipal implements principal{private String key;
Public Myprincipal (String key) {this.key = key;
@Override public String GetName () {return key; }
}
}
The server then sends a message to the client:
Simpmessagingtemplate.convertandsendtouser ("Test", "/queue/notifications", "new Message");
The message sent by the client subscriber (the dashboard prints the message as shown in Figure 1):
Stomp.subscribe ("/user/queue/notifications", handlefunction);
Note : Why not Here is "/user/test/queue/notifications" and speak later.
Figure 1
3. Verify logon rights when connecting
Generally, when connecting to the server, you need to verify the security of this connection, verify that the user is logged in, and that you cannot connect to the server if you are not logged in.
/** * Connect to verify that the user is logged in * @author Leitao * @date April 18, 2018 Morning 10:10:37/public class Sessionauthhandshakeinterceptor Impleme
NTS handshakeinterceptor{Private final Logger Logger = Loggerfactory.getlogger (This.getclass ()); @Override public boolean beforehandshake (ServerHTTPRequest request, serverhttpresponse response, Websockethandler
Wshandler, map<string, object> attributes) throws Exception {Userdo user = Shiroutils.getuser ();
if (user = null) {logger.error ("WebSocket Permission denied: User not logged in");
return false;
}//attributes.put ("User", user);
return true; @Override public void Afterhandshake (ServerHTTPRequest request, serverhttpresponse response, Websockethandler Wshandl Er, Exception Exception) {}} @Configuration @EnableWebSocketMessageBroker public class Websocketconfig extends abstractwebsocketmessagebrokerconfigurer {@Override public void registerstompendpoints (stompendpointregistry Registry) {//Add a/endpointchat endpoint where the client can connect through the endpoint; WITHSOCThe KJS role is to add SOCKJS support Registry.addendpoint ("/endpointchat")//Add Connection login verification. Addinterceptors (New
Sessionauthhandshakeinterceptor ()). WITHSOCKJS (); @Override public void Configuremessagebroker (Messagebrokerregistry registry) {//defines the prefix information for one (or more) client subscription addresses, that is, the client receives the service
The service end sends the message prefix information registry.enablesimplebroker ("/queue", "/topic");
Defines the prefix of the service-side receive address, or the address prefix//registry.setapplicationdestinationprefixes ("/app") that the client sends a message to the server;
Point-to-Point use of the subscription prefix (on the client subscription path will be reflected), not set, the default is also/user///registry.setuserdestinationprefix ("/user/"); }
}
iii.@MessageMapping, @SendTo, @SendToUser annotations
@MessageMapping annotations are similar to the @requestmapping annotation feature, except that @requestmapping indicates that this method is the destination address of the Stomp client send message to the server.
The use of the following methods:
@Controller public
class Websocketcontroller {
@Autowired public
simpmessagingtemplate template;
@MessageMapping ("/hello")
@SendTo ("/topic/hello") public
greeting greeting (greeting message) throws Exception {return message
;
}
@MessageMapping ("/message")
@SendToUser ("/message") public
usermessage Usermessage (usermessage Usermessage) throws Exception {return
usermessage;
}
The first method indicates that the server can receive messages that the client sends over to the address "/hello". @SendTo indicates that this method broadcasts message messages to users subscribing to "/topic/hello". @SendTo ("/topic/hello") annotation is equivalent to using the
Simpmessagingtemplate.convertandsend ("/topic/hello", New Response ("Hello"));
Client through
Stomp.subscribe ("/topic/hello", handlefunction);
A message is received where the method is subscribed.
The second method is the same, just note that here is the @sendtouser, which is sent to a single client logo. In this example, the client receives a pair of messages subject to "/user/message", "/user/" is a fixed collocation, the server will automatically identify.
@SendToUser ("/message") is equivalent to using the
Simpmessagingtemplate.convertandsendtouser (Key, "/message", "new Message");
Client through
Stomp.subscribe ("/user/message", handlefunction);
method to receive a message when it is subscribed to and when the Username=key is returned at registration.
Note: There are a number of related annotations that are not described here. Four, point-to-point delivery process
A one-to-many broadcast message flow is simpler and is not described here.
Point-to-Point delivery differs not only in the use of @sendtouser or Convertandsendtouser methods. The most important difference lies in the underlying implementation logic.
When I was just learning a problem encountered by the client through the
Stomp.subscribe ("/user/queue/notifications", handlefunction);
Subscribe to the address, incredibly can receive background use
Simpmessagingtemplate.convertandsendtouser (user.tostring, "/queue/notifications", "New News");
The point-to-point message for the publication.
Through the simple research code, discovers the Convertandsendtouser bottom pass method:
@Override public
void Convertandsendtouser (string user, string destination, object payload, map<string, object > Headers,
messagepostprocessor postprocessor) throws Messagingexception {
assert.notnull (user, "user must Not being null ");
user = Stringutils.replace (user, "/", "%2f");
Super.convertandsend (this.destinationprefix + user + destination, payload, headers, postprocessor);
Convert "/queue/notifications" to "/user/userdo{userid=1,accounttype=0, username= ' admin ', name= ' Super Administrator ',...} /queue/notifications ". And the front end according to the online version of the client should be subscribed to the same address "/user/userdo{userid=1,accounttype=0, username= ' admin ', name= ' Super Administrator ',...} /queue/notifications "to be able to accept the message, which makes me baffled.
Note the following starting point:
System boot through
Stomp.subscribe ("/user/queue/notifications", handlefunction);
When you subscribe, you call the Org.springframework.messaging.simp.user.DefaultUserDestinationResolver resolvedestination method, Return the connection server to the front-end username to the Resolvedestination method, and then obtain the SessionIDfor this user, which is the unique ID generated for each user when the server is connected. Get (my system username=userdo{userid=1,accounttype=0, username= ' admin ', name= ' Super Administrator ',...} by returning to the front end of the username. is the ToString () string of the user entity. Then finally the "/user/queue/notifications" Address to "/queue/notifications-userefna60v1", where "-user" is a fixed collocation, "Efna60v1" Is the user's SessionID.
Server Through method
Simpmessagingtemplate.convertandsendtouser (User.tostring (), "/queue/notifications", User.getName () + "new Message");
When sending a message, it is really first to convert "/queue/notifications" to "/user/userdo{userid=1,accounttype=0, username= ' admin ', name= ' Super Administrator ',...} /queue/notifications, but it will also call the resolvedestination method to convert just the address to "/QUEUE/NOTIFICATIONS-USEREFNA60V1". The specific process is by decomposing the original address string to get "userdo{userid=1,accounttype=0, username= ' admin ', name= ' Super Administrator ',...}" (This information is the username that is returned to the front end when you just registered), and then this information gets to the SessionID generated by the user's registration, and finally translates the address to "/QUEUE/NOTIFICATIONS-USEREFNA60V1" and broadcasts the message, Because there is only one client subscribing to this address, point-to-point communication is achieved.
Other methods involved in this process are as follows:
Private Parseresult Parse (message<?> message) {messageheaders headers = message.getheaders ();
String sourcedestination = simpmessageheaderaccessor.getdestination (headers);
if (sourcedestination = null | |!checkdestination (sourcedestination, This.prefix)) {return null;
} simpmessagetype MessageType = Simpmessageheaderaccessor.getmessagetype (headers); Switch (messagetype) {case Subscribe:case Unsubscribe:return parsesubscriptionmessage (message, Sourcedestinat
ION);
Case Message:return Parsemessage (headers, sourcedestination);
Default:return null; } private Parseresult Parsemessage (messageheaders headers, String sourcedestination) {int prefixend = THIS.PREFIX.L
Ength ();
int userend = Sourcedestination.indexof ('/', prefixend);
Assert.istrue (userend > 0, "expected destination pattern \"/user/{userid}/**\ ");
String actualdestination = sourcedestination.substring (userend); String subscribedestination = This.prefix.substriNg (0, prefixEnd-1) + actualdestination;
String userName = sourcedestination.substring (Prefixend, userend);
UserName = Stringutils.replace (UserName, "%2f", "/");
String sessionId = Simpmessageheaderaccessor.getsessionid (headers);
Set<string> SessionIDs;
if (Username.equals (sessionId)) {userName = null;
SessionIDs = Collections.singleton (sessionId);
else {sessionids = Getsessionidsbyuser (UserName, sessionId);
} if (!this.keepleadingslash) {actualdestination = actualdestination.substring (1);
Return to New Parseresult (Sourcedestination, Actualdestination, Subscribedestination, SessionIDs, userName); ///through the set of UserName to query sessionId private set<string> getsessionidsbyuser (string userName, String sessionId) {Set< ;
String> SessionIDs;
Simpuser user = This.userRegistry.getUser (userName);
if (user!= null) {if (User.getsession (sessionId)!= null) {sessionids = Collections.singleton (sessionId); else {Set<simpsession> sessions = User.getsessions ();
SessionIDs = new Hashset<string> (Sessions.size ());
for (Simpsession session:sessions) {Sessionids.add (Session.getid ());
}} else {sessionids = Collections.emptyset ();
return sessionids; }
v.STOMP Client API 1, initiate the connection
Client.connect (headers, connectcallback, errorcallback);
Where headers represents authentication information for the client:
var headers = {
login: ' MyLogin ',
passcode: ' Mypasscode ',
//Additional header
' Client-id ': ' My-client-id '
};
If you do not need authentication, directly use the empty object "{}" can be;
(1) Connectcallback A callback method that indicates a successful connection (the server responds to a CONNECTED frame);
(2) Errorcallback indicates that the callback method (server responds to ERROR frame) when the connection fails, is not required;
Instance code:
Establish a Connection object (no connection has been initiated)
var socket=new sockjs ("/endpointchat");
Gets the client object of the STOMP child protocol
var stompclient = stomp.over (socket);
Initiates a websocket connection to the server and sends the Connect frame
stompclient.connect ({},
function Connectcallback (frame) {
// callback method
Console.log (' connected ' + frame + ' ") When the connection succeeds (the server responds to CONNECTED frames);
Subscribe to a message
stompclient.subscribe ('/topic/getresponse ',
function (response) {
Showresponse ( response.body);
}
,
callback method for function Errorcallback (error) {
//connection failure (server response to error frame) Console.log (' Connection failed ' + error + ' "');
} );
2. Disconnect the connection
To actively disconnect from a client, call the Disconnect () method:
Client.disconnect (
function () {
alert ("Disconnected");
});
3, send the information
Once the connection is successful, the client can send the message to the server using the Send () method:
Client.send (destination URL, headers, body);
which
(1) Destination URL for the server controller the matching URL in the @MessageMapping, string, must be parameters;
(2) Headers is the Header,javascript object to send the information, optional parameter;
(3) Body for sending information of the body, string, optional parameters;
Instance code:
Client.send ("/queue/test", {priority:9}, "Hello, STOMP");
Client.send ("/queue/test", {}, "Hello, STOMP");
4. Subscribe, receive message
STOMP clients want to receive messages from the server push, they must subscribe to the corresponding URL, that is, send a SUBSCRIBE frame, and then continue to receive the server from the