websocket을 이용한 채팅만들기

pom.xml 에 라이브러리를 추가한다.
채팅, 푸시알림 등을 받으려면 지속적인 연결이 필요하다. 이때 사용하는 기술이 websocket
websocket을 지원안하는 브라우저가 있다. websocket을 지원안하는 브라우저를 위한 기술이 필요하다.
sockjs (스프링이 지원), socket.io 와 같은 라이브러리가 있다.

		<!-- websocket -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-websocket</artifactId>
		</dependency>

		<!-- 프론트단에서 사용하는 sockjs 라이브러리 -->
		<dependency>
			<groupId>org.webjars</groupId>
			<artifactId>sockjs-client</artifactId>
			<version>1.0.2</version>
		</dependency>

websocket을 사용하려면 몇가지 설정이 필요하다.
로그인을 한 사용자만 채팅을 하도록 할 예정.
/chatrooms
/ws <– 웹 소켓 엔드포인트 URL (웹소켓은 브라우저가 http로 요청을 보낸후, 서버가 웹소켓을 지원해주면 웹 소켓연결로 바뀐다.)

다음의 클래스와 파일을 작성한다.
설명은 주석을 참고

WebSocketConfig

@Configuration
@EnableWebSocket // 웹소켓에 대해 대부분 자동설정을 한다.
public class WebSocketConfig implements WebSocketConfigurer {
    // WebSocketConfigurer를 구현하여 추가적인 설정을 한다.

    // WebSocketHandler를 추가한다.
    // 1. 클라이언트가 접속을 했을 때 특정 메소드가 호출
    // 2. 클라이언트가 접속을 close했을 때 특정 메소드가 호출
    // 3. 클라이언트가 메시지를 보냈을 때 특정 메소드 호출
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
        webSocketHandlerRegistry.addHandler(new ChatSocketHandler(), "/ws").withSockJS();
    }
}

ChatMessage

@Data
public class ChatMessage {
    private String name;
    private String message;
}

ChatSocketHandler


// TextWebSocketHandler인터페이스를 구현한다.
public class ChatSocketHandler extends TextWebSocketHandler {
    ObjectMapper objectMapper = new ObjectMapper();
    List<WebSocketSession> list = Collections.synchronizedList(new ArrayList<>());
    // 웹 소켓에 연결될 때 호출되는 메소드
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        System.out.println("접속===========================");
        System.out.println(session.getId());
        System.out.println("접속===========================");
        list.add(session);
    }

    // 메시지를 전송받았을때 호출되는 메소드
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        System.out.println("메시지가 왔어요...");
        System.out.println(session.getId() + ", " + message.getPayload());
        System.out.println("메시지가 왔어요...");
        AbstractAuthenticationToken principal = (AbstractAuthenticationToken)session.getPrincipal();
        BlogSecurityUser securityUser = (BlogSecurityUser)principal.getPrincipal();

        ChatMessage clientMessage = objectMapper.readValue(message.getPayload(), ChatMessage.class);


        ChatMessage chatMessage = new ChatMessage();
        chatMessage.setName(securityUser.getName());
        chatMessage.setMessage(clientMessage.getMessage());
        String json = objectMapper.writeValueAsString(chatMessage);
        for(WebSocketSession wss : list){
            wss.sendMessage(new TextMessage(json));
        }
    }

    // 웹 소켓이 연결이 클로즈될때 호출되는 메소드
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        System.out.println("접속 close===========================");
        System.out.println(session.getId());
        System.out.println("접속 close===========================");
        list.remove(session);
    }
}

ChatController

@Controller
public class ChatController {
    @GetMapping("/chatrooms")
    public String chatrooms(){
        return "chatrooms";
    }
}

chatrooms.js

// sockjs 를 이용한 서버와 연결되는 객체
var ws = null;

function setConnected(connected) {
}

function showMessage(message) {
    console.log(message);
    var jsonMessage = JSON.parse(message);

    $("#chatArea").append(jsonMessage.name + ' : ' + jsonMessage.message + '\n');

    var textArea = $('#chatArea');
    textArea.scrollTop( textArea[0].scrollHeight - textArea.height()   );

}


function connect() {
    // SockJS라이브러리를 이용하여 서버에 연결
    ws = new SockJS('/ws');
    // 서버가 메시지를 보내주면 함수가 호출된다.
    ws.onmessage = function(message) {
        showMessage(message.data);
    }
}

function disconnect() {
    if (ws != null) {
        ws.close();
    }
    setConnected(false);
    console.log("Disconnected");
}

function send() {
    // 웹소켓 서버에 메시지를 전송
    ws.send(JSON.stringify({'message': $("#chatInput").val()}));
    // 채팅입력창을 지우고 포커싱하라.
    $("#chatInput").val('');
    $("#chatInput").focus();
}


// $(함수(){ 함수내용 });  // jquery에서 문서가 다 읽어들이면 함수()를 호출한다.
$(function () {

    connect();

    // 채팅입력창에서 키가 눌리면 함수가 호출
    // 엔터를 입력하면 send()함수가 호출
    $("#chatInput").keypress(function(e) {
        if (e.keyCode == 13){
            send();
        }
    });

    $( "#sendBtn" ).click(function() { send(); });
});

chatrooms.html

<!DOCTYPE html>
<html lang="ko" xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"/>
    <script  type="text/javascript" src="/webjars/jquery/3.3.1-2/jquery.min.js"></script>
    <script  type="text/javascript" src="/webjars/sockjs-client/1.0.2/sockjs.min.js"></script>

    <script src="/js/chatroom.js"></script>
    <title>chat room</title>
</head>

<body>

<div class="jumbotron">
    <h1>chat room</h1>
</div>

<div class="container">

    <div class="col-sm-12 col-md-12">
        <textarea cols="80" rows="15" id="chatArea" class="form-control"></textarea>
    </div>
    <div class="col-sm-12 col-md-12">
        <input type="text" id="chatInput" class="form-control"/>
        <input type="button" id="sendBtn" value="전송" class="btn btn-primary btn-small"/>
    </div>

</div>
</body>
</html>