구현코드와 코드에 대한 설명은 각각 아래와 같다.
@Configuration
@EnableRabbit
public class RabbitConfig {
private static final String CHAT_QUEUE_NAME = "chat.queue"; // 바인딩 할 Queue 이름
private static final String CHAT_EXCHANGE_NAME = "chat.exchange"; // 바인딩 할 Exchange 이름
private static final String ROUTING_KEY = "room.*"; // 라우팅키 설정 (큐와 라우팅 키의 역할은 아래에서 설명하도록 한다)
// 큐 주입
@Bean
public Queue queue() {
return new Queue(CHAT_QUEUE_NAME, false);
}
// 익스체인지 주입
@Bean
public TopicExchange exchange() {
return new TopicExchange(CHAT_EXCHANGE_NAME);
}
// 바인딩 설정
@Bean
public Binding binding(Queue queue, TopicExchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with(ROUTING_KEY);
}
// RabitTemplete 설정
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
rabbitTemplate.setMessageConverter(jsonMessageConverter()); // JSON을 String으로 변환
rabbitTemplate.setRoutingKey(CHAT_QUEUE_NAME);
return rabbitTemplate;
}
private Jackson2JsonMessageConverter jsonMessageConverter() {
return new Jackson2JsonMessageConverter();
}
}
Exchange는 메세지를 전달 받아 정해진 바인딩 규칙에 따라 특정 Queue에 메세지를 전달하는 역할을 수행한다.
그래서 위 코드에서는 routing key(회원이 들어있는 채팅방 고유 번호가 될 것이다)에 따라 회원이 요청한 메세지를 알맞은 queue에 전달하게 된다.
RabbitTemplete은 메세지를 전달하기위한 객체이다. 하지만 RabbitMq의 메세지 컨버터는 기본적으로 String을 받도록 설정되어있다.
그래서 위 코드와 같이 RabbitTemplete의 기본 Converter를 변경해줌으로 String이 아닌 객체를 받을 수 있도록 하였다.
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws") // 클라이언트에서 ws://localhost:8080/ws로 요청 보낼시 STOMP 생성
.setAllowedOriginPatterns("*") //"*" 설정을 위해서는 setAllowedOrgin이 아닌 setAllowedOriginPatterns로 설정해주어야 한다
.withSockJS(); //WebSocket을 지원하지 않는 브라우저에 대해 채팅 기능이 잘 작동할 수 있도록하는 설정이다
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.setPathMatcher(new AntPathMatcher("."));
//url 경로 형식을 바꾸는 설정이다. 찾아보니 'Stomp에서는 이런 패턴을 {보통} 사용한다' 라고하는데 그 보통의 이유가 도대체 무엇인지 아직 알아내지 못했다.
//ex) chat/room/3 -> chat.room.3
registry.setApplicationDestinationPrefixes("/pub"); // 매세지를 발행할 때 기본 경로이다.
registry.enableStompBrokerRelay("/queue", "/topic", "/exchange", "/amq/queue"); // 어떤 채팅방에 입장할 것인지 구독할 때 Url의 prefix이며 각 방식마다 동작 방식이 다르다.
}
}
rabbitmq github에 올라온 구독 형식 자료를 아래에 첨부한다
= "/exchange/" X "/" RK Consume from temp queue bound to X with routing key RK
| "/topic/" RK Consume from temp queue bound to amq.topic with routing key RK
| "/amq/queue/" Q Consume from Q
| "/queue/" Q Consume from Q
| Q (no leading slash) Consume from Q
@Controller
@RequiredArgsConstructor
@Slf4j
public class ChatMessageController {
private static final String CHAT_EXCHANGE_NAME = "chat.exchange";
private final ChatService chatService;
private final RabbitTemplate template;
public void getMessageList(Long roomId, @RequestHeader("Authorization") String token) {
// RDBS에 저장된 메세지를 불러오기, 추후 적용 후 수정
return;
}
@MessageMapping("chat.message.{chatRoomId}") // /pub/cart.message.1 의 주소로 메세지를 보내면 1번 방을 구독한 유저에게 메세지가 전달된다.
public void sendMessage(@Payload MessageDto request,
@DestinationVariable String chatRoomId) {
log.info("message = {}", request.toString());
chatService.sendMessage(request); //RDBS에 메세지를 저장하는 함수
template.convertAndSend(CHAT_EXCHANGE_NAME, "room." + chatRoomId, request);
}
}
다음 글은 채팅 기능에 대해 APIC이라는 chrome 외부 툴을 통해 Test 한 방법과 그 결과에 대해서 올리려고 한다.
추가적으로 생각해 봐야 할 것은 우리가 실생활에서 사용되고 있는 채팅 앱처럼 읽지 않은 채팅방의 채팅 갯수를 표현 할 수 있도록 소켓을 통해 구현해 주려고 한다.
해당 기능이 완성되면 해당 글을 수정하여 올릴 계획이다.
출처
about rabbitTemplte : https://minholee93.tistory.com/entry/RabbitMQ-Jackson2JsonMessageConvertor
rabbitmq github : https://github.com/rabbitmq/rabbitmq-amqp1.0#routing-and-addressing