이전 포스팅에서는 WebSocket (SockJS, STOMP)을 사용하여 클라이언트와 서버 간에 양방향 실시간 통신을 구현하여, 파이썬 파일이 실행되는 과정을 클라이언트 뷰로 가져와서 프로그래스 바로 표시하는 예제를 구현하였다. 그러나 클라이언트 측에서 전달해야 하는 인자값이 없는 경우에는 양방향 통신이 필요하지 않을 수 있다.
따라서 이번에는 단방향 통신 중 하나인 SSE (Server-Sent-Events)를 사용하여 실시간 업데이트를 구현해보려고 한다.
+) 들어가기 전에 이번 포스팅은 테스트 삼아 간단히 구현한 것임을 알아주었으면 좋겠다.
SSE: 서버에서 클라이언트로 단방향으로 데이터를 전송하는 기술로, 클라이언트가 서버로부터 이벤트를 받아 실시간으로 업데이트를 수신할 수 있다.
SSE 는 다음과 같은 상황에서 많이 활용된다.
1. 실시간 업데이트: 주식 시장에서 주가 변동, 소셜 미디어에서 실시간으로 새로운 알림 표시 등
2. 이벤트 기반 알림: 새로운 채팅의 실시간 수신 알림 표시 등
3. 실시간 통계 및 모니터링: 실시간 로그 모니터링 등
서버에서는 클라이언트로부터 요청이 오면 연결이 끊어질 때까지 이벤트를 생성하고 해당 이벤트를 클라이언트에게 수신하게 된다. 다음은 이벤트를 생성하여 수신하는 내용의 코드이다.
(Service 까지 가지 않는 내용이므로 Conroller 안에서 모두 수행된다.)
@GetMapping(value = "/sse/{filename}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter sse(@PathVariable String filename) {
SseEmitter emitter = new SseEmitter();
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.execute(() -> {
try {
int exitValue = new ProcessExecutor().command("python", FILE_PATH+filename)
.redirectOutput(new LogOutputStream() {
@SneakyThrows
@Override
protected void processLine(String line) {
emitter.send(Map.of("process", line));
}
})
.execute()
.getExitValue();
emitter.send(Map.of("result", exitValue));
} catch (Exception e) {
emitter.completeWithError(e);
} finally {
emitter.complete();
sseEmitterMap.put(filename, null);
}
});
return emitter;
}
작업을 비동기적으로 실행하고 관리하기 위한 인터페이스
내부적으로 하나의 스레드만을 사용하여 작업을 처리한다. 생성된 단일 스레드는 작업 큐에서 순차적으로 작업을 처리하며, 순서가 보장된다.
이벤트를 생성하고 클라이언트에게 메시지를 전송하는 객체
emmiter.send(Object obj)
: 클라이언트에게 이벤트를 전달한다.emitter.complete()
: 클라이언트와 연결을 종료한다.클라이언트 측에서는 JavaScript의 EventSource 객체를 사용하여 SSE 이벤트를 수신하고 처리한다. SSE 이벤트 수신 및 처리를 위한 예제 코드를 통해 클라이언트 측 구현 방법을 설명해보도록 하겠다.
$(document).ready(()=>{
connectSSE();
})
function connectSSE(){
const eventSource = new EventSource("/sse/test.py")
eventSource.onopen = event =>{
console.log("SSE connected..")
}
eventSource.onmessage = event => {
let data = JSON.parse(event.data)
if(data.result !== undefined){
eventSource.close()
return
}
console.log(data)
}
}
onmessage
함수를 통해 확인할 수 있다. 전달 할 때, Object로 전달한 경우에도 문자열로 전달되기 때문에 추가적인 Json 파싱이 필요하다.eventSource.close()
, 안 닫아주면 서버에서 계속 전달되어 파일이 무한 재시작된다!)이전 포스팅에서 다뤘던 Websocket과 비교하면 코드도 비교적 짧고 추가되는 라이브러리가 없었다. 둘 중 어떤 것을 선택해야할지는 아직 고민이 된다. 또한 실제 배포 이후에 해당 코드가 어떤 문제를 일으킬지 몰라 추가적인 코드 작성이 요구된다.
다음 포스팅에서는 Python 파일를 직접 실행하지 않고, Python 서버와 통신하는 방법으로 구현해보도록 할 예정이다. 점점 파고들수록 대안이 너무 많아지고 있다..