I was going over the basic Spring Boot WebSocket Tutorial: https://spring.io/guides/gs/messaging-stomp-websocket/
I decided to modify it to print out how many users are subscribed to a channel in the console but couldn't figure it out for hours. I've seen a few StackOverflow posts but they don't help. The last one I check was this: https://stackoverflow.com/a/51113021/11200149 which says to add try this:
@Autowired private SimpUserRegistry simpUserRegistry;
public Set<SimpUser> getUsers() {
return simpUserRegistry.getUsers();
}
So, I added the above to my controller, and here is the change:
@Controller
public class GreetingController {
@Autowired
private SimpUserRegistry userRegistry;
@MessageMapping("/hello")
@SendTo("/topic/greetings")
public Greeting greeting(HelloMessage message) throws Exception {
Set<SimpUser> subscribedUsers = userRegistry.getUsers();
System.out.println("User amount: " + subscribedUsers.size()); // always prints: 0
return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!");
}
}
This always prints 0:
System.out.println("User amount: " + subscribedUsers.size());
I'm coming from Socket.IO so maybe things work a bit differently because I've seen people implement their own manual Subscription Service classes. In socket.io this would be a piece of cake so I would assume Spring Boot would have this, but I just can't seem to find it.
Edit: This post does a great explanation for this problem. Principal is null for every Spring websocket event
Maybe you can try to add custom HandshakeHandler class into registry and override the determineUser method to return the Principal object that containing subscriber name so that the SimpUserRegistry can work properly.
If you would like to see the effect, the below is what I'm trying.
function connect() {
var socket = new SockJS('/gs-guide-websocket?name=' + $('#name').val());
stompClient = Stomp.over(socket);
stompClient.connect({}, function (frame) {
setConnected(true);
console.log('Connected: ' + frame);
stompClient.subscribe('/topic/greetings', function (greeting) {
showGreeting(JSON.parse(greeting.body).content);
});
});
}
@Component
public class WebSocketHandShakeHandler extends DefaultHandshakeHandler {
@Override
protected Principal determineUser(ServerHttpRequest request, WebSocketHandler wsHandler, Map<String, Object> attributes) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpServletRequest httpServletRequest = servletRequest.getServletRequest();
String name = httpServletRequest.getParameter("name");
return new MyPrincipal(name);
}
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MyPrincipal implements Principal {
private String name;
}
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Autowired
private WebSocketHandShakeHandler webSocketHandShakeHandler;
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic");
config.setApplicationDestinationPrefixes("/app");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/gs-guide-websocket")
.setHandshakeHandler(webSocketHandShakeHandler)
.withSockJS();
}
}
@RestController
public class ApiController {
@Autowired
private SimpUserRegistry simpUserRegistry;
@GetMapping("/users")
public List<String> connectedEquipments() {
return this.simpUserRegistry
.getUsers()
.stream()
.map(SimpUser::getName).toList();
}
}
By the way, you can check the DefaultSimpUserRegistry.class to observe the process of putting name into subscribers user map.