🔀

SSE (Server-Sent-Event)

Updated
2024/09/11 15:18
Category
Front-end
Tags
SSE
JavaScript
Kotlin
2 more properties

SSE(Server-Sent-Event)란?

클라이언트에게 서버로부터 실시간 업데이트를 허용하는 웹 기술.
text/event-stream 타입으로 서버에서 클라이언트에 단방향 통신을 할 수 있습니다.

필요 사항

클라이언트에서 매번 요청없이 한번의 연결을 통해 서버와 통신해야하는 경우
WebSocket 방식보다 라이트한 방식을 원하는 경우
이벤트 타입을통해 한번의 연결로 다양한 분기를 수행해야하는 경우
지속적인 연결을 유지해야야는 경우

Client (JS)

EventSource 객체를통해 서버와 통신이 가능합니다. (단방향)
const eventSource = new EventSource(url)
JavaScript
복사
선언과 동시에 해당 URL로 통신 시도
실시간 데이터 스트리밍 방식 (text/event-stream)
지속적인 연결 상태 유지
호출을 통해 닫힐 때까지 연결함.
서버에서 보내주는 이벤트를 통해 action을 분리할 수 있다.
server에서 eventName을 입력하면 해당 이벤트로 받아야 합니다.
eventSource.addEventListener('eventname', (event) => { //... })
JavaScript
복사
server에서 eventName을 비어서 보낸다면 “message” 이벤트로 받으면 됩니다.
eventSource.addEventListener('message', (event) => { //... }) // or eventSource.onmessage = event => { //... }
JavaScript
복사

응답값

lastEventId 값: 이것을 통해 통신이 끊겨있는동안 받지 못한 이벤트를 받을 수 있음.
type : 서버에서 전달해주는 이벤트 이름 (default: “message”)
data : 서버에서 전달해주는 값

요청값

SSE는 서버에서 클라이언트로 일반적인 단방향 통신을 지원합니다. 따라서 도중에 클라이언트가 서버에게 요청하는 방법은 없습니다.
예외방법
최초 통신 시도시 : query-string
new EventSource('sse-stream?param=Helloworld')
JavaScript
복사
서버에서 End-point를 제공하여 쿠키나 검증 코드 실행하고 연결

Server (kotlin)

SseEmitter 객체를 통해 emitter 인스턴스를 생성하고 리턴하면 연결 끝
private val emitters: ConcurrentHashMap<String, SseEmitter> = ConcurrentHashMap() @GetMapping("/stream-sse", produces = [MediaType.TEXT_EVENT_STREAM_VALUE]) fun streamEvents(@RequestParam(value = "user", required = false, defaultValue = "empty") user: String): SseEmitter { val emitter = SseEmitter(60_000L) emitters[user] = emitter emitter.send( SseEmitter.event().name("message").data(user) ) emitter.onCompletion { emitters.remove(user) } emitter.onTimeout { emitters.remove(user) } return emitter }
JavaScript
복사
concurrentHansMap 방식 또는 CopyOnWriteArrayList 방식중에서 선택
쓰레드로 접근시 값의 충돌이나 점유상태를 안전하게하기 위함
client로 발신
private val scheduler: TaskScheduler = ConcurrentTaskScheduler(Executors.newSingleThreadScheduledExecutor()) @PostConstruct fun startPeriodicTimeEvents() { scheduler.scheduleWithFixedDelay({ // 모든 사용자 중 랜덤하게 한 명을 선택 if (emitters.isNotEmpty()) { val users = emitters.keys.toList() val selectedUser = users[Random.nextInt(users.size)] // 선택된 사용자에게만 메시지 전송 emitters[selectedUser]?.let { emitter -> try { val eventId = System.currentTimeMillis().toString() val data = jacksonObjectMapper().writeValueAsString(mapOf("time" to eventId)) emitter.send(SseEmitter.event().id(eventId).name("message").data(data, MediaType.APPLICATION_JSON)) } catch (e: IOException) { println("Error sending message: $e") // 로그 출력 emitters.remove(selectedUser) } } } }, 2000) }
JavaScript
복사

예제 코드

client (html, css, JS)
server (spring boot, java 17, gradle, jar, kotlin)
참고