SockJS
SockJS는 어플리케이션이 WebSocket API를 사용하도록 허용하지만, 브라우저에서 WebSocket을 지원하지 않는 경우에 대안으로 어플리케이션의 코드를 변경할 필요 없이 런타임에 필요할 때 대체를 하는 것이다.
SockJS는 다양한 기술을 이용해 웹소켓을 지원하지 않는 브라우저에서 정상적으로 동작하도록 해준다. 전송 타입은 크게 다음의 3가지로 분류된다
•
WebSocket
•
HTTP Streaming
•
HTTP Long Polling
STOMP (Simple/Stream Text Oriented Message Protocol)
WebSocket 위에서 동작하는 문자 기반 메시징 프로토콜, 클라이언트와 서버가 전송할 메시지의 유형, 형식, 내용들을 정의하는 매커니즘이다.
기본적으로 pub/sub 구조로 되어있어, 메시지를 전송하고 받아 처리하는 부분이 확실히 정해져 있다.
http와 마찬가지로 frame을 사용해 전송하는 프로토콜이다.
pub/sub 구조란?
STOMP의 Frame 구조
COMMAND // 메시지 타입을 나타내는 문자열, SEND, SUBSCRIBE, UNSUBSCRIBE 등이 있다.
header1 : value1 // 추가 정보를 제공하는 헤더
header2 : value2 // 추가 정보를 제공하는 헤더
Body^@ // 메시지의 내용, ^@ : NULL 문자이다. Body의 끝을 나타낸다.
Java
복사
SWAPSWAP의 Frame 구조 예시 (채팅 방 입장시 순서대로)
1.
웹 소켓 연결 초기화, 클라이언트가 서버에게 웹 소켓 연결을 요청하는데 사용
2.
웹 소켓 연결이 성공적으로 설정 되었음을 나타냄
3.
클라이언트가 특정 대상에 대한 구독을 요청하는 SUBSCRIBE 프레임
4.
클라이언트가 메시지를 특정 대상으로 보내는 명령어
•
destination : 메시지가 전송되는 대상의 목적지
5.
특정 채팅방으로부터 수신된 실제 메시지 내용을 나타내는 MESSAGE 프레임
•
destination : 메시지가 전송된 대상의 목적지
채팅방 입장 시 실행되는 스크립트
1.
chatroom.html가 로드될 때 웹 소켓 연결을 생성. /ws/chat는 웹 소켓 엔드포인트의 URL.
2.
Stomp.js 라이브러리를 사용하여 WebSocket을 Stomp 프로토콜로 래핑.
•
onConnect는 연결 성공 시 , onError는 실패 시 실행되는 콜백 함수.
3.
연결 성공 시 /quere/chat/room’ + chatRoomId 에 대한 구독을 설정한다.
4.
메시지를 보낼 때 실행되는 함수
•
/app/chat/message로 메시지를 보낸다.
5.
보낸 메시지는 해당 메소드로 들어와 chatRoomService.saveMessage(message)에서 처리된 후
6.
“/queue/chat/room” + message.roomId()로 메시지를 전송 → 해당 채널을 구독하고 있는 구독자는 메시지를 받는다.
STOMP를 사용하지 않고 순수 웹소켓을 썼을 때의 문제점
웹 소켓은 기본적으로 Text, Binary 타입의 메시지를 양방향으로 주고받을 수 있는 프로토콜이다.
그런데 웹소켓만 사용하는 프로젝트가 커지면, 주고 받는 메시지에 대한 형식이 중요하게 된다.
여기서 STOMP를 사용하면 어떠한 장점을 누릴 수 있을까
STOMP는 커맨드, 헤더, 바디로 이루어진 프레임 단위를 정의해두었기 때문에 메시징 프로토콜과 메시징 형식을 개발할 필요가 없다. (즉 하위 프로토콜과 메시지 컨벤션을 정의할 필요가 없다)
연결 주소마다 새로운 핸들러를 구현하고 설정해줄 필요가 없다. WebSocket만 사용한다면 텍스트 기반의 웹 소켓 통신을 처리하기 위해서는 TextWebSocketHandler를 상속 받은 핸들러를 구현해 주어야 한다.
메시지 브로커를 사용하면 구독을 관리하고 메시지를 Broadcast 하는데 사용할 수 있다. 또한 외부 Messaging Queue를 사용할 수 있다.
위 그림은 순수 웹 소켓을 사용했을 때이고, 오른쪽은 STOMP를 사용했을 때이다.
웹 소켓만 사용했을 때는 날 것의 메시지만 오고가는 반면에 STOMP를 사용했을 때는 COMMAND, header, body의 형태로 오고 가는 것을 볼 수 있다.
동작 흐름
•
메시지를 보내려는 발신자, 메시지를 받으려는 구독자가 있다. 구독자는 /topic 이라는 경로를 구독하고 있다.
•
발신자는 /topic을 destination 헤더로 넣어 메시지를 메시지 브로커를 통해 구독자들에게 곧바로 송신할 수 있다.
•
혹은 서버 내에서 어떤 가공처리가 필요하다면 /app 경로로 메시지를 송신할 수도 있다.
◦
서버가 가공 처리를 끝낸 데이터를 /topic이라는 경로를 담아 메시지 브로커에게 전달하면 메시지 브로커는 전달받은 메시지를 /topic을 구독하는 구독자들에게 최종적으로 전달한다.
Spring에서 STOMP를 사용했을 때 장점
•
하위 프로토콜, 컨벤션을 따로 정의할 필요가 없다.
◦
STOMP가 프레임 단위로 정의해준다.
•
연결 주소마다 새로운 Handler를 구현하고 설정해줄 필요가 없다.
◦
@Controller 어노테이션을 사용하면 된다.
•
외부 Messaging Queue를 사용할 수 있다.
◦
Spring이 기본적으로 제공하는 내장 메시지 브로코가 아닌 외부 메시지 큐를 연동해서 사용할 수 있다.
•
Spring Security를 사용할 수 있다.
◦
오고가는 메시지에 대한 보안 설정을 할 수 있다.
여기서 ChannelInterceptor는 메시지 전송 전과 후에 특정 작업을 수행할 수 있도록 하는 인터페이스이다. 위 preSend메서드는 메시지가 실제로 채널로 전송되기 전에 호출되는데, 이를 통해 메시지를 가로채고 원하는 작업을 수행할 수 있다.
위 같은 경우에는 STOMP의 Frame구조에서 COMMAND가 CONNECT일 때, 즉 클라이언트가 서버에게 연결을 요청할 때 실행되는 메서드이다. 만약 해당 if()문 안에 JWT 토큰을 validation 하는 메서드는 넣어준다면 연결을 요청하기 전에 JWT 토큰을 검증할 수 있을 것이다.