Spring websocket + Stomp + SockJS Detailed real-time communication

Spring websocket + Stomp + SockJS Detailed real-time communication

First, the relationship between the three

        Http connection to a request (request) a response (response), must be synchronized is called. WebSocket protocol provides the ability to achieve full-duplex communication over a socket. After a connection will be established tcp connection, follow the client-server interaction to interact with full-duplex mode, the client can send a message to the server, the server can also send messages to the client.

 

    SockJS WebSocket is a simulation technique. In response to many questions browser does not support WebSocket protocol design alternative SockJs. After opening and use SockJS, it will be preferred Websocket as transport protocol, if the browser does not support Websocket protocol, it will in other programs, choose a good protocol.

- The service uses:

registry.addEndpoint ( "/ endpointChat") withSockJS ();.
- the client uses:

// load sockjs

<Script the src = "http://cdn.sockjs.org/sockjs-0.3.min.js"> </ Script>

var URL = '/ Chat';

var = new new SockJS our sock (URL);

/ / URL SockJS are dealing with "http: //" or "https: //", instead of "WS: //" or "wss: //"

// .....
        STOMP Chinese as: message-oriented simple text protocol. websocket transmission defines two types of information: text information and the binary information. Although the type is determined, but their body is not provided for transmission. Therefore, the need for a simple transfer of text type is specified transfer content, it can be defined as an interactive information communication text transport protocol, namely the interaction of high-level protocol.

STOMP itself can support streaming network transmission protocol type: websocket tcp protocol and protocol.

Stomp also provides a stomp.js, for browser clients using STOMP messaging protocol transmission js library.

STOMP the following advantages:

(1) does not require a self-defined message format

(2) Existing stomp.js client (browser used) can be used directly

(3) capable of routing information message to the designated location

(4) may be used as agents mature STOMP broadcast such as: RabbitMQ, ActiveMQ

Second, the configuration WebsocketStompConfig

1, shared session


org.springframework.context.annotation.Configuration Import;
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 notes show: this configuration class is not only equipped with WebSocket, also equipped with agent-based STOMP messages;
* /
@Configuration
@ EnableWebSocketMessageBroker
public class WebSocketConfig the extends AbstractWebSocketMessageBrokerConfigurer {
/ **
* the replication registerStompEndpoints (): Add a service endpoint, connected to the receiving client. The "/ endpointChat" STOMP registered as a path endpoint.
* The path object path send and receive messages is different, which is an endpoint, client prior subscription or publish messages to the destination address, to connect to the endpoint,
* i.e. user sends a request: url = "/ 127.0.0.1 : 8080 / endpointChat "connected with STOMP server, then forwarded to subscription URL;
* /
@Override
public void registerStompEndpoints (StompEndpointRegistry Registry) {
// add a / endpointChat endpoint client can be connected by this endpoint; withSockJS effect adding SockJS support
registry.addEndpoint ( "/ endpointChat") withSockJS ();.
}

/ **
* the replication configureMessageBroker () method:
* a simple configuration message broker, popular thing about the connection request message is provided for each kind of specification information.
* Send the application's message will come with "/ app" prefix.
* /
@Override
public void configureMessageBroker (MessageBrokerRegistry Registry) {
// defines one (or more) clients subscribe address prefix information, that is, the client receives the message sent by the server prefix information
registry.enableSimpleBroker ( "/ queue" , "/ topic");
// define the server receives an address prefix, ie the client to the server a message to the address prefix
//registry.setApplicationDestinationPrefixes("/app ");
will be reflected on the subscription // prefix used point (client subscribes path out) is not set, the default is / User /
//registry.setUserDestinationPrefix("/user/ ");
}
}
Note:

This configuration is based on the framework SpringBoot + Shiro, Shiro maintain all the session, when the user logs on through

SimpleAuthenticationInfo info = new SimpleAuthenticationInfo (user, password, getName ());
the user information registered as a principal. When a client connects endpointChat success, stomp will take the default implementation class of java.security.Principal (shiro as a principal in my system) information registered as a username, then returned to the client. The username to send messages point to point is very important to achieve the purpose of precise push messages maintain the same username (this is a unique string username) by the server and the client.


2, custom matching rules


  If using other architectures, it did not materialize principal, which need to implement their own custom username rule must be accomplished by implementing its principal class, reference code is as follows:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig the extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints (StompEndpointRegistry Registry) {
registry.addEndpoint ( "/ endpointChat"). SetHandshakeHandler (new new DefaultHandshakeHandler () {
@Override
protected the Principal determineUser (ServerHTTPRequest Request, WebSocketHandler wsHandler, the Map <String, Object> Attributes) {
// Key server and the client is consistent marker, can generally account name or user ID.
return new new MyPrincipal ( "Test");
}
})
.withSockJS ();
}


