저번 주에 이어서 이번 주도 웹소켓 공부를 했다. 웹 기초 지식이 없어서 서비스 흐름을 이해하는 데 너무 오래 걸렸다. 소켓 연결은 클라이언트에서 수행하고 서버에서는 전송된 메세지를 어디에 배포할지를 정의하는 메소드를 구현하면 된다고 이해했다.
WebSocketConfig 웹소켓 관련 설정 파일
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOriginPatterns("*")
.withSockJS();
}
@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
registry.enableSimpleBroker("/sub");
registry.setApplicationDestinationPrefixes("/pub");
}
}
Endpoint(/ws)으로 클라이언트가 웹 소켓 연결 요청을 하면 handshake 과정을 거쳐 연결이 됨!
withSocketJS()
WebSocket은 단순한 통신 구조이기 때문에 메세지가 어떤 요청인지, 어떻게 처리해야 하는지에 대한 정보가 없음.
이 정보를 담아주는 프로토콜이 STOMP 프로토콜
클라이언트와 서버가 전송할 메세지의 유형, 형식, 내용 등을 정의한다.
STOMP는 구독-발행의 구조
DestinationPrefix(”/pub”)는 클라이언트에서 소켓에 메세지를 보낼 때 주소에 붙여야 하는 prefix
Broker(”/sub”)는 클라이언트에서 publish를 통해 메세지를 보내면 subscribe에 구독(연결 요청)을 했던 사용자들에게 메세지를 보냄
프론트 예시
구독
function connect() {
var socket = new WebSocket('/ws');
stompClient = Stomp.over(socket);
stompClient.connect({}, function () {
setConnected(true);
stompClient.subscribe('/sub/rooms/5', function (greeting) {
console.log(greeting.body);
});
});
}
발행
function sendMessage() {
stompClient.send("/pub/rooms/5/chat", {}, JSON.stringify({
'message': $("#name").val(),
'senderId': 7,
'receiverId': 14,
'roomId': 5
}));
}
Controller
@RestController
@RequiredArgsConstructor
public class SocketController {
private final SocketService socketService;
@MessageMapping("/rooms/{roomId}/chat")
@SendTo("/sub/rooms/{roomId}")
public ChatResponse chat(@DestinationVariable Long roomId, ChatRequest request) {
return ChatResponse.builder()
.chatType(request.getChatType())
.roomId(request.getRoomId())
.userId(request.getUserId())
.message(request.getMessage())
.time(LocalDateTime.now())
.build();
}
}
@MessageMapping : DestinationPrefix와 결합해서 클라이언트가 SEND할 수 있는 주소가 된다. ("/pub/rooms/{roomId}/chat”)
@DestinationVariable : {roomId}처럼 목적지의 pathvariable 변수를 받는 용도
Postman에서도 웹소켓을 지원한다고 해서 테스트를 해보려 했으나 연결이 되지 않았다. 찾아보니 웹소켓, socketIO는 지원하지만 STOMP 프로토콜은 지원하지 않기 때문이었다. (raw 파일로 STOMP 설정을 다 작성하면 복잡하지만 가능은 하다고 함...)
따라서 STOMP 테스트를 지원하는 APIC이라는 웹 앱에서 테스트를 진행했다.
APIC의 화면이다. ws으로 new tab을 열고 Connection Type을 STOMP으로 설정해주면 된다.
Request URL에 웹소켓 handshake 주소를 입력하고 Connect를 클릭하면 소켓에 연결이 된다.
두 개의 APIC 창을 열어서 같은 소켓에 연결하고, 한 창에서 메세지를 보냈을 때 두 개의 창 모두 메세지를 받는 것을 확인할 수 있었다!
단, SockJS는 보안 이슈로 Origin 설정에 와일드카드를 허용하지 않는데, APIC에서는 Origin을 알 수 없어 EPIC 테스트 시에는 withSockJS();를 주석처리 해야 한다.