1

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

NoobSailboat
  • 109
  • 9

1 Answers1

1

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.

app.js (sending out a user name through request parameter)

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);
        });
    });
}

custom class extends DefaultHandshakeHandler.class

@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);
    }
}

custom object implement Principal.class

@Data
@NoArgsConstructor
@AllArgsConstructor
public class MyPrincipal implements Principal {
    private String name;
}

WebSocketConfig.class

@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();
    }
}

Show all subscribers

@RestController
public class ApiController {
    @Autowired
    private SimpUserRegistry simpUserRegistry;

    @GetMapping("/users")
    public List<String> connectedEquipments() {
        return this.simpUserRegistry
                .getUsers()
                .stream()
                .map(SimpUser::getName).toList();
    }
}

Result

enter image description here

By the way, you can check the DefaultSimpUserRegistry.class to observe the process of putting name into subscribers user map. enter image description here

Kevin
  • 36
  • 3