We had a requirement where we wanted to add the session header to the CONNECTED stomp frame to share it with the Stomp clients. As per STOMP's documentation, it is possible to add an optional session header to the CONNECTED frame to uniquely identify the session: https://stomp.github.io/stomp-specification-1.2.html#CONNECTED_Frame
In order to achieve this I added an outbound channel interceptor to add the session header to CONNECT_ACK messages. Here is the implementation that I ended up with:
internal object StompConnectedFrameInterceptor : ChannelInterceptor {
/**
* Unfortunately Spring Api doesn't expose the supported Stomp versions, so we have to replicate here
*/
internal val SUPPORTED_STOMP_VERSIONS = listOf("1.2", "1.1", "1.0")
override fun preSend(message: Message<*>, channel: MessageChannel): Message<*> {
val messageAccessor = StompHeaderAccessor.wrap(message)
if (messageAccessor.messageType != SimpMessageType.CONNECT_ACK) {
return message
}
val sessionId = messageAccessor.sessionId ?: return message
/**
* Create the CONNECTED frame ourselves so Spring does not drop the custom
* session header during its CONNECT_ACK -> CONNECTED translation. Since this
* bypasses Spring's translation, we must also set version and heartbeat here.
* See StompSubProtocolHandler for details
*/
val connectedAccessor = StompHeaderAccessor.create(StompCommand.CONNECTED)
connectedAccessor.setNativeHeader(StompHeaders.SESSION, sessionId)
connectedAccessor.version = negotiateStompVersion(messageAccessor)
val heartbeat = getHeartbeat(messageAccessor)
connectedAccessor.setHeartbeat(heartbeat[0], heartbeat[1])
return MessageBuilder.fromMessage(message)
.copyHeaders(connectedAccessor.messageHeaders)
.build()
}
private fun negotiateStompVersion(connectAckAccessor: StompHeaderAccessor): String {
val connectMessage = connectAckAccessor.getHeader(StompHeaderAccessor.CONNECT_MESSAGE_HEADER) as? Message<*>
val connectAccessor = connectMessage?.let { StompHeaderAccessor.wrap(it) }
val acceptedVersion = connectAccessor?.acceptVersion?.let { acceptVersions ->
SUPPORTED_STOMP_VERSIONS.firstOrNull { it in acceptVersions }
}
return acceptedVersion ?: throw IllegalArgumentException(
"No supported STOMP version found in CONNECT message. Supported versions: $SUPPORTED_STOMP_VERSIONS",
)
}
private fun getHeartbeat(connectAckAccessor: StompHeaderAccessor): LongArray {
val heartbeat = connectAckAccessor.getHeader(SimpMessageHeaderAccessor.HEART_BEAT_HEADER) as? LongArray
return heartbeat?.takeIf { it.size == 2 } ?: longArrayOf(0, 0)
}
}
I had to do the version negotiation and heartbeat negotiation myself. If I skip it, then there were no version and heartbeat headers in the frame received by clients. The downside of this application is that much of the Spring's internal code is being rewritten here. Also the STOMP versions are also being redeclared here.
I looked into the code and found out that the StompSubProtocolHandler is responsible for translating the CONNECT_ACK to the actual CONNECTED frame. During this translation all the headers except the version and heartbeat are ignored, so essentially even if we set the session header it will be ignored by the translation logic.
Is there a purpose to not allow optional headers defined by the STOMP protocol specifically the session header? If there is no specific purpose, could it be supported? I would be happy to contribute if required
We had a requirement where we wanted to add the
sessionheader to theCONNECTEDstomp frame to share it with the Stomp clients. As per STOMP's documentation, it is possible to add an optionalsessionheader to theCONNECTEDframe to uniquely identify the session: https://stomp.github.io/stomp-specification-1.2.html#CONNECTED_FrameIn order to achieve this I added an outbound channel interceptor to add the session header to
CONNECT_ACKmessages. Here is the implementation that I ended up with:I had to do the version negotiation and heartbeat negotiation myself. If I skip it, then there were no version and heartbeat headers in the frame received by clients. The downside of this application is that much of the Spring's internal code is being rewritten here. Also the STOMP versions are also being redeclared here.
I looked into the code and found out that the
StompSubProtocolHandleris responsible for translating theCONNECT_ACKto the actualCONNECTEDframe. During this translation all the headers except the version and heartbeat are ignored, so essentially even if we set the session header it will be ignored by the translation logic.Is there a purpose to not allow optional headers defined by the STOMP protocol specifically the
sessionheader? If there is no specific purpose, could it be supported? I would be happy to contribute if required