반응형
WebSocket 서버 만들기
의존성 추가
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
WebSocketInterceptor
@Slf4j
@Component
public class WebSocketInterceptor implements ChannelInterceptor {
@SneakyThrows
@Override
public Message<?> preSend(Message<?> message, MessageChannel channel) {
StompHeaderAccessor accessor = StompHeaderAccessor.wrap(message);
if (accessor.getCommand() == StompCommand.CONNECT) {
String authToken = accessor.getFirstNativeHeader("auth-token");
if (!"spring-chat-auth-token".equals(authToken)) {
throw new AuthException("fail");
}
}
return message;
}
}
WebSocketConfig
@RequiredArgsConstructor
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
private final WebSocketInterceptor webSocketInterceptor;
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/sub");
config.setApplicationDestinationPrefixes("/pub");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
// 연결 URL : ws://localhost:8080/ws-stomp/websocket
registry.addEndpoint("/ws-stomp")
// .setAllowedOrigins("http://localhost:3000") // "http://localhost:3000" 페이지로부터의 요청만 허용
.setAllowedOriginPatterns("**") // 전체 페이지로부터의 요청 허용
.withSockJS();
}
@Override
public void configureClientInboundChannel(ChannelRegistration registration) {
registration.interceptors(webSocketInterceptor);
}
}
ChatMessage
@AllArgsConstructor
@NoArgsConstructor
@Data
public class ChatMessage {
private Integer roomSeq;
private String message;
}
ChatController
@Slf4j
@RequiredArgsConstructor
@RestController
public class ChatController {
private static final Set<String> SESSION_IDS = new HashSet<>();
private final SimpMessagingTemplate messagingTemplate;
@MessageMapping("/chat") // "/pub/chat"
public void publishChat(ChatMessage chatMessage) {
log.info("publishChat : {}", chatMessage);
messagingTemplate.convertAndSend("/sub/chat/" + chatMessage.getRoomSeq(), chatMessage);
}
@EventListener(SessionConnectEvent.class)
public void onConnect(SessionConnectEvent event) {
String sessionId = event.getMessage().getHeaders().get("simpSessionId").toString();
SESSION_IDS.add(sessionId);
log.info("[connect] connections : {}", SESSION_IDS.size());
}
@EventListener(SessionDisconnectEvent.class)
public void onDisconnect(SessionDisconnectEvent event) {
String sessionId = event.getSessionId();
SESSION_IDS.remove(sessionId);
log.info("[disconnect] connections : {}", SESSION_IDS.size());
}
}
JavaScript로 접속하기
index.html
- 경로 : ~/src/main/resources/static/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<input id="input"/>
<button id="send">send</button>
<pre id="messages"></pre>
<script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.4.0/sockjs.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
<script>
const client = Stomp.over(new SockJS("/ws-stomp"));
client.connect({},
() => {
client.subscribe("/sub/chat/1", (payload) => {
document.querySelector("#messages").appendChild(document.createTextNode(payload.body + "\n"));
});
},
(error) => {
console.error(error);
}
);
document.querySelector("#send").addEventListener("click", () => {
client.send("/pub/chat", {}, JSON.stringify({
roomSeq: 1,
message: document.querySelector("#input").value,
}));
});
</script>
</body>
</html>
WebSocketClient로 접속하기
StompClientTest
@Slf4j
public class StompClientTest {
public static void main(String[] args) {
StompChatClient client = new StompChatClient("ws://localhost:8080/ws-stomp/websocket", "/sub/chat/1");
client.connect();
while (true) {
String line = new Scanner(System.in).nextLine();
try {
String[] commands = line.split(":");
Integer roomSeq = Integer.parseInt(commands[0]);
String message = commands[1];
client.send("/pub/chat", new ChatMessage(roomSeq, message));
} catch (Exception e) {
log.error("error", e);
}
}
}
@RequiredArgsConstructor
private static class StompChatClient extends StompSessionHandlerAdapter {
private final String url;
private final String subscribe;
private StompSession session;
@Override
public Type getPayloadType(StompHeaders headers) {
return ChatMessage.class;
}
@Override
public void afterConnected(StompSession session, StompHeaders connectedHeaders) {
this.session = session;
this.session.subscribe(subscribe, this);
}
@Override
public void handleFrame(StompHeaders headers, Object payload) {
ChatMessage chatMessage = (ChatMessage) payload;
log.info("receive : {}", chatMessage);
}
public void connect() {
WebSocketHttpHeaders httpHeaders = new WebSocketHttpHeaders();
StompHeaders stompHeaders = new StompHeaders();
stompHeaders.add("auth-token", "spring-chat-auth-token");
WebSocketStompClient stompClient = new WebSocketStompClient(new StandardWebSocketClient());
stompClient.setMessageConverter(new MappingJackson2MessageConverter());
stompClient.connect(url, httpHeaders, stompHeaders, this);
}
public void send(String destination, ChatMessage chatMessage) {
session.send(destination, chatMessage);
}
}
}
반응형
'Development > Spring' 카테고리의 다른 글
[Spring] SQL Mapper (with MyBatis) (0) | 2020.12.27 |
---|---|
[Spring] ORM (with JPA, Hibernate) (0) | 2020.12.27 |
[Spring] Dependency Injection (0) | 2020.12.27 |
[Spring] Distributed Lock (with MySQL, Redis) (4) | 2020.12.27 |
[Spring] Transactional (0) | 2020.12.27 |