Post

Spring 에서 Server-Sent Events(SSE)로 브라우저 알림 구현하기

ServerSideEvent 로 사용자 브라우저에 알림 주기


채팅 기능이 생기면서 실시간으로 사용자에게 알림을 주고 싶은 일이 생겼다. 브라우저 알림을 주려고 하는데 이는 Server Side Event 로 구현이 가능했다.

ServerSideEvent 가 뭐지 ?


Server Side Event 는 서버에서 클라이언트엑 통신을 하는 방법이라고 할 수 있다. Websocket 은 서버와 클라이언트 사이에 왔다갔다 할 수 있는 양방향이었다면 SSE는 단방향이다! 단방향인 만큼 전력 소비량도 적다. 그리고 http 프로토콜을 사용해서 에러 처리에도 굉장히 용이하다.

Server Side Event 를 보내보자!


우선 client 에서 server 에 요청을 보내서 나만의 sseEmitter 를 저장하도록 해야한다. 따라서 SseController.java 를 사용자 id 기반으로 1개씩 저장 할 수 있도록 만들어 주었다.

1
2
3
4
5
6
7
8
9
// SseController.java
@GetMapping(value="/connect/{id}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public ResponseEntity<SseEmitter> (HttpServletRequest res, @PathVariable String id) {
	SseEmitter emitter = sseEmitter.getEmmiterById(id);
	if (emitter != null) return ResponseEntity.ok(emitter);
	emitter = new SseEmitter(60 * 60 * 1000L);
	sseEmitters.add(id, emitter);
	return ResponseEntity.ok(emitter);
}

그리고 Client 단에서 아래와 같이 요청하면 sseEmitter 를 받을 수 있다.

1
2
//js
const sse = new EventSource("/connect/12345")

SseEmitter 에 대한 class는 따로 만들어 주었다. 아래처럼 만들어 Map 에 저장하고 user 의 id 가 들어오면 저장하고 있던 sseEmitter 를 return 할 수 있도록 한다.

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
//SseEmitters.java
public class SseEmitters {
	private Map<String, SseEmitter> CLIENTS;

	public void add (String id, SseEmitter sseEmitter){
		CLIENTS.put(id, sseEmitter);
		sseEmitter.onCompletion(() -> {
			if(CLIENTS.containsKey(id)) {
				SseEmitter sseEmitter = CLITENTS.remove(id); 
				sseEmitter = null;
			} // sseEmitter 가 없어 질땐 삭제한다. 11월 27일 Mem Leak 이 해결된 코드이다!
		});
		sseEmitter.onTimeout(() -> {
			sseEmitter.complete();
		});
		return sseEmitter;
	}

	public getEmitterById (String id) {
		if(CLIENTS.containsKey(id)) {
			return CLIENTS.get(id);
		}
		return null;
	}
	public removeEmitterById(String id) {
		if(CLIENTS.containsKey(id)) {
			SseEmitter sseEmitter = CLIENTS.remove(id);
			sseEmitter = null;
		} // 11월 27일 Mem Leak 이 해결된 코드이다!
	}
}

브라우저 알림을 보내보자 !


이제 알림을 보내는 부분을 만들어보겠다!

1
2
3
4
5
6
public void sendBrowserAlarm(String participant, String body, String url, EventType type) {
	Map<String, String> dataBody = new HashMap<>();
	dataBody.put("body", body);
	dataBody.put("url", url);
	sseEmitters.publish(dataBody, participant);
}
1
2
3
4
5
6
7
8
9
10
//SseEmitters.java
public void publish(Map<String, String> dataBody, String targetId) {
	SseEmitter targetEmitter = CLIENT.get(id);
	if (targetEmitter == null) return;
	try {
		targetEmitter.send(SseEmitter.event().name("notify").data(dataBody));
	} catch (Exception e) {
		CLIENT.remove(id);
	}
}

이렇게 하면 Client에게 notify 라는 Event 를 줄수가 있다. 그렇다면 Client 에서는 어떻게 받으면 될까 ?? 아까 client 단에서 받은 SseEmitter 에 event listener 를 달아주면 된다!

1
2
3
4
5
sse.addEventListener("notify", e => {
	const {data: strDataBody} = e;
	let dataBody = JSON.parse(strDataBody);
	//Notification 코드 작성
})

Spring 에서 워낙 잘 만들어 놓은 까닭에 Sse Emitter 로 손쉽게 추가하여 Client 에 정보를 전달 할 수가 있었다!

This post is licensed under CC BY 4.0 by the author.

© 병욱. Some rights reserved.