springboot version: 2.1.3 spring-websocket version: 5.1.1 spring-cloud-sleuth version: 2.1.1
When I try to use SimpMessagingTemplate.convertAndSend to send a String message to the websocket client after adding spring cloud sleuth to the application, an exception occurs:
Caused by: java.lang.UnsupportedOperationException: null
at java.util.Collections$UnmodifiableMap.remove(Collections.java:1460)
at org.springframework.messaging.support.NativeMessageHeaderAccessor.removeNativeHeader(NativeMessageHeaderAccessor.java:209)
at org.springframework.cloud.sleuth.instrument.messaging.MessageHeaderPropagation.removeAnyTraceHeaders(MessageHeaderPropagation.java:86)
at org.springframework.cloud.sleuth.instrument.messaging.TracingChannelInterceptor.preSend(TracingChannelInterceptor.java:173)
at org.springframework.messaging.support.AbstractMessageChannel$ChannelInterceptorChain.applyPreSend(AbstractMessageChannel.java:178)
at org.springframework.messaging.support.AbstractMessageChannel.send(AbstractMessageChannel.java:132)
... 123 common frames omitted
some primary code belows:
@RequestMapping(value = "/ws/reply", method = RequestMethod.GET)
public String msgReply(@RequestParam String msg) {
System.out.println(msg);
template.convertAndSend("/riten_ws_topic", msg);
return msg;
}
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/riten_ws_topic");
config.setApplicationDestinationPrefixes("/riten_ws_client");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/riten_ws_server").setAllowedOrigins("*").withSockJS();
}
}
Comment From: jhoeller
There seems to be some internal state mismatch: Despite still being in mutable state (passing that assertion in removeNativeHeader
), the header accessor seems to contain a "nativeHeaders" entry with an unmodifiable Map
. Since we're always taking a defensive copy on regular NativeMessageHeaderAccessor
construction, I wonder whether some external setHeader
call was setting a custom unmodifiable "nativeHeaders" entry? @rstoyanchev Am I missing something here?
Comment From: rogue2yjg
Is there any way to solve the problem?@jhoeller @rstoyanchev
Now I have to delete the sleuth reference to use spring websocket.
Comment From: rstoyanchev
@rogue2yjg does this fail predictably every time? If you have something to reproduce with that we can debug that would be really helpful.
Comment From: rogue2yjg
@rogue2yjg does this fail predictably every time? If you have something to reproduce with that we can debug that would be really helpful.
Yes, it fails every time, not surprisingly. The attach is my application to reproduce that, hoping that will be helpful. You need use postman to simulate a get request which url is /ws/reply.
Comment From: rstoyanchev
Thanks for the sample, I was able to debug and find the issue in SimpleBrokerMessageHandler
. It creates a copy of the message to broadcast for each subscriber and makes it mutable but the copying does not take proper care of the unmodifiable native headers sub-map so we end up in an inconsistent state.
I will work on a fix. In the mean time as a workaround you could install an interceptor on the clientOutboundChannel that makes the native headers modifiable:
@Override
public void configureClientOutboundChannel(ChannelRegistration registration) {
// Workaround for https://github.com/spring-projects/spring-framework/issues/25821
registration.interceptors(new ChannelInterceptor() {
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
MessageHeaderAccessor accessor = MessageHeaderAccessor.getMutableAccessor(message);
accessor.setLeaveMutable(true);
String key = NativeMessageHeaderAccessor.NATIVE_HEADERS;
Map<String, List<String>> map = (Map<String, List<String>>) accessor.getHeader(key);
if (map != null) {
accessor.removeHeader(key);
accessor.setHeader(key, new LinkedMultiValueMap<>(map));
}
return new GenericMessage<>(message.getPayload(), accessor.toMessageHeaders());
}
});
}