🖊️ 공식 API문서 https://spring.io/guides/gs/messaging-stomp-websocket/
🖊️애플리케이션 클래스는 SpringBoot Project를 생성하면 자동으로 생성되기 때문에 안가져와도 됨
🌱필요한 lib
- springframework.boot:spring-boot-starter-websocket
- webjars(웹자르 관련): sockjs, stomp, bootstrap, jquery
=> 추후 html에 app.js와 함께 링크함🌱내 sts 설정
🌼필요한 lib는 링크에 있고, 나머지는 수업시간에 추가한 swagger, mapper 등이다.
plugins { id 'java' id 'war' id 'org.springframework.boot' version '2.7.9' id 'io.spring.dependency-management' version '1.0.15.RELEASE' } group = 'hta' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' configurations { compileOnly { extendsFrom annotationProcessor } } repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-websocket' implementation 'org.webjars:webjars-locator-core' implementation 'org.webjars:sockjs-client:1.0.2' implementation 'org.webjars:stomp-websocket:2.3.3' implementation 'org.webjars:bootstrap:3.3.7' implementation 'org.webjars:jquery:3.1.1-1' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat' testImplementation 'org.springframework.boot:spring-boot-starter-test' implementation 'org.apache.tomcat.embed:tomcat-embed-jasper' implementation 'javax.servlet:jstl' implementation 'org.modelmapper:modelmapper:2.4.0' // https://mvnrepository.com/artifact/io.springfox/springfox-swagger2 implementation 'io.springfox:springfox-swagger2:2.9.2' // https://mvnrepository.com/artifact/io.springfox/springfox-swagger-ui implementation 'io.springfox:springfox-swagger-ui:2.9.2' } tasks.named('test') { useJUnitPlatform() }
🌱HelloMessage 클래스
- 클라이언트(입력) -> 서버로 전송한 메세지를 담는 객체
- 추후 Controller에서 @MessageMapping("/hello") 어노테이션이 붙은 메서드인 greeting()의 파라미터로 들어감
package com.example.messagingstompwebsocket; public class HelloMessage { private String name; //기본 생성자 public HelloMessage() {} //생성자 public HelloMessage(String name) { this.name = name; } //get메소드 public String getName() { return name; } //set메소드 public void setName(String name) { this.name = name; } }
🌱Greeting 클래스
- HelloMessage객체의 메세지를 받아 클라이언트 뷰로 전송할때 사용
- 추후 Controller에서 @MessageMapping("/hello") 어노테이션이 붙은 메서드인 greeting()의 반환타입으로 들어감
public class Greeting { private String content; public Greeting() {} public Greeting(String content) { this.content = content; } public String getContent() { return content; } }
🌱GreetingController
🌼STOMP를 사용하여 /hello 로부터 메시지를 받았을 때, /topic/greetings 채널로 메시지를 보내는 역할
package com.example.messagingstompwebsocket; import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.stereotype.Controller; import org.springframework.web.util.HtmlUtils; @Controller public class GreetingController { @MessageMapping("/hello") @SendTo("/topic/greetings") public Greeting greeting(HelloMessage message) throws Exception { Thread.sleep(1000); // simulated delay return new Greeting("Hello, " + HtmlUtils.htmlEscape(message.getName()) + "!"); } }
🌼@MessageMapping("/hello")
- 클라이언트에서 보낸 메시지(HelloMessage객체)가 매핑되어 greeting메서드를 호출하도록 지정함
- /hello : STOMP 메시지의 대상 주소
1. 참고로 클라이언트는 app.js에서 stompClient.send()메서드를 통해 /app/hello 주소로 메세지를 전송하도록 설정함
🌼@SendTo("/topic/greetings")
- 메서드의 반환값을 지정된 채널로 보내도록 설정(서버 -> 클라이언트 전송)
- greeting() 메서드에서 반환된 Greeting객체는 "/topic/greetings" 채널로 전송
1. 참고로 클라이언트는 app.js에서 stompClient.subscribe('/topic/greetings', function (greeting) 코드를 통해 /topic/greetings 채널을 구독하고 있음
2. 구독채널에 메시지가 도착할 때마다 function (greeting) 함수가 호출되도록 함
🌼HtmlUtils.htmlEscape(전송메세지)
- 받은 메시지에서 HTML escape 처리를 해주는 메서드(XSS 공격 등의 보안 이슈를 예방)
- 보안을 위해 작성하는 메서드
package com.example.messagingstompwebsocket; import org.springframework.context.annotation.Configuration; import org.springframework.messaging.simp.config.MessageBrokerRegistry; import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry; import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; @Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker("/topic"); config.setApplicationDestinationPrefixes("/app"); } @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/gs-guide-websocket").withSockJS(); } }
🌼@Configuration
-클래스가 SpringBoot에서 자동으로 인식되지 않으므로 @Configuration을 사용하여 빈 등록 해줌
🌼@EnableWebSocketMessageBroker
- 스프링 웹소켓을 사용하기 위한 설정
- 웹소켓 연결 시 필요한 MessageBroker를 제공함
- Message Broker: STOMP 프로토콜을 이용하여 메세징처리를 해주는 중간 매개체
- WebSocketMessageBrokerConfigurer 인터페이스 구현과 @Configuration 필수
🌼WebSocketMessageBrokerConfigurer 인터페이스
- Websocket 메세징을 위한 구성을 제공
🌼메서드
- void configureMessageBroker(MessageBrokerRegistry config)
- 브로커를 구성하는 메서드. 브로커가 구독하고 있는 대상과 구독을 위한 기본 URL을 설정할 수 있음
1. config.enableSimpleBroker("/topic");
->"/topic" 주제를 구독한 클라이언트에게 서버에 존재하는 메세지를 발신
->즉, app.js에서 구독한 클라이언트에게 발신처리
2. config.setApplicationDestinationPrefixes("/app");
->클라이언트가 메시지를 보낼 때 사용하는 목적지에 접두어를 설정함
->저렇게 설정함으로써 클라이언트에서 전송한 메시지를 효율적으로 분류하고 처리함
->클라이언트가 app.js에서 /app/hello를 구독했다면 @MessageMapping("/hello")을 설정한 컨트롤러가 호출됨
- void registerStompEndpoints(StompEndpointRegistry registry)
- 웹소켓 엔드포인트를 등록하는 메서드. SockJS를 사용하여 웹소켓에 연결할 수 있도록 설정
- 즉, 브라우저에서 WebSocket을 지원하지 않는 경우에도 WebSocket 연결을 사용할 수 있도록 함
- app.js에 다음과 같이 SockJS url을 등록함
var socket = new SockJS('/gs-guide-websocket');
var stompClient = null; function setConnected(connected) { $("#connect").prop("disabled", connected); $("#disconnect").prop("disabled", !connected); if (connected) { $("#conversation").show(); } else { $("#conversation").hide(); } $("#greetings").html(""); } function connect() { var socket = new SockJS('/gs-guide-websocket'); stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { setConnected(true); console.log('Connected: ' + frame); stompClient.subscribe('/topic/greetings', function (greeting) { showGreeting(JSON.parse(greeting.body).content); }); }); } function disconnect() { if (stompClient !== null) { stompClient.disconnect(); } setConnected(false); console.log("Disconnected"); } function sendName() { stompClient.send("/app/hello", {}, JSON.stringify({'name': $("#name").val()})); } function showGreeting(message) { $("#greetings").append("<tr><td>" + message + "</td></tr>"); } $(function () { $("form").on('submit', function (e) { e.preventDefault(); }); $( "#connect" ).click(function() { connect(); }); $( "#disconnect" ).click(function() { disconnect(); }); $( "#send" ).click(function() { sendName(); }); });
<!DOCTYPE html> <html> <head> <title>Hello WebSocket</title> <link href="/webjars/bootstrap/css/bootstrap.min.css" rel="stylesheet"> <link href="/main.css" rel="stylesheet"> <script src="/webjars/jquery/jquery.min.js"></script> <script src="/webjars/sockjs-client/sockjs.min.js"></script> <script src="/webjars/stomp-websocket/stomp.min.js"></script> <script src="app.js"></script> </head> <body> <!-- noscript: 브라우저가 js를 지원하지 않는 경우 뜨는 문구 --> <noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websocket relies on Javascript being enabled. Please enable Javascript and reload this page!</h2></noscript> <div id="main-content" class="container"> <div class="row"> <div class="col-md-6"> <form class="form-inline"> <div class="form-group"> <label for="connect">WebSocket connection:</label> <button id="connect" class="btn btn-default" type="submit">Connect</button> <button id="disconnect" class="btn btn-default" type="submit" disabled="disabled">Disconnect </button> </div> </form> </div> <div class="col-md-6"> <form class="form-inline"> <div class="form-group"> <label for="name">메세지 입력</label> <input type="text" id="name" class="form-control" placeholder="메세지를 입력하세요."> </div> <button id="send" class="btn btn-default" type="submit">Send</button> </form> </div> </div> <div class="row"> <div class="col-md-12"> <table id="conversation" class="table table-striped"> <thead> <tr> <th>Greetings</th> </tr> </thead> <tbody id="greetings"> </tbody> </table> </div> </div> </div> </body> </html>