Server-Sent Event
웹 애플리케이션이 단방향 이벤트 스트림을 처리하고 서버가 데이터를 보낼 때마다 업데이트를 받을 수 있게 하는 HTTP 표준
소켓 통신(전이중 모드)과 달리 반이중 모드 → 단방향 통신
- 클라이언트에서 서버로 이벤트를 보낼 순 없다.
클라이언트
서버에서 보낸 이벤트를 수신하는 법은 간단하다.
- EventSource 인스턴스 만들기
- message Event 수신 → 이벤트에 대한 핸들러 연결
- 정의된 필드가 있다면, 지정된 이름의 이벤트로 수신된다.
EventSource 인스턴스 만들기
const evtSource = new EventSource("ssedemo.php");
→ 이건 같은 origin 일 경우
const evtSource = new EventSource("//api.example.com/ssedemo.php", {
withCredentials: true,
});
→ 다른 origin 에서 받는 거라면 url 과 옵션을 설정해주자 !
message 이벤트 수신
evtSource.onmessage = (event) => {
const newElement = document.createElement("li");
const eventList = document.getElementById("list");
newElement.textContent = `message: ${event.data}`;
eventList.appendChild(newElement);
};
→ 이게 기본이지만 보통 어떤 이벤트인지 명시할테니.. 아래 코드를 확인하는 것이 더 정확할듯.
evtSource.addEventListener("ping", (event) => {
const newElement = document.createElement("li");
const eventList = document.getElementById("list");
const time = JSON.parse(event.data).time;
newElement.textContent = `ping at ${time}`;
eventList.appendChild(newElement);
});
→ “ping” 이벤트를 받는 코드
- 이렇게 이벤트로 받은 값을 가져와 클라이언트에서 사용하면 됩니다 ^_^/
주의 사항
HTTP/2 를 사용하지 않는다면 한 브라우저에서는 6개의 이벤트까지만 연결될 수 있는듯.
그렇다면 서버에서는 어떻게 이벤트를 보낼까용?
서버에서 이벤트 보내기
- MIME type 을 이용해서 응답해야한다. (Content-tpye: text/event-stream)
- 각 알림은 텍스트 블록으로 전송된다.
event-stream 형식
- UTF-8 을 사용하여 인코딩하는 간단한 텍스트 데이터 스트림.
- 한 쌍의 줄바꿈 문자로 구분되며, 줄 앞에 콜론 : 을 붙이면 주석이라 표시한다.
- 주석을 이용해서 주기적으로 주석으로 설명을 보내면 연결 시간 초과를 방지할 수 있다!
필드
event → 이벤트 이름.
- 이 이름을 받는 리스너로 해당 요청이 전달 됨.
data → 데이터.
- 연속된 줄을 받으면 연결해줌.
id → EventSource 개체의 마지막 이벤트 id 값
retry → 재연결 시간.
- 서버와 연결이 끊어지면 브라우저가 다시 연결을 시도하기 전에 retry 시간까지 기다림.
예시 : 그냥 문자열 데이터만 보내는 이벤트
: this is a test stream # 주석
data: some text # 한 줄
data: another message # 총 두 줄 짜리 묶음 데이터
data: with two lines
예시 : json 데이터 보내는 이벤트
event: userconnect
data: {"username": "bobby", "time": "02:33:48"}
event: usermessage
data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}
event: userdisconnect
data: {"username": "bobby", "time": "02:34:23"}
event: usermessage
data: {"username": "sean", "time": "02:34:36", "text": "Bye, bobby."}
- json 으로는 형식을 이렇게 보낸다.
예시 : 혼합 이벤트
event: userconnect
data: {"username": "bobby", "time": "02:33:48"}
data: Here's a system message of some kind that will get used
data: to accomplish some task.
event: usermessage
data: {"username": "bobby", "time": "02:34:11", "text": "Hi everyone."}
- 요렇게 혼합해서 보낼 수도 있다.
Spring 에서는 어떻게 씁니까 그럼?
dependencies {
// dependency 추가
implementation 'org.springframework.boot:spring-boot-starter-webflux:3.2.2'
}
String 으로
@GetMapping(value = "/stream-flux", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamFlux() {
// 서버에 초 단위로 현재 시간을 보내는 이벤트
return Flux.interval(Duration.ofSeconds(1))
.map(sequence -> "Flux Event : " + LocalTime.now());
}
- 간단하게 Flux 객체에 문자열을 담아 서버에 초 단위로 시간을 보내는 이벤트를 보낸다.
- 문자열로 보내는 것.
- 이 방법을 사용할 경우 MediaType 명시가 필요. (불ㅡ편)
ServerSentEvent 객체로
@GetMapping("/stream-sse")
public Flux<ServerSentEvent<User>> eventEvents() {
User user = User.builder()
.name("holiday")
.username("holiday_k")
.age(28)
.build();
return Flux.interval(Duration.ofSeconds(1))
.map(sequence -> ServerSentEvent.<User>builder()
.id(String.valueOf(sequence))
.event("user-event")
.data(getUser())
.build());
}
- ServerSentMessage 객체에 원하는 class type 으로 담아 보낼 수 있다.
- 1 초마다 해당 user 를 user-event 라는 이름으로 이벤트를 보낸다.
- 실제 sse 전송 시나리오를 그대로 사용 가능하고, text/event-stream 데이터 유형 선언을 하지 않아도 된다.
구독
// SSE Subscribe
// inStatus 문을 추가하지 않으면 4xx , 5xx 응답에서 WebClientResponseException 발생
public void consumeServerSentEvent() {
WebClient webClient = WebClient.create("<http://localhost:8000/sse-server>");
// body Type
ParameterizedTypeReference<ServerSentEvent<String>> type =
new ParameterizedTypeReference<>() {};
Flux<ServerSentEvent<String>> eventStream = webClient.get()
.uri("/stream-sse")
.retrieve()
.bodyToFlux(type);
// SSE subscribe
eventStream.subscribe(
// success
content -> log.info("Time: {} - event: name[{}], id [{}], content[{}] ",
LocalTime.now(), content.event(), content.id(), content.data()),
// error
error -> log.error("Error receiving SSE: {}", error),
() -> log.info("Completed!!!")
);
}
- SSE 구독하는 코드
- 처음에 구독을 보내서 WebClient → 누군지 알려줘야 함.
- 말 그대로 구독을 시작한다고 보면 된다.
- 성공 / 실패 시나리오 로그를 찍는다.
SseEmitter 사용 예시
@GetMapping("/stream-sse-mvc")
public SseEmitter streamSseMvc() {
SseEmitter emitter = new SseEmitter();
ExecutorService sseMvcExecutor = Executors.newSingleThreadExecutor();
sseMvcExecutor.execute(() -> {
for (int i = 0; true; i++) {
try {
SseEventBuilder event = SseEmitter.event()
.id(String.valueOf(i))
.data(getUser())
.name("user-event-mvc");
emitter.send(event);
Thread.sleep(1000);
} catch (IOException | InterruptedException e) {
emitter.completeWithError(e);
}
}
});
return emitter;
}
- SseEmitter 를 이용해 데이터를 푸쉬하는 작업을 하는 스레드를 정의한다.
- Emitter 인스턴스를 반환하고 연결을 열어둔다. 😊
- 스레드가 1000milis 쉬며 계속 user 객체를 보내는 알림을 보내도록 설정하는 api.
- 계속 열어두긴 하지만 소켓과 다르게 단방향으로 알림만 보낼 뿐!
예시 코드 )
https://github.com/h0l1da2/SSE-Test
GitHub - h0l1da2/SSE-Test: Server-sent Event
Server-sent Event. Contribute to h0l1da2/SSE-Test development by creating an account on GitHub.
github.com
728x90
'개발공부 개발새발 > etc' 카테고리의 다른 글
HTTP ) HTTP1.1 과 HTTP2 의 차이는 무엇일까? (0) | 2024.12.23 |
---|---|
Kafka ) 변신의 카프카가 아니라 Apache Kafka 에 관하여 (0) | 2024.06.13 |
배웠던 것을 정리하는 그런 notion 을 작성했다면 믿으시겠습니까 ? (0) | 2023.07.17 |
클라우드 교육 정리와 후기! (0) | 2023.07.16 |
Mac M2 칩에서 오라클 실행 저장용 (0) | 2023.05.09 |