0

Problem Description: I'm working on a WebSocket chat application using Spring Boot. The application uses STOMP over WebSocket for communication. When I test the WebSocket connection in Postman, I can establish the connection and send subscribe messages, but I'm not receiving any messages.

Here are the details of my setup:

WebSocket Endpoint for Sender: ws://localhost:8080/ws/10000 WebSocket Endpoint for Recipient: ws://localhost:8080/ws/10002

Connected to WebSocket for Sender:

URL: ws://localhost:8080/ws/10000 Sent STOMP Connect message : CONNECT accept-version:1.2 heart-beat:10000,10000

\0

Sent Message : SEND destination:/app/chat.sendMessage content-type:application/json

{ "senderId": 10000, "recipientId": 10002, "content": "Hello, World!", "isPrivate": true }\0

Connected to WebSocket for Recipient: URL: ws://localhost:8080/ws/10002 Sent STOMP Connect message: CONNECT accept-version:1.2 heart-beat:10000,10000

\0

SUBSCRIBE id:sub-0 destination:/user/10002/queue/messages

\0

Current Status: The recipient WebSocket connection shows the subscription message as sent. However, no messages are being received in the recipient's Postman WebSocket session. Relevant Code: WebSocket Configuration:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic", "/queue");
        config.setApplicationDestinationPrefixes("/app");
        config.setUserDestinationPrefix("/user");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws/{matricule}")
                .setAllowedOrigins("*")
                .withSockJS();
    }
}

Handshake Interceptor:

public class HandshakeInterceptor extends HttpSessionHandshakeInterceptor {

    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map<String, Object> attributes) throws Exception {
        String path = request.getURI().getPath();
        Long matricule = Long.parseLong(path.substring(path.lastIndexOf('/') + 1));
        attributes.put("matricule", matricule);
        return super.beforeHandshake(request, response, wsHandler, attributes);
    }
}

WebSocket Handler:

@Component
public class TradeWebSocketHandler extends TextWebSocketHandler {

    private final Map<Long, WebSocketSession> sessions = new HashMap<>();
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        Long matricule = (Long) session.getAttributes().get("matricule");
        sessions.put(matricule, session);
        System.out.println("Connected: " + matricule);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String payload = message.getPayload();
        System.out.println("Received: " + payload);

        // Parse the message
        ChatMessageDTO chatMessageDTO = objectMapper.readValue(payload, ChatMessageDTO.class);

        // Get the recipient's session
        WebSocketSession recipientSession = sessions.get(chatMessageDTO.recipientId());
        if (recipientSession != null && recipientSession.isOpen()) {
            recipientSession.sendMessage(new TextMessage(payload));
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        Long matricule = (Long) session.getAttributes().get("matricule");
        sessions.remove(matricule);
        System.out.println("Disconnected: " + matricule);
    }
}
@Component
public class TradeWebSocketHandler extends TextWebSocketHandler {

    private final Map<Long, WebSocketSession> sessions = new HashMap<>();
    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        Long matricule = (Long) session.getAttributes().get("matricule");
        sessions.put(matricule, session);
        System.out.println("Connected: " + matricule);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String payload = message.getPayload();
        System.out.println("Received: " + payload);

        // Parse the message
        ChatMessageDTO chatMessageDTO = objectMapper.readValue(payload, ChatMessageDTO.class);

        // Get the recipient's session
        WebSocketSession recipientSession = sessions.get(chatMessageDTO.recipientId());
        if (recipientSession != null && recipientSession.isOpen()) {
            recipientSession.sendMessage(new TextMessage(payload));
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        Long matricule = (Long) session.getAttributes().get("matricule");
        sessions.remove(matricule);
        System.out.println("Disconnected: " + matricule);
    }
}

Chat Controller:

@RestController
@RequestMapping("/api/chat")
public class ChatController {

    @Autowired
    private SimpMessagingTemplate messagingTemplate;

    @Autowired
    private ChatService chatService;

    @MessageMapping("/chat.sendMessage")
    public void sendMessage(@DestinationVariable Long matricule, ChatMessageDTO chatMessageDTO) {
        ChatMessageDTO updatedChatMessageDTO = new ChatMessageDTO(
                null,
                chatMessageDTO.senderId(),
                chatMessageDTO.recipientId(),
                chatMessageDTO.content(),
                LocalDateTime.now(),
                chatMessageDTO.isPrivate()
        );

        ChatMessageDTO savedChatMessageDTO = chatService.saveMessage(updatedChatMessageDTO);
        System.out.println("Saved ChatMessageDTO: " + savedChatMessageDTO);

        try {
            messagingTemplate.convertAndSendToUser(
                    savedChatMessageDTO.recipientId().toString(),
                    "/queue/messages",
                    savedChatMessageDTO
            );
            System.out.println("Message sent to user: " + savedChatMessageDTO.recipientId());
        } catch (Exception e) {
            System.err.println("Error sending message: " + e.getMessage());
        }
    }

    @GetMapping("/history")
    public ResponseEntity<List<ChatMessageDTO>> getChatHistory(@RequestParam Long senderId, @RequestParam Long recipientId) {
        List<ChatMessageDTO> messages = chatService.getChatHistory(senderId, recipientId);
        return ResponseEntity.ok(messages);
    }

    @GetMapping("/unique-conversations")
    public ResponseEntity<List<Long>> getUniqueConversations(@RequestParam Long userMatricule) {
        List<Long> conversations = chatService.getUniqueConversations(userMatricule);
        return ResponseEntity.ok(conversations);
    }
}

My spring security configuration :

 @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
                .csrf(AbstractHttpConfigurer::disable)
                .cors(cors -> cors.configurationSource(corsConfigurationSource()))
                .authorizeHttpRequests(authz -> authz
                        .requestMatchers("/api/auth/**").permitAll()
                        .requestMatchers("/actuator/**").permitAll()
                        .requestMatchers("/ws/**").permitAll()
                        .requestMatchers("/ws").permitAll()
                        .anyRequest().authenticated()
                )
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .headers(headers -> headers.frameOptions().disable())
                .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                .exceptionHandling(exception -> exception
                        .authenticationEntryPoint((request, response, authException) -> {
                            logger.error("Unauthorized request - {}", authException.getMessage());
                            response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
                        })
                )
                .httpBasic();
        return http.build();
    }

    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.setAllowedOrigins(Arrays.asList("http://localhost:4200"));
        corsConfiguration.setAllowedHeaders(Arrays.asList("Origin", "Access-Control-Allow-Origin", "Content-Type",
                "Accept", "Authorization", "Origin, Accept", "X-Requested-With",
                "Access-Control-Request-Method", "Access-Control-Request-Headers", "Upgrade", "Connection")); // Ajouter "Connection"
        corsConfiguration.setExposedHeaders(Arrays.asList("Origin", "Content-Type", "Accept", "Authorization",
                "Access-Control-Allow-Origin", "Access-Control-Allow-Origin", "Access-Control-Allow-Credentials"));
        corsConfiguration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }

Why am I not receiving messages in the recipient WebSocket session? Is there a mistake in my WebSocket or STOMP configuration? Are there any specific settings in Postman that I need to adjust for testing WebSocket communication? Is my sendMessage method correctly set up to handle and route messages via STOMP? Any insights or suggestions to resolve this issue would be greatly appreciated. Thank you!

Expected Outcomes

The SEND message should be processed by the server, and the content should be routed to the recipient's queue. Message Reception:

The recipient (matricule 10002) should receive the message "Hello, World!" in their WebSocket session after subscribing to the appropriate queue.

1

0