일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- zsh
- failed upgrade header
- beforeunload
- omz
- Intellij
- SSH
- 토이프로젝트
- alpine
- MSA
- byungjoo kim
- 커리어스킬
- java
- Dominic
- docker
- AWS
- Spring
- python
- GIT
- remote
- iTerm2
- handshake
- WebSocket
- 페이지이동경고
- socjs
- nginx
- rsa충돌
- 김병주
- gcOverhead
- KPT
- docker-compose
- Today
- Total
Eyeeshot BloG
Sping boot + websocket Hands On 본문
Spring frameworkd는 WebSocket 메시지를 처리하는 Client, Server 측 application을 작성하는데
사용할 수 있는 WebSocket API를 제공하고 있습니다.
Gradle 로 작업
build.gladle
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
implementation 'org.springframework.boot:spring-boot-starter-websocket:2.3.4.RELEASE'
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'
}
1. WebSocket end-point message broker 구성 ,config 설정
package com.eyeeshot.device.config;
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 registry) {
// 메시지 브로커는 특정 주제를 구독 한 연결된 모든 클라이언트에게 메시지를 broadcast 합니다.
registry.enableSimpleBroker("/sub");
// 시작되는 메시지가 message-handling methods으로 라우팅 되어야 한다는 것을 명시합니다.
registry.setApplicationDestinationPrefixes("/pub");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/console/device").withSockJS();
}
}
registerStompEndpoints - client 에서 웹소켓 연결할때 쓸 endpoint를 설정합니다.
setApplicationDestinationPrefixes - 연결후 보낼 메시지 앞쪽 Prefixes 를 pub 으로 설정합니다.
enableSimpleBroker - sub 으로 들어오는것에 대해 broadcast 합니다.
2. 클라이언트와 서버간에 교환되는 메시지 페이로드 모델 생성
package com.eyeeshot.device.model;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class ConsoleRequestModel {
private String privateKeyPem;
private String certificatePem;
private String algorithm;
private String thingName;
private String topic;
private String message;
private String shadowState;
}
3. Controller
client 에서 message를 수신한 다음 다른 client 에게 broadcast 하는 부분
package com.eyeeshot.device.controller;
import com.eyeeshot.device.model.ConsoleRequestModel;
import com.eyeeshot.device.service.MqttService;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.handler.annotation.DestinationVariable;
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.Payload;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Controller;
@Log4j2
@Controller
public class ConsoleController {
@Autowired
private SimpMessagingTemplate simpMessagingTemplate;
private final MqttService mqttService;
public ConsoleController(MqttService mqttService) {
this.mqttService = mqttService;
}
@GetMapping(value = "")
public ModelAndView ready() {
ModelAndView mav = new ModelAndView();
mav.setViewName("console");
return mav;
}
@MessageMapping("/connect")
public void connect(@Payload ConsoleRequestModel messageModel) {
log.info(messageModel.toString());
try {
mqttService.connect(messageModel);
} catch (Exception e) {
log.info(e);
}
}
@MessageMapping("/publishJob")
public void publishJob(@Payload ConsoleRequestModel messageModel) {
log.info(messageModel.toString());
try {
mqttService.publishJob(messageModel);
} catch (Exception e) {
log.info(e);
}
}
@MessageMapping("/publishTopic")
public void publishTopic(@Payload ConsoleRequestModel messageModel) {
try {
mqttService.publishTopic(messageModel);
} catch (Exception e) {
log.info(e);
}
}
@MessageMapping("/subscribeTopic")
public void subscribeTopic(@Payload ConsoleRequestModel messageModel) {
try {
mqttService.subscribeTopic(messageModel);
} catch (Exception e) {
log.info(e);
}
}
@MessageMapping("/{clientId}")
public void sendMessage( @DestinationVariable String clientId,@Payload ConsoleRequestModel messageModel) {
log.info(clientId);
log.info(messageModel);
simpMessagingTemplate.convertAndSend("/sub/" + clientId , messageModel);
}
}
MessageMapping 이 WebSocket 용 Request Mapping 이라 생각하면 이해하기 쉽다.
simpMessagingTemplate.convertAndSend("/sub/" + clientId , messageModel); -> @SendTo("/sub/{clientId}") 라 보면된다 하지만 @SendTo("/sub/{clientId}") 안됨으로 저렇게 처리하였음.
4. test.html
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0">
<title>Spring Boot WebSocket Application</title>
<script src="/webjars/jquery/3.1.1-1/jquery.js"></script>
<script src="/webjars/sockjs-client/1.0.2/sockjs.min.js"></script>
<script src="/webjars/stomp-websocket/2.3.3/stomp.min.js"></script>
<script src="/statics/test.js"></script>
</head>
<body>
<div id="thing-page">
<div class="thing-page-container">
<h1 class="title"> 입력하세요</h1>
<div class="form-group">
<input type="text" id="certificatePem" placeholder="certificatePem" autocomplete="off" class="form-control" />
<input type="text" id="privateKeyPem" placeholder="privateKeyPem" autocomplete="off" class="form-control" />
<input type="text" id="thingName" placeholder="thingName" autocomplete="off" class="form-control" />
</div>
<div class="form-group">
<button class="connect-button">시작하기</button>
</div>
</div>
</div>
<div id="message-page" style="display: none">
<div class="chat-container">
<div class="chat-header">
<h2>Spring WebSocket Chat Demo</h2>
</div>
<div class="form-group">
<div class="input-group clearfix">
<button class="jobPublish">Job 확인 보내기</button>
</div>
<div class="input-group clearfix">
<input type="text" id="subscribeTopic" placeholder="topic" autocomplete="off" class="form-control"/>
<button class="subscribeTopic">Topic 구독하기</button>
</div>
<div class="input-group clearfix">
<input type="text" id="publishTopic" placeholder="topic" autocomplete="off" class="form-control"/>
<input type="text" id="payload" placeholder="payload" autocomplete="off" class="form-control"/>
<button class="publishTopic">Topic 보내기</button>
</div>
</div>
<div class="connecting">
연결중...
</div>
<ul id="messageArea">
</ul>
</div>
</div>
</body>
</html>
pem , privtekey, thingName 으로 접속하면 웹소켓이 연결되어 각 토픽을 호출하는부분을 만들어놨음.
5. test.js
let stompClient = null;
let thingName = null;
let privateKeyPem = null;
let certificatePem = null;
let payloadData = {
privateKeyPem : "",
certificatePem : "",
algorithm : "",
thingName : "",
topic : "",
message : "",
shadowState : "",
}
function connect(event) {
payloadData.thingName = thingName = $('#thingName').val();
payloadData.privateKeyPem = privateKeyPem = $('#privateKeyPem').val();
payloadData.certificatePem = certificatePem = $('#certificatePem').val();
$('#thing-page').hide();
$('#message-page').show();
let socket = new SockJS('/console/device');
stompClient = Stomp.over(socket);
stompClient.connect({}, onConnected, onError);
}
function onConnected() {
$('.connecting').append('<br>접속 성공!');
stompClient.subscribe('/sub/'+thingName, onMessageReceived);
stompClient.send("/pub/connect", {}, JSON.stringify(payloadData));
}
function onMessageReceived(payload) {
let message = JSON.parse(payload.body);
$('.connecting').append('<br>'+ message.topic +' : <code>'+ message.payload + '</code>');
}
function onError(error) {
$('#thing-page').show();
$('#message-page').hide();
$('.connecting').append('<br>Could not connect to WebSocket server. Please refresh this page to try again!');
stompClient = "";
}
function sendMessage(topic,message) {
if(thingName && stompClient) {
payloadData.topic = topic;
payloadData.message = message;
stompClient.send("/pub/"+thingName, {}, JSON.stringify(payloadData));
} else {
alert('연결이 끊어졌습니다.');
}
}
$(document).on("click", ".connect-button", function(e) {
e.preventDefault();
connect();
});
$(document).on("click", ".jobPublish", function(e) {
e.preventDefault()
stompClient.send("/pub/publishJob", {}, JSON.stringify(payloadData));
});
$(document).on("click", ".publishTopic", function(e) {
e.preventDefault()
if(thingName && stompClient) {
payloadData.topic = $('#publishTopic').val();
payloadData.message = $('#payload').val();
stompClient.send("/pub/publishJob", {}, JSON.stringify(payloadData));
} else {
alert('연결이 끊어졌습니다.');
}
});
$(document).on("click", ".subscribeTopic", function(e) {
e.preventDefault()
if(thingName && stompClient) {
payloadData.topic = $('#subscribeTopic').val();
stompClient.send("/pub/subscribeTopic", {}, JSON.stringify(payloadData));
} else {
alert('연결이 끊어졌습니다.');
}
});
버튼들에 대한 action 시 소켓으로 pub sub 하는 부분들 처리함.
'Tech' 카테고리의 다른 글
vi 에서 붙여넣기 했을때 탭 깨지는 현상 해결방법 (0) | 2021.01.11 |
---|---|
SSH 접속시 RSA 공유키 충돌 해결방법 (0) | 2020.12.03 |
ELB Websocket 설정 문제 (0) | 2020.12.03 |
Spring WebSocket (0) | 2020.12.03 |
Mac OS 에 기본 설정 하기 (0) | 2020.04.12 |