how to use and customize MessageConversion(Spring websocket client) - spring

I wrote a web socket server and a client with spring. The codes is following. The codes sending message to server work, but the sesssion.subscribe method cannot receive message from the server. I search for many documents and check my codes. I don't why it cannot work.
Here is my client codes:
public class Test {
public static void main(String[] args) {
Thread thread = new Thread(new WebsocketThread());
thread.start();
Thread.sleep(5000);
}
}
class MyStompSessionHandler extends StompSessionHandlerAdapter {
#Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
session.send("/app/messages", "{'payload3':2222}".getBytes());
session.subscribe("/user/queue/position-updates", new StompFrameHandler() {
#Override
public Type getPayloadType(StompHeaders headers) {
return String.class;
}
#Override
public void handleFrame(StompHeaders headers, Object payload) {
System.out.println("test:" + payload);
}
});
}
}
class WebsocketThread implements Runnable{
#Override
public void run() {
List<Transport> transports = new ArrayList<>(1);
transports.add(new WebSocketTransport( new StandardWebSocketClient()) );
WebSocketClient webSocketClient = new SockJsClient(transports);
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
String url = "ws://127.0.0.1:8860/orders";
StompSessionHandler sessionHandler = new MyStompSessionHandler();
ListenableFuture<StompSession> future = stompClient.connect(url, sessionHandler);
}
}
Here is my server codes:
#Controller
public class TestController {
#Autowired
private SimpMessagingTemplate simpMessagingTemplate;
#MessageMapping("/messages")
public void sendUserMsg(String messages) throws IOException {
System.out.println("webSocket:" + messages);
simpMessagingTemplate.convertAndSend("/queue/position-updates", "This is return message");
}
}
It is Exception:
org.springframework.messaging.converter.MessageConversionException: No suitable converter, payloadType=class java.lang.String, handlerType=class com.example.hello.MyStompSessionHandler
at org.springframework.messaging.simp.stomp.DefaultStompSession.invokeHandler(DefaultStompSession.java:419)
at org.springframework.messaging.simp.stomp.DefaultStompSession.handleMessage(DefaultStompSession.java:373)
at org.springframework.web.socket.messaging.WebSocketStompClient$WebSocketTcpConnectionHandlerAdapter.handleMessage(WebSocketStompClient.java:342)
at org.springframework.web.socket.sockjs.client.AbstractClientSockJsSession.handleMessageFrame(AbstractClientSockJsSession.java:267)
at org.springframework.web.socket.sockjs.client.AbstractClientSockJsSession.handleFrame(AbstractClientSockJsSession.java:200)
at org.springframework.web.socket.sockjs.client.WebSocketTransport$ClientSockJsWebSocketHandler.handleTextMessage(WebSocketTransport.java:162)
at org.springframework.web.socket.handler.AbstractWebSocketHandler.handleMessage(AbstractWebSocketHandler.java:43)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.handleTextMessage(StandardWebSocketHandlerAdapter.java:110)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.access$000(StandardWebSocketHandlerAdapter.java:42)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:81)
at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:78)
at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:399)
at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:500)
at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:295)
at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:131)
at org.apache.tomcat.websocket.WsFrameClient.processSocketRead(WsFrameClient.java:73)
at org.apache.tomcat.websocket.WsFrameClient.access$300(WsFrameClient.java:31)
at org.apache.tomcat.websocket.WsFrameClient$WsFrameClientCompletionHandler.completed(WsFrameClient.java:131)
at org.apache.tomcat.websocket.WsFrameClient$WsFrameClientCompletionHandler.completed(WsFrameClient.java:114)
at sun.nio.ch.Invoker.invokeUnchecked(Invoker.java:126)
at sun.nio.ch.Invoker$2.run(Invoker.java:218)
at sun.nio.ch.AsynchronousChannelGroupImpl$1.run(AsynchronousChannelGroupImpl.java:112)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)

add a StringMessageConverter to Client, it works.
WebSocketStompClient stompClient = new WebSocketStompClient(webSocketClient);
stompClient.setMessageConverter(new StringMessageConverter());
But how to customize our own MessageConverter? Is there any article?