@override
public void configureMessageBroker (MessageBrokerRegistry Registry) {
// defines one (or more) address prefix information of the client is subscribed, the client is sent a message server receives prefix information
registry.enableSimpleBroker ( "/ Queue", "/ Topic");
// defines the service prefix receiving end address, ie the client to the server a message to the address prefix
//registry.setApplicationDestinationPrefixes("/app ");
will be reflected on the subscription // prefix used point (client subscribes path), do not set , the default is / User /
//registry.setUserDestinationPrefix("/user/ ");
}

/ **
* custom the Principal
* /
class MyPrincipal the implements the Principal {

Private Key String;

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");
message sent from the client subscriber (message printed board shown in Figure 1):

stomp.subscribe ( "/ user / queue / notifications", handleFunction);
Note: Why not here "/ user / test / queue / notifications", wait repeat.

 

figure 1

 

 


3. Verify the logon permissions connection


Generally when connecting to a server, you need to verify the security of this connection, verify that the user logs in, if not logged in, you can not connect to the server, the subscription message.

/ *
If * Verification connect a user logs
* @author Leitao
* @date 2018 Nian 4 18 morning 10:10:37
* /
public class SessionAuthHandshakeInterceptor the implements HandshakeInterceptor {
Private Final Logger Logger = LoggerFactory.getLogger (this.getClass ( ));
@Override
public Boolean beforeHandshake (ServerHTTPRequest Request, Response ServerHttpResponse, WebSocketHandler wsHandler,
the Map <String, Object> Attributes) throws Exception {

UserDO ShiroUtils.getUser = User ();
IF (User == null) {
logger.error ( "websocket permission denied: user is not logged");
return to false;
}
//attributes.put("user ", user);
return to true;
}

@Override
void afterHandshake public (ServerHTTPRequest Request, Response ServerHttpResponse, WebSocketHandler wsHandler,
Exception Exception) {

}

}

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig the extends AbstractWebSocketMessageBrokerConfigurer {

@Override
public void registerStompEndpoints (StompEndpointRegistry Registry) {
// add a / endpointChat endpoint, the client may be connected through this terminal; withSockJS role is to support the adding SockJS
registry.addEndpoint ( "/ endpointChat")
// add the connection log to verify
.addInterceptors (new new SessionAuthHandshakeInterceptor ())
.withSockJS ();
}


@Override
public void configureMessageBroker (MessageBrokerRegistry registry) {
// defines one (or more) address prefix information of the client is subscribed, the client is sent a message server receives prefix information
registry.enableSimpleBroker ( "/ Queue", "/ Topic");
// defines the service prefix receiving end address, ie the client to the server a message to the address prefix
//registry.setApplicationDestinationPrefixes("/app ");
will be reflected on the subscription // prefix used point (client subscribes path), do not set , the default is / User /
//registry.setUserDestinationPrefix("/user/ ");
}

}

Three, @ MessageMapping, @ SendTo, @ SendToUser comment

        @MessageMapping @RequestMapping notes and annotations similar, but @RequestMapping show that this method is the destination address Stomp client to the server send messages.

Used as follows:


@Controller
public class WebSocketController {
@Autowired
public SimpMessagingTemplate Template;

@MessageMapping ( "/ Hello")
@SendTo ( "/ Topic / Hello")
public the Greeting Greeting (the Greeting Message) throws Exception {
return Message;
}

@MessageMapping ( "/ Message ")
@SendToUser (" / message ")
public UserMessage UserMessage (UserMessage UserMessage) throws Exception {
return UserMessage;
}
}
    the first method, the server represents a client may be received by the address" message sent over / hello ". This method represents @SendTo to subscribers "/ topic / hello" messages users broadcast message. @SendTo ( "/ topic / hello" ) is equivalent to the use of annotations

SimpMessagingTemplate.convertAndSend ( "/ topic / hello" , new Response ( " Hello"));
client by

stomp.subscribe ( "/ topic / hello" , handleFunction);
method where a subscription can receive messages.

 

    The second method same token, just pay attention to here is the @SendToUser, which is sent to the flag single client. In the present embodiment, the client receives one message relating should be "/ user / message", "/ user /" is fixed with, the server will automatically recognize.

@SendToUser ( "/ message") is equivalent to the use of

SimpMessagingTemplate.convertAndSendToUser (Key, "/ message" , " new message");
client by

stomp.subscribe ( "/ user / message" , handleFunction);
method of subscription and is returned when registering to receive messages when username = Key.

 

Note: On a related note, there are many, not one by one described here.

Fourth, the point-sending process

To-many broadcast message flow is relatively simple, not described here.

        Point transmission function differences not only in use or convertAndSendToUser @SendToUser method. The most important distinction is that the underlying logic of the above implementation.

