1
0
Files
spring-messaging-demo/sse-websocket/src/main/java/com/ankurm/ssews/ChatController.java
2026-09-04 00:52:18 +05:30

71 lines
3.2 KiB
Java

package com.ankurm.ssews;
import java.security.Principal;
import java.time.Instant;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.messaging.simp.SimpMessageType;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
/**
* The chat. Three handlers, each demonstrating a different routing rule.
*
* @see <a href="../../../../../docs/04-stomp.md">docs/04-stomp.md</a>
*/
@Controller
public class ChatController {
private final SimpMessagingTemplate template;
ChatController(SimpMessagingTemplate template) {
this.template = template;
}
/** Explicit fan-out destination. Client SENDs to /app/chat.send, subscribers get /topic/room. */
@MessageMapping("/chat.send")
@SendTo("/topic/room")
public ChatMessage send(@Payload ChatMessage in, SimpMessageHeaderAccessor headers) {
String from = in.from() == null ? headers.getSessionId() : in.from();
return new ChatMessage(from, in.text(), Instant.now());
}
/**
* No {@code @SendTo}. The return value still goes somewhere: the default destination is the
* broker prefix plus the mapping, i.e. {@code /topic/chat.echo}. That default is why a
* handler you thought was private is readable by anyone who guesses the path.
*/
@MessageMapping("/chat.echo")
public ChatMessage echo(@Payload ChatMessage in) {
return new ChatMessage("echo", in.text(), Instant.now());
}
/**
* Point-to-point. Sent to one session's private destination; every other subscriber to
* /user/queue/whisper gets nothing, because the broker rewrote the destination per session.
*
* <p>The three extra lines are not optional and their absence is silent. With no
* authenticated {@link Principal} the "user" is the STOMP session id, and
* {@code DefaultUserDestinationResolver} can only turn a session id into a real destination
* if the message also <em>carries</em> that session id in its headers. Call the two-argument
* {@code convertAndSendToUser(sessionId, dest, payload)} instead and the message is resolved
* against a user registry that has never heard of that name: no exception, no log line, no
* delivery. {@code setLeaveMutable(true)} is required too &mdash; without it the accessor is
* immutable by the time the resolver looks, and the session id is lost again.
*/
@MessageMapping("/chat.whisper")
public void whisper(@Payload ChatMessage in, SimpMessageHeaderAccessor headers, Principal principal) {
String targetSessionId = in.from();
SimpMessageHeaderAccessor out = SimpMessageHeaderAccessor.create(SimpMessageType.MESSAGE);
out.setSessionId(targetSessionId);
out.setLeaveMutable(true);
template.convertAndSendToUser(targetSessionId, "/queue/whisper",
new ChatMessage(principal == null ? headers.getSessionId() : principal.getName(),
in.text(), Instant.now()),
out.getMessageHeaders());
}
}