In my case, the server was sending both json and raw string messages on different channels.
To be able to handle both cases, I went through the MessageConverter implementations and found CompositeMessageConverter, which allows multiple converters to be setup on the client.
Code:
List<MessageConverter> converters = new ArrayList<MessageConverter>();
converters.add(new MappingJackson2MessageConverter()); // used to handle json messages
converters.add(new StringMessageConverter()); // used to handle raw strings
client.setMessageConverter(new CompositeMessageConverter(converters));
The StompFrameHandler will then decide, based on what getPayloadType() returns, which converter to use.

add a SimpleMessageConverter to Client, it works.
stompClient.setMessageConverter(new SimpleMessageConverter());

It seems like you don't have configured any org.springframework.messaging.converter.MessageConverter in web socket configuration.
If you have jackson jar on your class path then it will be automatically picked up for json conversion. For other convertors , you need to configure it in WebSocket Config file .
#Configuration
#EnableWebSocketMessageBroker
#ComponentScan(SpringScanPackageNames)
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public boolean configureMessageConverters(List<MessageConverter> arg0) {
StringMessageConverter strConvertor = new StringMessageConverter();
arg0.add(strConvertor);
return true;
}
// Other config

I had a similar problem (though I was sending custom objects) and what worked for me was to simply set the Jackson2Message message converter as
webSocketStompClient.messageConverter = new MappingJackson2MessageConverter()
I found useful info (and examples) about this on: https://github.com/Noozen/spring-boot-websocket-client#the-java-client

Related

Capturing a message from unity using WebSocketSharp on a Spring server

I can't solve the problem. It is necessary to send messages to the server through the socket and process them. I can intercept the subscription, unsubscribe, connection and disconnection events on the server. But I can’t understand how to send a message to a specific destonation and intercept it on the server. On the client I use the library https://github.com/sta/websocket-sharp
Server code
#Configuration
#EnableWebSocket
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer implements WebSocketConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/multiplayer");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/multiplayer").setAllowedOrigins("*").withSockJS();
}
#Autowired
MessageHandler messageHandler;
// message interception option
#Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
webSocketHandlerRegistry.addHandler(messageHandler, "/result-game");
}
}
In the registerWebSocketHandlers method, I register a listener, it intercepts messages only if you specify "/multiplayer/websocket" in the "/result-game" place, the only trouble is that it intercepts everything in general and the #EventListaner listeners that intercepted my connection events stop working , shutdowns, etc. And when you try to send data to the client on the socket, the client does not receive them.
Client code
var DOMAIN = "1.1.0.1:8080";
_webSocket = new WebSocket($"ws://{DOMAIN}/multiplayer/websocket");
_webSocket.OnOpen += (sender, e) =>
{
// Sending a connection message
StompMessageSerializer serializer = new StompMessageSerializer();
var connect = new StompMessage("CONNECT");
connect["accept-version"] = "1.1";
connect["heart-beat"] = "10000,10000";
connect["playerId"] = _id;
var s = serializer.Serialize(connect);
SubscribeSocket(clientId.ToString(), $"/multiplayer/connect/{_id}", ConnectServer);
_webSocket.Send(s);
// Attempt to send messages to a specific destination
var content = new { Subject = "Stomp client", Message = "Hello World!!" };
var broad = new StompMessage("SEND", JsonConvert.SerializeObject(content));
broad["content-type"] = "application/json";
broad["destination"] = "/result-game";
_webSocket.Send(serializer.Serialize(broad));
Debug.LogError(TAG + "Connect open");
};
_webSocket.ConnectAsync();
On the server, in addition to the option above, there are two more attempts to intercept
First
#Slf4j
#Controller
#RequiredArgsConstructor
public class MessageController{
#MessageMapping("/result-game")
public void say(String message) throws InterruptedException {
Thread.sleep(30);
}
}
Second
#ServerEndpoint("/result-game")
public class MessageHandle {
#OnMessage
public void handleMessage(Session session, String message) {
// Do something with the message
System.out.println("Received message: " + message);
}
#OnMessage
public void processGreeting(String message, Session session) {
System.out.println("Greeting received:" + message);
}
}
Could someone tell me what I'm doing wrong with spring and working with sockets for the first time

Stomp java client auth headers are not send

