https://loy124.tistory.com/414
spring boot + apache kafka 를 활용한 미니 채팅앱 만들어보기(2) - spring boot 에서 producer, consumer 구현하
https://loy124.tistory.com/413 spring boot + apache kafka 를 활용한 미니 채팅앱 만들어보기(1) - kafka 개요 및 테스트Apache Kafka kafka는 분산 스트리밍 플랫폼으로서 메시지큐 역할을 할 수 있는 시스템이다.대
loy124.tistory.com
https://github.com/loy124/kafka-chat-server/tree/v1.0.0
GitHub - loy124/kafka-chat-server
Contribute to loy124/kafka-chat-server development by creating an account on GitHub.
github.com
앞으로 내용은 위 코드를 기반으로 진행할 예정이다. (이전 글에 작성한 내용들)
현재 구현된 내용
카프카에 데이터 전송하기 클라이언트 → [ChatController] → [ChatService] → [ChatKafkaProducer] → Kafka 토픽(chat-room)
카프카로부터 데이터 조회하기 kafka 토픽(chat-room) -> [ChatKafkaConsumer] -> [ChatService]
현재 상황은 이렇게 /api/chat/send에 데이터를 POST 요청하면
Consumer에서 해당 코드를 받아 service를 호출성공및 log가 잘 나오는 상태이다.
@KafkaListener(topics = "chat-room", groupId = "chat-group")
public void userMessageListener(ConsumerRecord<String, String> record) {
try {
ChatMessage message = objectMapper.readValue(record.value(), ChatMessage.class);
System.out.println("유저용 Kafka 수신 메시지: " + message);
chatService.handleReceivedMessage(message); // WebSocket 전송 등
} catch (Exception e) {
System.err.println("[chat-group] 처리 실패: " + e.getMessage());
}
}
이제 websocket을 연동해서 클라이언트에 전송할 차례이다.
프로세스
데이터 조회하기 kafka 토픽(chat-room) -> [ChatKafkaConsumer] -> [ChatService] -> websocket을 통해 클라이언트에 전송
websocket 연결하기
core/config/WebSocketConfig.java
package com.example.chat.chat_server.core.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws-chat") // 클라이언트 연결 주소
.setAllowedOriginPatterns("*")
.withSockJS(); // SockJS fallback
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/topic"); // 메시지 구독 경로
registry.setApplicationDestinationPrefixes("/app"); // 클라이언트 전송 prefix
}
}
이제 클라이언트에 접근할때 /ws-chat으로 접근해서 소켓을 연결하게 된다.
core/websocket/WebSocketSender.java
package com.example.chat.chat_server.core.websocket;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Component;
@Component
@Slf4j
@RequiredArgsConstructor
public class WebSocketSender {
private final SimpMessagingTemplate messagingTemplate;
public void send(String destination, Object payload) {
try {
messagingTemplate.convertAndSend(destination, payload);
log.info("WebSocket 전송 - dest: {}, payload: {}", destination, payload);
} catch (Exception e) {
log.error("WebSocket 전송 실패 - dest: {}, error: {}", destination, e.getMessage());
}
}
}
클라이언트 만들기
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>
<script>
const socket = new SockJS('http://localhost:8080/ws-chat');
const stompClient = Stomp.over(socket);
stompClient.connect({}, () => {
console.log('WebSocket 연결 완료');
// 특정 채팅방 구독 (예: room-1001)
stompClient.subscribe('/topic/chat/room-1', (msg) => {
const chat = JSON.parse(msg.body);
console.log("받은 메시지:", chat);
});
});
</script>
</body>
</html>
여기에서 사용되고 있는 stomp는 Spring WebSocket에서 메시지 목적지를 정하고 구독/전송을 구조화할 수 있게 해주는 경량 프로토콜이다. 기본적으로 Spring WebSocket은 stomp 기반으로 구성되어 있어 클라이언트 단에서
websocket + stomp를 사용하는 구조로 바꿔줘야 한다.
위 서버를 vscode live-server (vscode extensions) 로 기동해준다.
PostMan에서 /topic/chat-room-1로 데이터를 보내게되면
데이터 전송-> kafka 토픽(chat-room) -> [ChatKafkaConsumer] -> [ChatService] -> websocket을 통해 클라이언트에 전송
(/topic/chat/room-1) -> 클라이언트에서 /topic/chat/room-1 로 구독하여 메시지를 전달 받음
위와같은 프로세스로 동작하게 된다.
클라이언트완성하기
이제 Postman으로 보내던 요청을 client에서도 요청을 보낼수 있게 변경한다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>STOMP 채팅 - 다중 유저</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>
<style>
body { font-family: Arial, sans-serif; padding: 1rem; }
#chat-box { border: 1px solid #ccc; height: 300px; overflow-y: auto; padding: 10px; margin-bottom: 10px; }
.me { text-align: right; color: blue; }
.other { text-align: left; color: green; }
</style>
</head>
<body>
<h2>채팅방: room-1</h2>
<label>사용자 이름 (user ID):</label>
<input type="text" id="user-id" value="userA" placeholder="userA 또는 userB" />
<button onclick="connect()">접속</button>
<div id="chat-ui" style="display: none;">
<div id="chat-box"></div>
<input type="text" id="message-input" placeholder="메시지를 입력하세요" style="width: 70%">
<button onclick="sendMessage()">전송</button>
</div>
<script>
let stompClient = null;
let userId = '';
const roomId = 'room-1';
function connect() {
userId = document.getElementById('user-id').value.trim();
if (!userId) {
alert('사용자 이름을 입력해주세요');
return;
}
const socket = new SockJS('http://localhost:8080/ws-chat');
stompClient = Stomp.over(socket);
stompClient.connect({}, () => {
console.log('WebSocket 연결 완료 (유저:', userId, ')');
document.querySelector('#chat-ui').style.display = 'block';
stompClient.subscribe(`/topic/chat/${roomId}`, (msg) => {
console.log(msg)
const chat = JSON.parse(msg.body);
displayMessage(chat);
});
});
}
function sendMessage() {
const messageInput = document.getElementById('message-input');
const message = messageInput.value.trim();
if (!message) return;
axios.post('http://localhost:8080/api/chat/send', {
roomId: 1,
sender: userId,
message: message
}).then(() => {
messageInput.value = '';
}).catch(err => {
console.error('전송 실패:', err);
});
}
function displayMessage(chat) {
const box = document.querySelector('#chat-box');
const div = document.createElement('div');
div.className = (chat.sender === userId) ? 'me' : 'other';
div.innerText = `${chat.sender}: ${chat.message}`;
box.appendChild(div);
box.scrollTop = box.scrollHeight;
}
</script>
</body>
</html>
위와 같이 요청하게 되면
cors 이슈가 발생한다.
CORS
- 기본적으로 브라우저는 다른 출처(IP가 같더라도 포트가 다른경우도 포함) 보안상 차단한다.
현재 클라이언트(vscode-live-server : 5500 port)
따라서 백엔드 단에서 해당 CORS에 대한 에러를 해결해주기 위해 추가적으로 설정을 해줘야한다.
core/config/CorsConfig.java
package com.example.chat.chat_server.core.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter(){
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true); // 내 서버가 응답을 할때 json을 자바스크립트에서 처리할수 있게 할지 설정
// config.addAllowedOrigin("*"); // 모든 ip 응답 허용
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*"); // 모든 hedaer 응답 허용
config.addAllowedMethod("*"); // 모든 post,get,pu,delete,path 요청등 허용
config.addExposedHeader("Authorization");
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}
위와 같이 cors에 대한 설정 완료하고 서버를 다시 키고 테스트를 진행하면 된다.
위와 같이 간단한 채팅이 되는것을 확인할 수 있다.
해당 부분까지 완성 코드는 다음과 같다.
https://github.com/loy124/kafka-chat-server/tree/v2.0.0
GitHub - loy124/kafka-chat-server
Contribute to loy124/kafka-chat-server development by creating an account on GitHub.
github.com
kafka-setting -> docker compose kafka
client -> web client html
chat-server-backend -> spring boot kafka + websocket backend
'프로젝트 > 미니 프로젝트' 카테고리의 다른 글
서로 다른 클라우드 서버를 VPN으로 묶어 쿠버네티스 구축하기 (2) - containerd, kubeadm 설치및 셋팅하기 (0) | 2025.05.11 |
---|---|
spring boot + apache kafka 를 활용한 미니 채팅앱 만들어보기(2) - spring boot 에서 producer, consumer 구현하기 (0) | 2025.05.05 |
spring boot + apache kafka 를 활용한 미니 채팅앱 만들어보기(1) - kafka 개요 및 테스트 (0) | 2025.05.05 |
원격 로그 수집 시스템 구성하기 (elastic stack) (0) | 2022.11.17 |
데이터 백업 시스템 구성하기 (0) | 2022.10.26 |