-1

I am currently building a chat application with spring reactive.

Each user can be part of multiple chat rooms.

I am trying to only send each chat message to clients connected to the corresponding rooms.

For example, if a user sends a message in the chat room with UUID "32e4adff-d0bd-4496-ae40-0e799eeb5fe7", only clients connected to the socket with endpoint "/chat/32e4adff-d0bd-4496-ae40-0e799eeb5fe7" will receive the message.

Currently, my websocket handler mapping is :

  @Bean
  public HandlerMapping webSocketHandlerMapping() {
    String path = "/chat/*";
    Map<String, WebSocketHandler> map = Map.of(path, webSocketHandler);
    return new SimpleUrlHandlerMapping(map, -1);
  } 

And my ChatSocketHandler :

@Component
public class ChatSocketHandler implements WebSocketHandler {

  private final ObjectMapper mapper = new ObjectMapper();

  private Sinks.Many<ChatMessage> sinks = Sinks.many().multicast().directBestEffort();
  private Flux<ChatMessage> flux = sinks.asFlux();

  private final Sinks.EmitFailureHandler emitFailureHandler =
      (signalType, emitResult) -> emitResult.equals(Sinks.EmitResult.FAIL_NON_SERIALIZED);

  private final ChatService chatService;

  @Autowired
  public ChatSocketHandler(ChatService chatService) {
    this.chatService = chatService;
  }

  @Override
  public Mono<Void> handle(WebSocketSession session) {
    // TODO get rid of subscribe
    session
        .receive()
        .map(
            webSocketMessage -> {
              try {
                return mapper.readValue(webSocketMessage.getPayloadAsText(), ChatMessage.class);
              } catch (JsonProcessingException e) {
                e.printStackTrace();
                return new ChatMessage();
              }
            })
        .flatMap(chatService::sendMessage)
        .subscribe(webSocketMessage -> sinks.emitNext(webSocketMessage, emitFailureHandler));

    return session.send(
        Mono.delay(Duration.ofMillis(100))
            .thenMany(flux.map(it -> session.textMessage(toJson(it)))));
  }

  private String toJson(ChatMessage object) {
    try {
      return mapper.writeValueAsString(object);
    } catch (JsonProcessingException e) {
      e.printStackTrace();
    }
    return null;
  }
}

And chat service function to send a message :

public Mono<ChatMessage> sendMessage(ChatMessage chatMessage) {
    return chatMessageRepository.insert(chatMessage);
}

For now, clients receive messages for all rooms, event the one they are not supposed to be connected to.

How to configure my websocket so that only users connected to a room receive the messages published there ?

Jots
  • 19
  • 4
  • Where is the code for `chatservice::sendMessage` – Toerktumlare Jun 14 '22 at 20:39
  • `chatservice::sendMessage` only inserts a message in the database – Jots Jun 15 '22 at 17:45
  • I did not ask what code does, i asked where the code is – Toerktumlare Jun 15 '22 at 17:56
  • Oh sorry, I misread. I added the sendMessage function to my question! – Jots Jun 15 '22 at 19:07
  • The code you have posted has nothing with any rooms? You are using a multicast sink that will push messages to all subscribing. You need logic that stores each user websocket session in some sort of list. Then you need group sessions into rooms. And then when you are going to push a message you do it to a select number of sessions. – Toerktumlare Jun 15 '22 at 22:02
  • I suggest you lookup how to write this first using regular java websockets. And when you figured that put you write it using webflux – Toerktumlare Jun 15 '22 at 22:03
  • When I say room, I mean that only clients connected to the websocket via the path parameter corresponding to the chat ID (for example /chat/123) should receive chat messages with this ID. I changed my handle function and it is now working (see my response below), but I was wondering if there was a better way to do this. – Jots Jun 18 '22 at 09:18

1 Answers1

1

I fixed it by adding a filter in my session.send

  @Override
  public Mono<Void> handle(WebSocketSession session) {
    String path = session.getHandshakeInfo().getUri().getPath();
    String chatId = path.substring(path.lastIndexOf('/') + 1);

    session
        .receive()
        .map(
            webSocketMessage -> {
              try {
                return mapper.readValue(webSocketMessage.getPayloadAsText(), ChatMessage.class);
              } catch (JsonProcessingException e) {
                e.printStackTrace();
                return new ChatMessage();
              }
            })
        .flatMap(chatService::sendMessage)
        .subscribe(webSocketMessage -> sinks.emitNext(webSocketMessage, emitFailureHandler));

    return session.send(
        Mono.delay(Duration.ofMillis(100))
            .thenMany(
                flux.filter(it -> chatId.equals(it.getChatId()))
                    .map(it -> session.textMessage(toJson(it)))));
  }

I am not sure whether there is a better way to do this.

Jots
  • 19
  • 4