- 이번에는 자신이 구독한 사용자가 새로운 글을 업로드할 경우 실시간으로 알림이 보내지도록 구현해보았습니다.
- 해당 기능은 서버 -> 클라이언트 방향으로만 이루어지는 단방향 통신만으로도 충분히 구현 가능했기 때문에 SSE(Server-Sent-Events)를 이용하였습니다.
📝 js
- 우선 클라이언트 쪽 구현 코드입니다.
function storyLoad() {
$.ajax({
url: `/api/image?page=${page}`,
dataType: "json"
}).done(resp => {
console.log(resp);
resp.data.content.forEach((image) => {
let item = getStoryItem(image);
$("#storyList").append(item);
});
//로그인과 동시에 이벤트 등록
notification();
}).fail(error => {
console.log(error);
});
}
//notification
function notification() {
let eventSource = new EventSource("http://localhost:8080/sub");
eventSource.addEventListener("notification", function(event) {
let message = event.data;
let notification_content = document.getElementById('notification-content');
let notification_container = document.getElementById('notification-container');
notification_content.textContent = message;
notification_container.classList.add('show');
setTimeout(() => {
notification_container.classList.remove('show');
}, 2000);
});
eventSource.addEventListener("error", function(event) {
eventSource.close();
});
}
- 해당 코드에서는 notification 함수를 정의하여 로그인과 동시에 해당 사용자에 대한 알림 이벤트가 등록되도록 하고 있습니다.
- SSE 통신을 위해서는 먼저 클라이언트에서 서버로의 연결이 필요하며 해당 연결 요청을 보내기 위해서 자바스크립트에서는 EventSource 객체를 제공하고 있습니다.
- 위 코드에서는 간단히 서버에서 메시지를 전달받아 해당 메시지를 사용자에게 알림으로 띄워주도록 구현하였습니다.
📝 Controller
- 다음으로 서버에는 위의 EventSource 객체를 통해 전달되는 요청을 받을 수 있도록 컨트롤러를 구현해주었습니다.
package com.cos.photogram.web;
...
@RequiredArgsConstructor
@Controller
public class NotificationController {
private final NotificationService notificationService;
public static Map<Long, SseEmitter> sseEmitters = new ConcurrentHashMap<>();
@GetMapping(value = "/sub", produces = "text/event-stream")
public SseEmitter subscribe(@AuthenticationPrincipal PrincipalDetails principalDetails,
@RequestHeader(value = "Last-Event-ID", required = false, defaultValue = "") String lastEventId) {
Long userId = principalDetails.getUser().getId();
SseEmitter sseEmitter = notificationService.subscribe(userId);
return sseEmitter;
}
}
- SSE 통신을 위해 MIME 타입을 text/event-stream으로 설정해주었습니다.
- 추가로 lastEventId는 클라이언트가 마지막에 수신한 데이터의 id 값을 의미하며, 이를 이용하여 유실된 데이터를 다시 보내줄 수 있습니다.
📝 Service
- 다음으로는 SSE 연결과 관련된 구체적이 로직을 서비스 단에 구현해주었습니다.
package com.cos.photogram.service;
...
@Service
@RequiredArgsConstructor
public class NotificationService {
public SseEmitter subscribe(Long userId) {
// 현재 클라이언트를 위한 SseEmitter 생성
SseEmitter sseEmitter = new SseEmitter(Long.MAX_VALUE);
try {
// 연결!!
sseEmitter.send(SseEmitter.event().name("connect"));
} catch (IOException e) {
e.printStackTrace();
}
// user의 pk값을 key값으로 해서 SseEmitter를 저장
NotificationController.sseEmitters.put(userId, sseEmitter);
sseEmitter.onCompletion(() -> NotificationController.sseEmitters.remove(userId));
sseEmitter.onTimeout(() -> NotificationController.sseEmitters.remove(userId));
sseEmitter.onError((e) -> NotificationController.sseEmitters.remove(userId));
return sseEmitter;
}
}
- 클라이언트의 SSE 연결 요청에 응답하기 위해서는 SseEmitter 객체를 생성하여 반환해주어야 합니다.
- 이때 유효 시간을 지정해줄 수 있으며 해당 시간만큼 SSE 연결이 유지되고, 시간이 지나면 자동으로 클라이언트에서 재연결 요청을 보내게 됩니다.
- 추가로 SseEmitter의 시간 초과 및 네트워크 오류를 포함한 모든 이유로 비동기 요청이 정상 동작할 수 없다면 저장해둔 SseEmitter를 삭제하도록 구현해주었습니다.
📝 ImageService
- 마지막으로 실제 실시간 알림 기능을 필요로 하는 구간에 알림을 보내기 위한 간단한 처리를 수행해주었습니다.
- 필자의 경우 다른 사용자가 새로운 이미지를 업로드 하는 경우에 알림이 보내지도록 구현하고자 했기 때문에 아래와 같이 업로드 로직 함수 안에 코드를 추가해주었습니다.
/* 이미지 업로드 */
@Transactional
public void upload(ImageUploadDto imageUploadDto, PrincipalDetails principalDetails) {
...
/* 알림 부분 */
List<Long> subsToList = subscribeRepository.findSubscribeTo(principalDetails.getUser().getId());
for (Long id : subsToList) {
SseEmitter sseEmitter = NotificationController.sseEmitters.get(id);
try {
sseEmitter.send(SseEmitter.event().name("notification").data("새로운 글을 업로드했습니다!"));
} catch (Exception e) {
NotificationController.sseEmitters.remove(id);
}
}
}
- 우리는 User의 PK 값과 연결된 SseEmiiter 저장소를 위에서 구현해놓았으며, 해당 저장소에 현재 새로운 이미지를 업로드 한 사용자를 구독하고 있는 사용자가 있다면 이벤트를 발행할 것입니다.
📝 Result
📝 Reference
- https://tecoble.techcourse.co.kr/post/2022-10-11-server-sent-events/
- https://velog.io/@dhk22/TIL-Day
'🚗 Backend Toy Project > Photogram' 카테고리의 다른 글
[Photogram] 연관 검색어 (0) | 2023.03.05 |
---|---|
[Photogram] 해시태그 기능 추가 (0) | 2023.01.03 |
[Photogram] 로그인 실패 처리 (0) | 2022.12.25 |
[Photogram] 검색 기능 구현 (0) | 2022.12.22 |
[Photogram] OAuth2 페이스북 로그인 (0) | 2022.07.19 |