when i try to send auth headers via stomp java fx client, they are not passed to the spring boot backend.
Client:
String URL = 'wss://myurl.de/wsconnection'
WebSocketClient client = new StandartWebSocketClient(client);
WebSocketStompClient stompClient = new WebSocketStompClient(client);
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
StompSessionHandler sessionHandler = new MyStompSessionHandler();
StompHeaders headers = new Headers();
headers.set("Authorization", "Bearer myToken")
headers.setLogin("Bearer myToken")
stompClient.connect(URL, sessionHandler,headers);
I tried two ways: using the login header method and custom Authorization header. So in the backend i try to get a jwt through one of these two headers. Unfortunatly the jwt value is always null so not passed.
Backend:
#Configuration
public class WebSocketSecurityConf extends AbstractSecurityWebSocketMessageBrokerConfigurer {
#Override
protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
messages.anyMessage().permitAll();
}
#Override
protected boolean sameOriginDisabled() {
return true;
}
}
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
#Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.setInterceptors(new ChannelInterceptorAdapter() {
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
if (StompCommand.CONNECT.equals(accessor.getCommand())) {
String jwtToken;
jwtToken = accessor.getFirstNativeHeader("Authorization");
if(jwtToken == null){
jwtToken = accessor.getFirstNativeHeader("login");
}
System.out.println("webSocket token is "+ jwtToken);
System.out.println("accessor header "+ accessor.getMessageHeaders());
Any ideas here?
You have mixed up the order of the parameters here: stompClient.connect(URL, sessionHandler,headers); Headers should be before handler.
Here is from spring sources:
public ListenableFuture<StompSession> connect(String url, #Nullable WebSocketHttpHeaders handshakeHeaders,
#Nullable StompHeaders connectHeaders, StompSessionHandler handler, Object... uriVariables)

How to Receive Response from Websocket Unit Test in Springboot

I am new to websockets and I am trying to write a unit test.
My unit test runs fine but it has following two issue
Idk why but it forces me to expect same object that is being sent as an input(i.e WebSocketRequestData) to the websocket instead of the actual response from the websocket which is WebSocketData
And it returns an empty object as result so it passes NotNull assertion.
Can anyone please clear out this confusion for me!
And also what is the right way to get response from the my websocket in unit test?
here is the code for my websocketTest Class
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ServerWebSocketTest {
#LocalServerPort
private Integer port;
static final String WEBSOCKET_TOPIC = "/user/locationrealtimedata/item" ;
BlockingQueue<WebSocketRequestData> blockingQueue;
WebSocketStompClient stompClient;
#BeforeEach
public void setup() {
blockingQueue = new LinkedBlockingDeque<>();
stompClient = new WebSocketStompClient(new SockJsClient(
asList(new WebSocketTransport(new StandardWebSocketClient()))));
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
}
#Test
public void shouldReceiveAMessageFromTheServer() throws Exception {
StompSession session = stompClient
.connect(getWsPath(), new DefaultStompFrameHandler() {
})
.get(1, TimeUnit.SECONDS);
session.subscribe(WEBSOCKET_TOPIC, new DefaultStompFrameHandler());
WebSocketRequestData webSocketRequestData = new WebSocketRequestData();
webSocketRequestData.setUserId("usr-1");
webSocketRequestData.setAccountId("acc-1");
webSocketRequestData.setGroupId("grp-1");
session.send("/wsconn/start", webSocketRequestData);
WebSocketRequestData responseObj = blockingQueue.poll(15, TimeUnit.SECONDS);
Assertions.assertNotNull(responseObj);
}
class DefaultStompFrameHandler extends StompSessionHandlerAdapter{
#Override
public Type getPayloadType(StompHeaders stompHeaders) {
return WebSocketRequestData.class;
}
#Override
public void handleFrame(StompHeaders stompHeaders, Object o) {
blockingQueue.offer((WebSocketRequestData) o); // instead of **WebSocketData** it forces me to add casting for **WebSocketRequestData**
}
#Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
exception.printStackTrace();
}
}
private String getWsPath() {
return String.format("ws://localhost:%d/location_services/locationrealtimedata", port);
}
}
Thanks in advance
You are not forced to use the same Java class for the input and response type.
The request type is what you use within session.send("/endpoint", payload); in your case that's WebSocketRequestData:
WebSocketRequestData webSocketRequestData = new WebSocketRequestData();
webSocketRequestData.setUserId("usr-1");
webSocketRequestData.setAccountId("acc-1");
webSocketRequestData.setGroupId("grp-1");
session.send("/wsconn/start", webSocketRequestData);
When it comes to consuming messages you specify the actual response type you expect when implementing StompFrameHandler and overriding getPayloadType.
So instead of implementing StompSessionHandlerAdapter, use the StompFrameHandler interface and implement it as the following:
class DefaultStompFrameHandler extends StompSessionHandlerAdapter{
#Override
public Type getPayloadType(StompHeaders stompHeaders) {
return WebSocketData.class; // or any other class your expect
}
#Override
public void handleFrame(StompHeaders stompHeaders, Object o) {
blockingQueue.offer((WebSocketData) o);
}
#Override
public void handleException(StompSession session, StompCommand command, StompHeaders headers, byte[] payload, Throwable exception) {
exception.printStackTrace();
}
}
Also make sure your BlockingQueue is using the correct type BlockingQueue<WebSocketData> blockingQueue

_AMQ_GROUP_ID present in message but JMSXGroupID null in #JmsListener

From this documentation:
Messages in a message group share the same group id, i.e. they have same group identifier property (JMSXGroupID for JMS, _AMQ_GROUP_ID for Apache ActiveMQ Artemis Core API).
I can see why the property originally set via JMSXGroupID becomes _AMQ_GROUP_ID when I browse the messages in the broker with a value of product=paper. However, In my #JmsListener annotated method I can see the _AMQ_GROUP_ID property is missing and the JMSXGroupID is coming through as null in the Message's headers hashmap.
#JmsListener(destination = "${artemis.destination}", subscription = "${artemis.subscriptionName}",
containerFactory = "containerFactory", concurrency = "15-15")
public void consumeMessage(Message<StatefulSpineEvent<?>> eventMessage)
So
My Producer application sends the message to the queue after setting the string property JMSXGroupID to 'product=paper'
I can see _AMQ_GROUP_ID has a value of 'product=paper' when I browse that message's headers in the Artemis UI
When I debug my listener application and look at the map of headers, _AMQ_GROUP_ID is absent and JMSXGroupID has a value of null instead of 'product=paper'.
Is the character '=' invalid or is there something else that can cause this? I'm running out of things to try.
Edit, with new code:
HeaderMapper:
#Component
public class GroupIdMessageMapper extends SimpleJmsHeaderMapper {
#Override
public MessageHeaders toHeaders(Message jmsMessage) {
MessageHeaders messageHeaders = super.toHeaders(jmsMessage);
Map<String, Object> messageHeadersMap = new HashMap<>(messageHeaders);
try {
messageHeadersMap.put("JMSXGroupID", jmsMessage.getStringProperty("_AMQ_GROUP_ID"));
} catch (JMSException e) {
e.printStackTrace();
}
// can see while debugging that this returns the correct headers
return new MessageHeaders(messageHeadersMap);
}
}
Listener:
#Component
public class CustomSpringJmsListener {
protected final Logger LOG = LoggerFactory.getLogger(getClass());
#JmsListener(destination = "local-queue", subscription = "groupid-example",
containerFactory = "myContainerFactory", concurrency = "15-15")
public void receive(Message message) throws JMSException {
LOG.info("Received message: " + message);
}
}
Application code:
#SpringBootApplication
#EnableJms
public class GroupidApplication implements CommandLineRunner {
private static Logger LOG = LoggerFactory
.getLogger(GroupidApplication.class);
#Autowired
private JmsTemplate jmsTemplate;
#Autowired MessageConverter messageConverter;
public static void main(String[] args) {
LOG.info("STARTING THE APPLICATION");
SpringApplication.run(GroupidApplication.class, args);
LOG.info("APPLICATION FINISHED");
}
#Override
public void run(String... args) {
LOG.info("EXECUTING : command line runner");
jmsTemplate.setPubSubDomain(true);
createAndSendObjectMessage("Message1");
createAndSendTextMessage("Message2");
createAndSendTextMessage("Message3");
createAndSendTextMessage("Message4");
createAndSendTextMessage("Message5");
createAndSendTextMessage("Message6");
}
private void createAndSendTextMessage(String messageBody) {
jmsTemplate.send("local-queue", session -> {
Message message = session.createTextMessage(messageBody);
message.setStringProperty("JMSXGroupID", "product=paper");
return message;
});
}
// BEANS
#Bean
public JmsListenerContainerFactory<?> myContainerFactory(ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
// This provides all boot's default to this factory, including the message converter
configurer.configure(factory, connectionFactory);
// You could still override some of Boot's default if necessary.
factory.setSubscriptionDurable(true);
factory.setSubscriptionShared(true);
factory.setMessageConverter(messagingMessageConverter());
return factory;
}
#Bean
public MessagingMessageConverter messagingMessageConverter() {
return new MessagingMessageConverter(messageConverter, new GroupIdMessageMapper());
}
}
Stack trace of where SimpleJmsHeaderMapper is being called:
toHeaders:130, SimpleJmsHeaderMapper (org.springframework.jms.support)
toHeaders:57, SimpleJmsHeaderMapper (org.springframework.jms.support)
extractHeaders:148, MessagingMessageConverter
(org.springframework.jms.support.converter) access$100:466,
AbstractAdaptableMessageListener$MessagingMessageConverterAdapter
(org.springframework.jms.listener.adapter) getHeaders:552,
AbstractAdaptableMessageListener$MessagingMessageConverterAdapter$LazyResolutionMessage
(org.springframework.jms.listener.adapter) resolveArgumentInternal:68,
HeaderMethodArgumentResolver
(org.springframework.messaging.handler.annotation.support)
resolveArgument:100, AbstractNamedValueMethodArgumentResolver
(org.springframework.messaging.handler.annotation.support)
resolveArgument:117, HandlerMethodArgumentResolverComposite
(org.springframework.messaging.handler.invocation)
getMethodArgumentValues:148, InvocableHandlerMethod
(org.springframework.messaging.handler.invocation) invoke:116,
InvocableHandlerMethod
(org.springframework.messaging.handler.invocation) invokeHandler:114,
MessagingMessageListenerAdapter
(org.springframework.jms.listener.adapter) onMessage:77,
MessagingMessageListenerAdapter
(org.springframework.jms.listener.adapter) doInvokeListener:736,
AbstractMessageListenerContainer (org.springframework.jms.listener)
invokeListener:696, AbstractMessageListenerContainer
(org.springframework.jms.listener) doExecuteListener:674,
AbstractMessageListenerContainer (org.springframework.jms.listener)
doReceiveAndExecute:318, AbstractPollingMessageListenerContainer
(org.springframework.jms.listener) receiveAndExecute:257,
AbstractPollingMessageListenerContainer
(org.springframework.jms.listener) invokeListener:1190,
DefaultMessageListenerContainer$AsyncMessageListenerInvoker
(org.springframework.jms.listener) executeOngoingLoop:1180,
DefaultMessageListenerContainer$AsyncMessageListenerInvoker
(org.springframework.jms.listener) run:1077,
DefaultMessageListenerContainer$AsyncMessageListenerInvoker
(org.springframework.jms.listener) run:748, Thread (java.lang)
Try subclassing the SimpleJmsHeaderMapper and override toHeaders(). Call super.toHeaders(), create a new Map<> from the result; put() any additional headers you want into the map and return a new MessageHeaders from the map.
Pass the custom mapper into a new MessagingMessageConverter and pass that into the container factory.
If you are using Spring Boot, simply add the converter as a #Bean and boot will auto wire it into the factory.
EDIT
After all this; I just wrote an app and it works just fine for me without any customization at all...
#SpringBootApplication
public class So58399905Application {
public static void main(String[] args) {
SpringApplication.run(So58399905Application.class, args);
}
#JmsListener(destination = "foo")
public void listen(String in, MessageHeaders headers) {
System.out.println(in + headers);
}
#Bean
public ApplicationRunner runner(JmsTemplate template) {
return args -> template.convertAndSend("foo", "bar", msg -> {
msg.setStringProperty("JMSXGroupID", "product=x");
return msg;
});
}
}
and
bar{jms_redelivered=false, JMSXGroupID=product=x, jms_deliveryMode=2, JMSXDeliveryCount=1, ...
EDIT2
It's a bug in the artemis client - with 2.6.4 (Boot 2.1.9) only getStringProperty() returns the value of the _AMQ_GROUP_ID property when getting JMSXGroupID.
The mapper uses getObjectProperty() which returned null. With the 2.10.1 client; the message properly returns the value of the _AMQ_GROUP_ID property from getObjectProperty().

How to add custom headers to STOMP CREATED message in Spring Boot application?

I'm trying to add custom headers to the STOMP 'CREATED' message, which is received by client at the first connection. Here is the function which connects to the WebSocket using STOMP JavaScript:
function connect() {
socket = new SockJS('/chat');
stompClient = Stomp.over(socket);
stompClient.connect('', '', function(frame) {
whoami = frame.headers['user-name'];
console.log(frame);
stompClient.subscribe('/user/queue/messages', function(message) {
console.log("MESSAGE RECEIVED:");
console.log(message);
showMessage(JSON.parse(message.body));
});
stompClient.subscribe('/topic/active', function(activeMembers) {
showActive(activeMembers);
});
});
}
This function prints the following to the browser's console:
body: ""
command: "CONNECTED"
headers: Object
heart-beat: "0,0"
user-name: "someuser"
version: "1.1"
And i want to add custom header so output must look like:
body: ""
command: "CONNECTED"
headers: Object
heart-beat: "0,0"
user-name: "someuser"
version: "1.1"
custom-header: "foo"
I have the following WebSocket configuration in my Spring Boot app.
WebSocketConfig.java
#Configuration
#EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
#Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/queue", "/topic");
config.setApplicationDestinationPrefixes("/app");
}
#Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/chat", "/activeUsers")
.withSockJS()
.setInterceptors(customHttpSessionHandshakeInterceptor());
}
...
#Bean
public CustomHttpSessionHandshakeInterceptor
customHttpSessionHandshakeInterceptor() {
return new CustomHttpSessionHandshakeInterceptor();
}
}
I have tried to register the 'HandshakeInterceptor' to set custom header, but it didn't work. Here is 'CustomHttpSessionHandshakeInterceptor':
CustomHttpSessionHandshakeInterceptor.java
public class CustomHttpSessionHandshakeInterceptor implements
HandshakeInterceptor {
#Override
public boolean beforeHandshake(ServerHttpRequest request,
ServerHttpResponse response,
WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest =
(ServletServerHttpRequest) request;
attributes.put("custom-header", "foo");
}
return true;
}
public void afterHandshake(ServerHttpRequest request,
ServerHttpResponse response,
WebSocketHandler wsHandler,
Exception ex) { }
}
I have found this code snippet at https://dzone.com/articles/spring-boot-based-websocket
Can someone explain me why this approach does not work? Is there another way to set custom headers to the STOMP 'CREATED' message at server side in Spring Boot application?
Thanks!
Maybe it's too late, but better late than never ...
Server messages (e.g. CONNECTED) are immutable, means that they cannot be modified.
What I would do is register a client outbound interceptor and trap the connected message by overriding the preSend(...) method and build a new message with my custom headers.
#Override
public Message<?> preSend(Message<?> message, MessageChannel channel)
{
LOGGER.info("Outbound channel pre send ...");
final StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(message);
final StompCommand command = headerAccessor.getCommand();
if (!isNull(command)) {
switch (command) {
case CONNECTED:
final StompHeaderAccessor accessor = StompHeaderAccessor.create(headerAccessor.getCommand());
accessor.setSessionId(headerAccessor.getSessionId());
#SuppressWarnings("unchecked")
final MultiValueMap<String, String> nativeHeaders = (MultiValueMap<String, String>) headerAccessor.getHeader(StompHeaderAccessor.NATIVE_HEADERS);
accessor.addNativeHeaders(nativeHeaders);
// add custom headers
accessor.addNativeHeader("CUSTOM01", "CUSTOM01");
final Message<?> newMessage = MessageBuilder.createMessage(new byte[0], accessor.getMessageHeaders());
return newMessage;
default:
break;
}
}
return message;
}
#UPDATE:::
The interface needed is called ChannelInterceptor and to register your own implementation you need to add #Configuration annotated class
#Configuration
public class CustomMessageBrokerConfig extends WebSocketMessageBrokerConfigurationSupport
implements WebSocketMessageBrokerConfigurer{}
and override a method configureClientOutboundChannel as below
#Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
log.info("Configure client outbound channel started ...");
registration.interceptors(new CustomOutboundChannelInterceptor());
log.info("Configure client outbound channel completed ...");
}
Did you try it like this way? MessageHeaderAccessor has a setHeader method too.
https://docs.spring.io/spring/docs/current/spring-framework-reference/html/websocket.html#websocket-stomp-authentication-token-based

Resources