본문 바로가기

Dev.BackEnd/Spring

[SpringBoot] WebSocket을 활용한 채팅 구현하기.


[SpringBoot] WebSocket을 활용한 채팅 구현하기


Spring WebSocket이란?
Spring 4.0 부터 지원하고 있으며 공식문서에는 Real-time full duplex communication over TCP 이라고 설명이 되어 있다. WebSocket 프로토콜 RFC 6455는 클라이언트와 서버간의 전이중, 양방향 통신과 같은 웹 응용 프로그램을 위한 중요한 기능을 정의한다. XMLHttpRequest, 서버 전송 이벤트 등을 포함하여 웹을 보다 interactive하게 만드는 기술이다. WebSocket은 메시징(Messagine) 아키텍쳐를 의미하지만 특정 메시징 프로토콜의 사용을 요구하지는 않는다. TCP를 통한 매우 얇은 레이어로, 바이트 스트림을 메시지의 스트림으로 변환한다. 응용 프로그램에서는 이 메시지의 의미를 해석하면 된다.


다음의 예제 코드는 Spring Boot를 사용하며, Gradle을 사용하였고 클라이언트에서는 SockJs와 Stomp 라이브러리를 사용하였다.
build.gradle
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile("org.springframework.boot:spring-boot-starter-websocket")
compile("org.webjars:sockjs-client:1.0.2")
compile("org.webjars:stomp-websocket:2.3.3")

runtime('org.springframework.boot:spring-boot-devtools')
testCompile('org.springframework.boot:spring-boot-starter-test')
}

코드에서 어떻게 구현되는지 살펴보자.


WebSocketConfig.java

package com.mapia.websocket;

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

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

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/websockethandler").withSockJS();
}
}

`WebSocketConfig`클래스는 Message broker에 대한 설정을 한다. 코드에서 볼 수 있듯이 `AbstractWebSocketMessageBorkerConfigurer` 추상클래스를 상속하며 두 가지의 메서드를 `Override`하고 있다.

`enableSimpleBroker()` 메소드는 메모리 기반 메세지 브로커가 해당 api를 구독하고 있는 클라이언트에게 메세지를 전달한다.

`setApplicationDestinationPrefixes()` 메소드는 서버에서 클라이언트로부터의 메세지를 받을 api의 prefix를 설정한다.

`registerStompEndpoints()` 메소드는 클라이언트에서 WebSocket을 연결할 api를 설정한다. parameter로 넘겨받는 `StompEndpointRegistry`의 메소드인 `addEndpoint()` 메소드를 통해서 여러 가지 end point 를 설정할 수 있다.


MessageHandler.java

package com.mapia.websocket;

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class MessageHandler {
@MessageMapping("/hello")
@SendTo("/topic/roomId")
public Message broadcasting(ClientMessage message) throws Exception {
return new Message(message.getContent());
}
}

이 클래스는 Message-handling controller 이다. 해당 클래스는 @MessageMapping 어노테이션에 의해 "/hello"라는 api로 mapping 되어있다. 만약에 클라이언트에서 "../hello"라는 api로 메세지를 보내면 `broadcasting()` 메소드가 호출된다. 클라이언트로부터 오는 메세지는 `broadcasting()` 메소드의 파라미터와 binding 되어 있다. return value는 @SendTo 어노테이션에 mapping 되어있는 api를 구독하고 있는 클라이언트들에게 브로드캐스팅 된다.


서버사이드 코드는 이것이 전부이다. 클라이언트 코드를 살펴보자.

index.html

<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<textarea id="chatOutput" name="" class="chatting_history" rows="24"></textarea>
<div class="chatting_input">
<input id="chatInput" type="text" class="chat">
</div>
<script src="/webjars/sockjs-client/1.0.2/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/2.3.3/stomp.min.js"></script>
<script src="js/main.js"></script>
<script src="js/socket.js"></script>
</body>
</html>

socket.js

document.addEventListener("DOMContentLoaded", function() {
WebSocket.init();
});

let WebSocket = (function() {
const SERVER_SOCKET_API = "/websockethandler";
const ENTER_KEY = 13;
let stompClient;
let textArea = document.getElementById("chatOutput");
let inputElm = document.getElementById("chatInput");

function connect() {
let socket = new SockJS(SERVER_SOCKET_API);
stompClient = Stomp.over(socket);
stompClient.connect({}, function () {
stompClient.subscribe('/topic/roomId', function (msg) {
printMessage(JSON.parse(msg.body).content);
});
});
}

function printMessage(message) {
textArea.value += message + "\n";
}

function chatKeyDownHandler(e) {
if (e.which === ENTER_KEY && inputElm.value.trim() !== "") {
sendMessage(inputElm.value);
clear(inputElm);
}
}

function clear(input) {
input.value = "";
}

function sendMessage(text) {
stompClient.send("/app/hello", {}, JSON.stringify({'content': text}));
}

function init() {
connect();
inputElm.addEventListener("keydown", chatKeyDownHandler);
}
return {
init : init
}
})();

자바스크립트 코드에서는 클라이언트 소켓을 생성하는데 SockJS를 사용한다. 그리고 그 소켓을 `Stomp.over()`메소드를 사용하여 사용하게 되는 구조이다. .subscribe()메소드를 통해서 broadcasting 받을 api를 설정할 수 있고 `.send()`메소드를 통해서 서버 측에 message를 보낼 수 있다.


Spring WebSocket 으로 검색하면 나오는 공식 홈페이지에 설명이 너무 잘되있어서 생각보다 금방 해결할 수 있었다.

Reference> https://spring.io/guides/gs/messaging-stomp-websocket/

end