Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- Sqoop
- Android
- hadoop
- GIT
- table
- tomcat
- mapreduce
- SQL
- mybatis
- IntelliJ
- Spring
- JavaScript
- R
- 공정능력
- plugin
- Express
- vaadin
- Eclipse
- Python
- MSSQL
- es6
- react
- SPC
- window
- xPlatform
- Java
- SSL
- NPM
- 보조정렬
- Kotlin
Archives
- Today
- Total
DBILITY
EventSource + Spring Server Sent Event 본문
반응형
HTML5 Websocket은 양방향, Server Sent Event는 단방향(Server -> Client)을 지원한다.
완성된 코드로 볼 수 없으나 동작함.
web.xml의 filter와 servlet(3.0이상)설정에 async-supported 추가 필요함
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0">
<display-name>ws</display-name>
<filter>
<filter-name>encodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<async-supported>true</async-supported>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>encodingFilter</filter-name>
<url-pattern>/*</url-pattern>
<!--<dispatcher>REQUEST</dispatcher>
<dispatcher>ASYNC</dispatcher>-->
</filter-mapping>
<!--
- Location of the XML file that defines the root application context.
- Applied by ContextLoaderListener.
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/context-*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--
- Servlet that dispatches request to registered handlers (Controller implementations).
-->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:config/dispatcher-servlet.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
Service와 Controller Class
@EventListner는 spring 4.2부터 지원됨
@RequestBody에 Map사용시 RequestMappingHandlerAdapter MappingJackson2HttpMessageConverter설정 필요
public class MessageEvent {
private String message;
public MessageEvent(String message) {
this.message = message;
}
public String getMessage() {
return this.message;
}
}
public interface SseService {
void add(SseEmitter emitter) throws Exception;
void remove(SseEmitter emitter) throws Exception;
void publishMessage(MessageEvent event) throws Exception;
long count() throws Exception;
}
@Service("sseService")
public class SseServiceImpl implements SseService {
private static final Logger logger = Logger.getLogger(SseServiceImpl.class);
private static CopyOnWriteArrayList<SseEmitter> subscribers = new CopyOnWriteArrayList<>();
@Override
public void add(final SseEmitter emitter) throws Exception {
emitter.onCompletion(new Runnable() {
@Override
public void run() {
synchronized (subscribers) {
subscribers.remove(emitter);
}
}
});
emitter.onTimeout(new Runnable() {
@Override
public void run() {
synchronized (subscribers){
subscribers.remove(emitter);
}
}
});
subscribers.add(emitter);
}
@Override
public void remove(SseEmitter emitter) throws Exception {
subscribers.remove(emitter);
}
@Async
@EventListener({MessageEvent.class})
@Override
public void publishMessage(MessageEvent event) throws Exception {
List<SseEmitter> deadSubscribers = new ArrayList<>();
for (SseEmitter subscriber : subscribers) {
try {
subscriber.send(event.getMessage(), MediaType.TEXT_EVENT_STREAM);
subscriber.complete();
} catch (Exception e) {
deadSubscribers.add(subscriber);
throw e;
}
}
subscribers.removeAll(deadSubscribers);
}
@Override
public long count() throws Exception {
return this.subscribers.size();
}
}
@Controller
public class DefaultController {
private static final Logger logger = LoggerFactory.getLogger(DefaultController.class);
@Resource(name = "sseService")
private SseService sseService;
private ApplicationEventPublisher eventPublisher;
@Autowired(required = false)
public void setEventPublisher(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
@RequestMapping(value = "/eventstream", method = RequestMethod.GET, produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter eventSourceConnect(@RequestParam("key") String key, HttpServletRequest request) throws Exception {
logger.info("1 ---------------> eventstream key {}", key);
SseEmitter emitter = new SseEmitter(10000L);
emitter.event().reconnectTime(10000L).id(key);
sseService.add(emitter);
logger.info("2 ---------------> add Emitter after {}", sseService.count());
return emitter;
}
@RequestMapping(value = "/sendMessage", method = RequestMethod.POST)
@ResponseBody
public Map<String,Object> sendMessage(@RequestBody Map<String, Object> paramMap) {
logger.info("3 ---------------> sendMessage {}", paramMap.get("message"));
int result = 1;
String message = "";
Map<String, Object> rtMap = new HashMap<>();
try {
if(eventPublisher!=null){
Gson gson = new Gson();
JsonObject obj = new JsonObject();
obj.addProperty("message",paramMap.get("message").toString());
eventPublisher.publishEvent(new MessageEvent(gson.toJson(obj)));
}
} catch (Exception e) {
result = -1;
message = e.getMessage();
rtMap.put("message", message);
}
rtMap.put("result", result);
return rtMap;
}
}
EventSource javascript + html
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!doctype html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<h1>EventSource!</h1>
<div class="container">
<div><input type="text" id="sendMessage" />
<button id="btnSend">메시지전송</button></div>
<ul class="msgList">
</ul>
</div>
<script>
let eventSource;
function log() {
for (let x of arguments) {
console.log(new Date().toLocaleTimeString() + " ---> ", x);
}
}
/**
* 어디선가 보고 베낌
* @returns {string}
*/
var fnGenerateUUID = function () {
let d = new Date().getTime();
let uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = (d + Math.random() * 16) % 16 | 0;
d = Math.floor(d / 16);
return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
return uuid;
};
window.addEventListener("load", e => {
eventSource = new EventSource("/eventstream?key=" + fnGenerateUUID(), {withCredentials: true});
try {
eventSource.addEventListener("message", e => {
let data = JSON.parse(e.data);
log(data);
let list = document.getElementsByClassName("msgList")[0];
let ele = document.createElement("li");
ele.innerText = data.message;
list.append(ele);
}, false);
eventSource.addEventListener("open", e => {
log("Connection : " + ((e.returnValue == true) ? "yes" : "no"));
}, false);
eventSource.addEventListener("error", e => {
if (e.readyState == EventSource.CLOSED) {
log("Disconnected");
e.currentTarget.close();
}
}, false);
} catch (e) {
log(e);
}
let btnSubmit = document.getElementById("btnSend");
btnSubmit.addEventListener("click", ev => {
let message = document.getElementById("sendMessage").value;
if (message.length == 0) {
e.preventDefault();
return;
}
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function () {
if (this.readyState == 4 && this.status == 200) {
log("xhr.onreadystatechange :" + this.responseText);
var msg = this.responseText;
}
};
xhr.onerror = function (ev) {
log("xhr.onerror :" + this.responseText);
};
xhr.open("POST", "/sendMessage");
xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8");
xhr.send(JSON.stringify({"message": message}));
});
});
window.addEventListener("unload",function () {
if(eventSource!=null){
eventSource.close();
}
});
</script>
</body>
</html>
실행결과
반응형
'front-end & ui > javascript' 카테고리의 다른 글
div move + rotate + resize test (0) | 2021.05.20 |
---|---|
div rotate test (0) | 2021.05.18 |
div drag test (0) | 2021.05.14 |
pure javascript file upload 테스트 (0) | 2021.05.12 |
ajax download (0) | 2019.06.03 |
Comments