   When I ran into a problem when just learning the client through

stomp.subscribe ( "/ user / queue / notifications", handleFunction);
subscription address, actually be able to receive background use

SimpMessagingTemplate.convertAndSendToUser (user.toString, "/ queue / notifications", " news");
peer news release.

Codes by a simple study, found that by a process convertAndSendToUser bottom:

@Override
public void convertAndSendToUser (User String, String Where do you want, Object payload, the Map <String, Object> headers,
MessagePostProcessor PostProcessor) throws MessagingException {

Assert.notNull (User, "Not the User MUST BE null");
User StringUtils.replace = ( User, "/", "%. 2F");
super.convertAndSend (User this.destinationPrefix + + Where do you want, payload, headers, PostProcessor);
}
the "/ queue / notifications" converted "/ user / UserDO {userId = 1, accountType = 0, username = 'admin', name = ' super administrator', ...} / queue / notifications ". And the front end view according to line the client should subscribe to by the same address "/ user / UserDO {userId = 1, accountType = 0, username = 'admin', name = ' super administrator', ...} / queue / notifications "to be able to receive messages fishes, and this gives me baffled.

 Note the following began to focus on:

The system starts by

stomp.subscribe ( "/ user / queue / notifications", handleFunction);
subscriptions when calls resolveDestination method org.springframework.messaging.simp.user.DefaultUserDestinationResolver, connect the server returns a username back to resolveDestination method to the front and obtain the sessionID user, the ID is connected to the server, generating a unique ID for each user, to get (my system at this time username = UserDO {userId = 1, accountType = 0 by returning to the front end of the username, username = 'admin', name = ' super administrator ", ...}, the user entity user toString () string). Then finally converted "/ user / queue / notifications" address "/ queue / notifications-userefna60v1" , wherein "-user" fixed match, "efna60v1" is the user's sessionID.

 

Server by method

SimpMessagingTemplate.convertAndSendToUser (user.toString (), "/ queue / notifications", user.getName () + " new message");
when sending messages, the first is the first true "/ queue / notifications" is converted to "/ user / UserDO {userId = 1, accountType = 0, username = 'admin', name = ' super administrator', ...} / queue / notifications ", but then also called just resolveDestination method to convert the address" / queue / notifications-userefna60v1 ". The specific process is carried out by decomposing the original address string, resulting "UserDO {userId = 1, accountType = 0, username = 'admin', name = ' super administrator', ...}" (this information is just registered while returns the username to the front end), then the acquired sessionID generated when the registered user through this information, and then to convert the address "/ queue / notifications-userefna60v1" and broadcast messages, since only a subscription to this address client, thus achieving point to point communication functions.

 

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, sourceDestination);
case MESSAGE:
return parseMessage(headers, sourceDestination);
default:
return null;
}
}

private ParseResult parseMessage(MessageHeaders headers, String sourceDestination) {
int prefixEnd = this.prefix.length();
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 new ParseResult(sourceDestination, actualDestination, subscribeDestination,
sessionIds, userName);
}

//通过设置的userName来查询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;
}


Five, Stomp client API

1 and initiates a connection

client.connect (headers, connectCallback, errorCallback) ;
wherein the authentication information represents headers client:

headers = {var
Login: 'mylogin',
PassCode: 'mypasscode',
// Additional header
'Client-ID': 'My-Client-ID'
};
if the non-authentication empty object "{}" to;

 (1) connectCallback indicates successful connection (CONNECTED server response frame) callback method; 

 (2) errorCallback means a link failure callback method (ERROR server response frame), non-essential;

 Example code:

// Create connection object (not yet initiated the connection)
var SockJS new new Socket = ( "/ endpointChat");
// Get STOMP sub-protocol client object
var stompClient = Stomp.over (Socket);
// initiate a connection to the server websocket and transmits the cONNECT frame
stompClient.connect ({},
function connectCallback (frame) {
// (cONNECTED server response frame) connection is successful callback method of
the console.log ( 'connected [' + frame + ']');
// a subscription message
stompClient.subscribe ( '/ Topic / getResponse',
function (response) {
the showResponse (response.body);
});
},
function errorCallback (error) {
// connection fails (eRROR server response frame) callback The method of
the console.log ( 'connection failed {' + error + ']');
});

2. Disconnect

To take the initiative to disconnect from the client, you can call disconnect () method:

client.disconnect (
function () {
Alert ( "Disconnect");
});
3, transmission information

Once connected, the client may use the send () method of sending information to the server:

client.send (destination url, headers, body );
wherein: 

(1) destination url controller in the server of the URL matching @MessageMapping, string parameters must be; 

(2) headers as a header, JavaScript object to send information, optional parameters; 

(3) body as body, which string, optional parameters transmitted information;

 

Example code:

client.send("/queue/test", {priority: 9}, "Hello, STOMP");

client.send("/queue/test", {}, "Hello, STOMP");
4、订阅、接收消息

