본문으로 바로가기
반응형

프로젝트를 진행하다가 서버에서 주기적으로 발행하는 데이터를 클라이언트에서 실시간으로 받아 표시해주어야하는 기능을 구현할 일이 생겼다.

 

제일 먼저 생각났던건 웹소켓이었는데 물론 웹소켓으로도 구현은 가능하지만 웹소켓에서 지원하는 양방향 통신까지는 필요가 없고
서버 -> 클라이언트 방향으로의 단방향만 지원하면 돼서 다른 기술을 찾아보다가 SSE를 알게 되었다.

 

SSE는 Server Send Events의 약자로 HTML5부터 사용하능한 표준 스펙이다. 

말 그대로 서버에서 주기적으로 발행하는 이벤트를 클라이언트(브라우저)에서 구독할 수 있는 기술로 접속연결만 HTTP로 하고 자체 프로토콜로 통신하는 웹소켓과 달리 전체 과정을 HTTP로 수행한다.

 

사용하는 방법은 간단하다.

 

먼저 원리를 알기 위해 SSE 스펙에 맞춰 이벤트를 발행하는 서버 코드를 작성해보자. (스프링에서 쉽게 적용하는 부분은 글 뒷 부분에 작성) 

@Controller
@RequestMapping("/sse")
public class SseController {

    @GetMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public void publish(HttpServletResponse response) throws Exception {    
	    response.setCharacterEncoding("UTF-8");
        
        PrintWriter writer = response.getWriter(); 
        for(int i = 1; i <= 10; i++) { 
        	writer.write("data: { \"message\" : \"number : "+ i + "\" }\n\n"); 
            try { 
            	Thread.sleep(1000); 
            } catch (InterruptedException e) { 
            	e.printStackTrace(); 
            } 
        } 
        writer.close();
    }

}

 

먼저 Content-Type을 "text/event-stream"로 지정하고, 응답하는 데이터형식은 "data:실제응답할데이터\n\n"와 같이 맞춰 응답하면 된다. 그러면 보통 모든 작업이 끝난 후 response를 한번에 보내는것과 달리 데이터형식에 맞는 데이터가 write될 때 마다 해당 데이터가 클라이언트로 전송된다.

 

클라이언트 코드는 간단하다.  

const eventSource = new EventSource("SSE API URL");
eventSource.onmessage = event => {
    console.log(event.data);
};

eventSource.onerror = error => {
    eventSource.close();
};

EventSource라는 SSE를 쉽게 다룰수 있는 클래스를 활용하면 정말 간단하게 구독 처리를 할 수 있다.

 

그리고 스프링 프레임워크에서도 마찬가지로 SSE를 쉽게 적용할 수 있는 구현체를 제공한다.

@Controller
public SseController {

    private TaskExecutor = Executors.newSingleThreadExecutor();

    @GetMapping
    public SseEmitter greeting(@PathVariable final int count) {
        final SseEmitter emitter = new SseEmitter();
        taskExecutor.execute(() -> {
            try {
                for (int i = 0; i < 10; i++) {
                    Thread.sleep(1000);
                    emitter.send("test data " + i);
                }
                emitter.complete();
            } catch (Exception e) {
                emitter.completeWithError(e);
            }
        });
        return emitter;
    }
}

이 예시에서는 이벤트 발행을 비동기로 처리하기 위해 별도 스레드를 통해 처리했다.

중요한건 "SseEmitter"라는 클래스를 제공하며 send 메소드로 데이터를 전송하고 전송이 끝나면 complete 메소드를 호출해주면 끝이다. 위 예시처럼 직접 Content-Type을 지정해주거나 데이터포맷을 맞출 필요 없이 편하게 사용할 수 있다.

반응형