webSocket 채팅 기능 구현

song yuheon·2023년 10월 8일
0

스터디 계기

프로젝트 진행 중 멘토님의 추가 기능 제안을 받았다. 이에 따라, 기존의 여론조사 웹사이트에 websocket을 활용한 채팅봇 기능을 추가하고자 한다.

먼저, 실시간 채팅 구현을 위해 build.gradle에 다음 의존성을 포함시켜야 한다.

build.gradle

	// websocket
	implementation 'org.springframework.boot:spring-boot-starter-websocket'

의존성 추가 후, WebSocketConfig 클래스를 생성하여 웹소켓 설정을 한다.

WebSocketConfig

@Configuration // 스프링 프레임 워크 설정 클래스로 등록
@EnableWebSocketMessageBroker // 웹소켓 서버 활성화하는데 사용한다.
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
// =>  웹소켓 설정을 사용자 정의한다.

	@Override
	public void configureMessageBroker(MessageBrokerRegistry config) {
		config.enableSimpleBroker("/chatMessage");
		//chatMessage 로 시작하는 목적지 메시지 관리
        config.setApplicationDestinationPrefixes("/app");
        //목적지가 /app 시작 메시지 라우팅
	}

	@Override
	public void registerStompEndpoints(StompEndpointRegistry registry) {
		registry.addEndpoint("/websocket");
		// URL을 웹소켓 엔드포인트로 등록한다. 클라이언트는 이 URL을 통해 웹소켓 연결을 초기화
	}

}

다음으로, /chat 주소로의 GET 요청 시 해당 페이지를 반환하는 컨트롤러가 필요하다.

ChatViewController 생성

@Controller
public class ChatViewController {
    @GetMapping("/chat")
    public String chatView(){
        return "chat";
    }
}

채팅에 사용될 HTML 페이지를 작성한다.

chat.html

<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>채팅 페이지</title>
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
    <link href="/css/main.css" rel="stylesheet">
    <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/@stomp/stompjs@7.0.0/bundles/stomp.umd.min.js"></script>
    <script src="/js/app.js"></script>
</head>
<body>
<div id="main-content" class="container">
    <h2 class="text-center">채팅 페이지</h2>
    <div class="row">
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="chatConnect">채팅 연결:</label>
                    <button id="chatConnect" class="btn btn-primary" type="submit">연결</button>
                    <button id="chatDisconnect" class="btn btn-danger" type="submit" disabled="disabled">연결 끊기</button>
                </div>
            </form>
        </div>
        <div class="col-md-6">
            <form class="form-inline">
                <div class="form-group">
                    <label for="name">채팅 메시지:</label>
                    <input type="text" id="name" class="form-control" placeholder="메시지를 입력하세요...">
                    <button id="messageSend" class="btn btn-success" type="submit">전송</button>
                </div>
            </form>
        </div>
    </div>
    <div class="row mt-4">
        <div class="col-md-12">
            <table id="conversation" class="table table-bordered">
                <thead>
                <tr>
                    <th class="text-center">채팅 시작</th>
                </tr>
                </thead>
                <tbody id="message">
                </tbody>
            </table>
        </div>
    </div>
</div>
</body>
</html>

우선 기능은 연결을 하는 기능, 연결 끊는 기능, 메시지 처리하는 기능을 추가할려구 한다.

각 버튼을 클릭할때 js를 실행 하도록 설계 한다.

app.js파일

  • 전체 코드
const client = new StompJs.Client({
    brokerURL: 'ws://192.168.1.77:8080/gs-guide-websocket'
});

client.onConnect = (frame) => {
    setConnected(true);
    console.log('Connected: ' + frame);
    client.subscribe('/chatMessage', (chating) => {
        showChating(JSON.parse(chating.body).content);
    });
};

client.onWebSocketError = (error) => {
    console.error('Error with websocket', error);
};

client.onStompError = (frame) => {
    console.error('Broker reported error: ' + frame.headers['message']);
    console.error('Additional details: ' + frame.body);
};

function setConnected(connected) {
    $("#chatConnect").prop("disabled", connected);
    $("#chatDisconnect").prop("disabled", !connected);
    if (connected) {
        $("#conversation").show();
    }
    else {
        $("#conversation").hide();
    }
    $("#message").html("");
}

function connect() {
    client.activate();
}

function disconnect() {
    client.deactivate();
    setConnected(false);
    console.log("Disconnected");
}

function sendMessage() {
    client.publish({
        destination: "/app/chat",
        body: JSON.stringify({'name': $("#name").val()})
    });
}

function showChating(message) {
    $("#message").append("<tr><td>" + message + "</td></tr>");
}

$(function () {
    $("form").on('submit', (e) => e.preventDefault());
    $("#chatConnect").click(() => connect());
    $("#chatDisconnect").click(() => disconnect());
    $("#messageSend").click(() => sendMessage());
});
  • 버튼 클릭했을때
$(function () {
    $("form").on('submit', (e) => e.preventDefault());
    $("#chatConnect").click(() => connect());
    $("#chatDisconnect").click(() => disconnect());
    $("#messageSend").click(() => sendMessage());
});

해당 버튼에 대한 함수로 이동된다.

  • connect() / disconnect()
function connect() {
    client.activate();
}

function disconnect() {
    client.deactivate();
    setConnected(false);
    console.log("Disconnected");
}

connect()는 웹소켓 연결을 활성화 시킨다.
disconnect()는 웹소켓 연결을 비활성화 시킨다.

  • sendMessage()
function sendMessage() {
    client.publish({
        destination: "/app/chat",
        body: JSON.stringify({'name': $("#name").val()})
    });
}

접속자가 입력한 메시지를 서버의 /app/chat으로 전송한다.

  • setConnected()
    연결했는지 여부(상태)를 html로 반영한다.
function setConnected(connected) {
    $("#chatConnect").prop("disabled", connected);
    $("#chatDisconnect").prop("disabled", !connected);
    if (connected) {
        $("#conversation").show();
    }
    else {
        $("#conversation").hide();
    }
    $("#message").html("");
}
  • 연결 성공시 콜백 함수 호출
client.onConnect = (frame) => {
    setConnected(true);
    console.log('Connected: ' + frame);
    client.subscribe('/chatMessage', (chating) => {
        showChating(JSON.parse(chating.body).content);
    });
};

setConnected(true)로 html의 websocket의 연결 상태를 업데이트한다.
서버에서 /chatMessage로 오는 메시지를 받기 위해 준비한다.
subscride 하면 /chatMessage로 오는 메시지를 실시간으로 받을 수 있다.

ChatController 구현

메시지 전송 처리를 위한 로직이 필요하다.

@Controller
public class ChatController {
    @MessageMapping("/chat")
    @SendTo("/chatMessage")
    public ChatingContent greeting(ChatMessageDto message) throws Exception {
        Thread.sleep(100);
        return new ChatingContent( HtmlUtils.htmlEscape(message.getChatingMessage()) );
    }
}

-> @MessageMapping("/chat")
@SendTo("/chatMessage")
/api/chat으로 오는 메시지를 /chatMessage 주소로 전송

message.getChatingMessage() -> 메시지의 내용을 가져온 다음에
htmlUtils.htmlEscape를 이용해서 html문자를 안전하게 변환 한다.
=> 스크립트 인젝션 공격으로 데이터 처리

위에 콜백함수에서 잡고 ShowChagin 함수에서 html의 message id에 추가해준다.

결과 화면


참고 레퍼런스


profile
backend_Devloper

0개의 댓글