STOMP client in order to receive messages from the server push, you must subscribe to the appropriate URL, that is, send a SUBSCRIBE frame before you can continue to receive push messages from the server.

 To subscribe and receive messages is achieved by subscribe () method:

subscribe(destination url, callback, headers)
其中 

(1) URL destination url server @SendTo match string; 

(2) callback to the callback method when pushed each time the server receives a message, the method comprising parameter Message; 

(. 3) headers as an additional headers, JavaScript object; the method returns an id attribute JavaScript object parameters of the method can be used as with unsubscribe (); By default, if no add headers additionally, this library will default to construct a a unique ID. When this parameter is passed headers, you can use your own id.

 

Reference Code:

headers = {var
ACK: 'Client',
// the information specifying the client confirms that it receives, only the receiving line with the selector: location = 'Europe' message.
'Selector': "LOCATION = 'Europe'",
// ID: 'MyID'
};
var = the callback function (Message) {
IF (message.body) {
Alert ( "GOT Message body with" + the JSON.parse (Message .body))}
the else {
Alert ( "GOT empty Message");
}});
var = client.subscribe Subscription ( "/ Queue / Test", the callback, headers);

If you want clients to subscribe to a plurality of destinations, you can call when all the information received in the same callback:
onMessage = function (message) {
// called Every time the Client receives message a
}
var client.subscribe SUB1 = ( "Queue / Test", onMessage);
var SUB2 = client.

5, unsubscribe

var subscription = client.subscribe(...);

subscription.unsubscribe();

6, transaction support

May be received in a transaction and to send the acknowledgment message.

The client calls itself begin () method of the transaction can be started, begin () has an optional parameter transaction, a unique string that identifies the transaction. If you do not pass this parameter, it will automatically build a library.

This method returns an object. This object has an id attribute corresponding to this transaction ID, there are two methods:

commit () to commit the transaction

abort () to abort the transaction
in a transaction, the transaction ID can specify the client transaction set when sending / receiving messages.

The Transaction Start //

var client.begin TX = ();

// The Send Message A in Transaction

client.send ( "/ Queue / Test", Transaction {:} tx.id, "A Message in Transaction");

/ / the commit at the transaction Effectively the send to the message at the

tx.commit ();
if you call to send () method when sending messages forget to add transction header, then this will not be called part of the transaction, this message will be sent directly, not wait until the transaction is complete before sending.

var txid = "unique_transaction_identifier";

// start the transaction

var tx = client.begin();

// oops! send the message outside the transaction

client.send("/queue/test", {}, "I thought I was in a transaction!");

tx.abort(); // Too late! the message has been sent

7, message ack acknowledgment

By default, before the message is sent to the client, the server automatically confirmed (acknowledged).

Client can choose ack header provided by a subscription to a destination client or client-individual confirmation message to process.

In the following example, the client must call message.ack () to notify the client that it has received the message.

Subscription client.subscribe = var ( "/ Queue / Test",

function (Message) {

// do something with The Message

...

// Acknowledge and IT

message.ack ();

},

{ACK: 'Client'}

) ;
ACK () accepts an additional parameter is used to confirm the message headers. For example, a message as part of a transaction (transaction), and when the received message is actually required Agent (Broker) ACK STOMP frame has been processed.

client.begin TX = var ();

message.ack ({Transaction: tx.id, Receipt: 'My-Receipt'});

tx.commit ();
NACK () may be used to inform STOMP 1.1.brokers (Agent ): the client can not consume the news. The same parameters ACK () method.

8, debug debugging

There are some test code library can help you know what to send or receive is in order to debug the program.

The client can be a function to set the debug attribute, a string parameter passed to observe all the debug database statements.

= function client.debug (STR) {

// the append The Debug log to div A #debug The Page Somewhere in the using JQuery:

$ ( "# Debug") the append (STR + "\ n-");.

};
default, debug messages are logged in the browser console.

9, heartbeat mechanism

If STOMP broker (agent) receives STOMP 1.1 version of the frame, heart-beating is enabled by default. i.e. heart-beating frequency, incoming receiving frequency, outgoing transmission frequency is.

The client can change by changing the incoming and outgoing heart-beating (the default is 10000ms):

= 20000 client.heartbeat.outgoing;

// Will Send HeartBeats Every Client 20000ms

client.heartbeat.incoming = 0;

// Not Client does want to the receive HeartBeats

// Server from The
Heart-beating using the window.setInterval () to the law heart-beats or the transmitting server checks the heart-beats.
----------------
Disclaimer: This article is CSDN blogger "Lei Xiaotao fought 'original article, follow the CC 4.0 BY-SA copyright agreements, please attach a reprint the original source link and this statement.
Original link: https: //blog.csdn.net/leixiaotao_java/article/details/79982309

Guess you like

Origin www.cnblogs.com/telwanggs/p/12125012.html