Websocket๊ณผ ๊ฐ์ด ์๋ฒ์์ ํด๋ผ์ด์ธํธ๋ก ๋จ๋ฐฉํฅ ํต์ ์ ๊ฐ๋ฅํ๊ฒ ํ๋ ์๋ฃจ์
์ค ํ๋์ด๋ค.
๊ฐ์ฅ ํฐ ์ฐจ์ด์ ์ผ๋ก๋ WebSocket์ ๊ฒฝ์ฐ ์์ ํ ์๋ฐฉํฅ ํต์ ์ ์ง์ํ๋ค. ํด๋ผ์ด์ธํธ์์ ์๋ฒ๋ก, ์๋ฒ์์ ํด๋ผ์ด์ธํธ๋ก ํต์ ์ด ๊ฐ๋ฅํ๋ค. ๋ฐ๋ฉด SSE์ ๊ฒฝ์ฐ ์๋ฒ์์ ํด๋ผ์ด์ธํธ๋ก ๋ณด๋ด๋ ๊ฒ๋ง ๊ฐ๋ฅํ๋ค.
์ถ๊ฐ๋ก Websocket๋ณด๋ค๋ ๊ฐ๋ณ๊ณ Springboot ๊ธฐ์ค ๊ตฌํ์ด ์ฝ๋ค๋ ์ฅ์ ์ ๊ฐ๊ณ ์๋ค.
์ด๋ฒ ํ๋ก์ ํธ์ ์ค์๊ฐ ์๋ฆผ๊ธฐ๋ฅ์ ์ ์ฉํด๋ณด๊ธฐ ์ํด ๊ฐ๋ณ๊ฒ ๋ค๋ค๋ดค๋ค.
Springboot ์ ๊ฒฝ์ฐ ๋ฐ๋ก ์์กด์ฑ ์ถ๊ฐ๋ ํ์์๋ค.
์ผ๋จ ๊ตฌํ์ ์์์ ์๊ตฌ์ฌํญ์ ๋ง๋ก ํ์ด๋ดค๋ค. ( ๋ง๋ก ํ๋๊น ์ข ์ด์ํ๊ธดํ๋ค..)
ํด๋ผ์ด์ธํธ ์ธก ๋ณด๋ค๋ ์๋ฒ ์ธก ์ฒ๋ฆฌ๊ฐ ์ชผ๋ ๋ณต์กํ๋ ํด๋ผ์ด์ธํธ ์ธก ์ฒ๋ฆฌ๋ถํฐ ๋ณด์.
ํด๋ผ์ด์ธํธ๋ ํ ํฐ์ ๋ณด์ ํ๊ณ ์์ ๋๋ง ์๋ฒ๋ฅผ ๊ตฌ๋
ํ๋ฉด ๋๋ค.
let subscribeUrl = "http://localhost:8080/sub";
$(document).ready(function() {
if (sessionStorage.getItem("mytoken") != null) {
let token = sessionStorage.getItem("mytoken");
let eventSource = new EventSource(subscribeUrl + "?token=" + token);
eventSource.addEventListener("addComment", function(event) {
let message = event.data;
alert(message);
})
eventSource.addEventListener("error", function(event) {
eventSource.close()
})
}
})
์์ง ์๊ฐํ์ง ์์์ง๋ง ์๋ฒ์ธก์์ ๊ตฌ๋
(์ฐ๊ฒฐ)์ ์ํด ์ด์ด๋์ ์๋ํฌ์ธํธ๋ /sub
์ด๋ค.
์ด ์๋ํฌ์ธํธ์ ์ฟผ๋ฆฌ์คํธ๋ง์ผ๋ก ํ ํฐ๊ฐ๊ณผ ํจ๊ป ์์ฒญํ๊ณ ์๋ค.
์ฐ๊ฒฐ์ ์ด๋ ๊ฒ ํ๋ฉด ๋์ด๋ค. (์ ๊ฐ๋จ)
๋ค์์ผ๋ก ์๋ฒ์์ ๋ฐํน๋ ์ด๋ฒคํธ์ ๋ํ ์ฒ๋ฆฌ๋ฅผ ํ๋ค.
addComment
๋ผ๋ ์ด๋ฆ์ผ๋ก ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ค๋ฉด ํจ๊ป ์ ๋ฌ๋ data๋ฅผ alertํ๋ ๊ฐ๋จํ ์ฒ๋ฆฌ๋ฅผ ํ๊ณ ์๋ค.
์ด์ ์๋ฒ์ธก ์ฒ๋ฆฌ์ด๋ค.
@RequiredArgsConstructor
@Slf4j
@RestController
public class SseController {
public static Map<Long, SseEmitter> sseEmitters = new ConcurrentHashMap<>();
private final JwtUtils jwtUtils;
@CrossOrigin
@GetMapping(value = "/sub", consumes = MediaType.ALL_VALUE)
public SseEmitter subscribe(@RequestParam String token) {
// ํ ํฐ์์ user์ pk๊ฐ ํ์ฑ
Long userId = jwtUtils.getUserIdFromToken(token);
// ํ์ฌ ํด๋ผ์ด์ธํธ๋ฅผ ์ํ SseEmitter ์์ฑ
SseEmitter sseEmitter = new SseEmitter(Long.MAX_VALUE);
try {
// ์ฐ๊ฒฐ!!
sseEmitter.send(SseEmitter.event().name("connect"));
} catch (IOException e) {
e.printStackTrace();
}
// user์ pk๊ฐ์ key๊ฐ์ผ๋ก ํด์ SseEmitter๋ฅผ ์ ์ฅ
sseEmitters.put(userId, sseEmitter);
sseEmitter.onCompletion(() -> sseEmitters.remove(userId));
sseEmitter.onTimeout(() -> sseEmitters.remove(userId));
sseEmitter.onError((e) -> sseEmitters.remove(userId));
return sseEmitter;
}
}
๊ฐ์ฅ ์ค์ํ ๋ถ๋ถ์ ์ ๋ฌ๋ฐ์ ํ ํฐ์์ user์ pk๊ฐ์ ํ์ฑํ๊ณ ํ์ฑ๋ pk๊ฐ์ ํค ๊ฐ์ผ๋ก ํ์ฌ SseEmitter
๋ฅผ ์ ์ฅํ๋ ๋ถ๋ถ์ด๋ค.
์ด๋ก์จ ์ฌ์ฉ์๋ณ๋ก SseEmitter
๋ฅผ ์๋ณํ์ฌ ์ด๋ฒคํธ๋ฅผ ๋ณด๋ผ ์ ์๊ฒ ๋์๋ค.
๋ง์ง๋ง์ผ๋ก ์๋ฆผ(์ด๋ฒคํธ)์ ๋ณด๋ด๊ธฐ ์ํ ์ฒ๋ฆฌ๊ฐ ๋จ์๋ค.
@RequiredArgsConstructor
@Service
public class NotificationService {
private final MemoRepository memoRepository;
public void notifyAddCommentEvent(Long memoId) {
// ๋๊ธ์ ๋ํ ์ฒ๋ฆฌ ํ ํด๋น ๋๊ธ์ด ๋ฌ๋ฆฐ ๊ฒ์๊ธ์ pk๊ฐ์ผ๋ก ๊ฒ์๊ธ์ ์กฐํ
Memo memo = memoRepository.findById(memoId).orElseThrow(
() -> new IllegalArgumentException("์ฐพ์ ์ ์๋ ๋ฉ๋ชจ์
๋๋ค.")
);
Long userId = memo.getUser().getId();
if (sseEmitters.containsKey(userId)) {
SseEmitter sseEmitter = sseEmitters.get(userId);
try {
sseEmitter.send(SseEmitter.event().name("addComment").data("๋๊ธ์ด ๋ฌ๋ ธ์ต๋๋ค!!!!!"));
} catch (Exception e) {
sseEmitters.remove(userId);
}
}
}
}
์ ๋ฉ์๋๋ ๋๊ธ์ ๋ํ ์ฒ๋ฆฌ๋ฅผ ๋ง์น ํ์ ํธ์ถ๋๋ค.
ํ๋ผ๋ฏธํฐ๋ก ๋ฐ์ ๊ฒ์๊ธ์ ์ฃผ์ธ(user)์ pk๋ฅผ ์กฐํํ๋ค.
์ฐ๋ฆฌ๋ user์ pk์ ์ฐ๊ฒฐ๋ SseEmitter ์ ์ฅ์๋ฅผ ๊ฐ๊ณ ์๋ค.
SseEmitter ์ ์ฅ์์ ํ์ฌ user์ pk๊ฐ ์กด์ฌํ๋ค๋ฉด (๊ฒ์๊ธ์ ์ฃผ์ธ์ด ํ์ฌ ์๋ฒ์ ์ฐ๊ฒฐ๋์ด ์๋ ์ํ๋ผ๋ฉด) ์ด๋ฒคํธ๋ฅผ ๋ฐํํ๋ฉด ๋๋ค.
์๋ฆผ์ ์ํ ๋ฉ์๋๋ฅผ ํธ์ถํ๋ ๋ถ๋ถ
@PostMapping("/memo/{id}/comment")
public ResponseEntity addComment(
@AuthenticationPrincipal UserDetailsImpl userDetails,
@PathVariable Long id,
@RequestBody CommentDto commentDto) {
Memo memo = memoService.addComment(id, userDetails.getUser(), commentDto);
// ์๋ฆผ ์ด๋ฒคํธ ๋ฐํ ๋ฉ์๋ ํธ์ถ
notificationService.notifyAddCommentEvent(memo);
return ResponseEntity.ok("ok");
}
๊ตฌํ ๋~~
์๋ ํ์ธ์. ๊ธ ๋ณด๋ฉด์ ๊ตฌํ์ ์ฑ๊ณตํ์ต๋๋ค.
๊ทธ๋ฐ๋ฐ ์ง๋ฌธ์ด ์์ต๋๋ค.
ํด๋ผ์ด์ธํธ์ ๋ชจ๋ ๊ฒฝ๋ก์์ ์๋์ ๋ฐ๊ณ ์ถ์ผ๋ฉด ์ด๋ป๊ฒ ํด์ผํ๋์?
ํ์ฌ ์ฝ๋๋ก๋ eventSource.addEventListener๋ฅผ ํด์ค html์ ์์น๊ฐ ์๋ ๋ค๋ฅธ ๊ฒฝ๋ก์์๋ ์๋ฆผ์ ๋ณด๋ด๋ ์๋ฆผ์ด ๊ฐ์ง ์์ต๋๋ค. ์ด๋ค์์ผ๋ก ํด์ผํ ๊น์. ๋ชจ๋ htmlํ์ผ์์ /sub๋ฅผ ํธ์ถํ๊ณ ์ด๋ฒคํธ๋ฅผ ๊ธฐ๋ค๋ ค์ผ ํ๋์?
Get "/sub" ํธ์ถ ์๊ธฐ
1๋ฒ ์ง๋ฌธ๊ณผ ๋น์ทํ ์ง๋ฌธ์ผ์ ์๋๋ฐ ํด๋น url์์ฒญ์ ํด์ผ sseEmitters (Map)์ ๋ฑ๋ก๋๋๋ฐ ๊ทธ๋ฌ๋ฉด ๋ก๊ทธ์ธ์ด ์ฑ๊ณตํ ์์ ์ ๋ฑ๋ก์ ํด์ผํ ๊น์?