1. 개요

이 예제에서는 Spring을 사용하여 Server-Sent-Events 기반 API를 구현하는 방법을 살펴봅니다.

간단히 말해, Server-Sent-Events 또는 줄여서 SSE는 웹 애플리케이션이 단방향 이벤트 스트림을 처리하고 서버가 데이터를 내보낼 때마다 업데이트를 수신할 수 있도록 하는 HTTP 표준입니다.

Spring 4.2 버전에서 이미 지원했지만 Spring 5부터는 보다 관용적이고 편리한 방법으로 처리할 수 있습니다.

2. Spring 5 Webflux를 사용한 SSE

이를 달성하기 위해 Reactor 라이브러리에서 제공하는 Flux  클래스 또는 잠재적 으로 이벤트 메타데이터를 제어할 수 있는 ServerSentEvent 엔터티 와 같은 구현을 사용할 수 있습니다 .

2.1. Flux 를 사용한 스트리밍 이벤트

Flux  는 이벤트 스트림의 반응적 표현입니다. 지정된 요청 또는 응답 미디어 유형에 따라 다르게 처리됩니다.

SSE 스트리밍 엔드포인트를 생성하려면 W3C 사양 을 따르고 MIME 유형을 text/event-stream 으로 지정해야 합니다 .

@GetMapping(path = "/stream-flux", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamFlux() {
    return Flux.interval(Duration.ofSeconds(1))
      .map(sequence -> "Flux - " + LocalTime.now().toString());
}

interval  메서드는 긴 값을 점진적으로 방출하는 Flux 생성 합니다 . 그런 다음 해당 값을 원하는 출력에 매핑합니다.

응용 프로그램을 시작하고 Endpoints을 탐색하여 사용해 보겠습니다.

우리는 브라우저가 서버에 의해 초 단위로 푸시되는 이벤트에 어떻게 반응하는지 볼 것입니다. FluxReactor Core 에 대한 자세한 내용은 이 게시물 에서 확인할 수 있습니다 .

2.2. ServerSentEvent 요소 사용하기

이제 출력 문자열ServerSentSevent 개체로 래핑하고 이 작업의 이점을 살펴보겠습니다.

@GetMapping("/stream-sse")
public Flux<ServerSentEvent<String>> streamEvents() {
    return Flux.interval(Duration.ofSeconds(1))
      .map(sequence -> ServerSentEvent.<String> builder()
        .id(String.valueOf(sequence))
          .event("periodic-event")
          .data("SSE - " + LocalTime.now().toString())
          .build());
}

알 수 있듯이 ServerSentEvent 엔터티 를 사용하면 몇 가지 이점이 있습니다 .

  1. 실제 사례 시나리오에서 필요한 이벤트 메타데이터를 처리할 수 있습니다.
  2. " text/event-stream " 미디어 유형 선언 을 무시할 수 있습니다.

이 경우 id , 이벤트 이름 및 가장 중요한 이벤트의 실제 데이터 를 지정했습니다.

또한 이벤트 전송을 시도할 때 사용할 재연결 시간을 지정 하는 어노테이션 속성과 재시 도 값을 추가할 수 있습니다.

2.3. WebClient로 서버 전송 이벤트 사용

이제 WebClient 로 이벤트 스트림을 사용하겠습니다 .:

public void consumeServerSentEvent() {
    WebClient client = WebClient.create("http://localhost:8080/sse-server");
    ParameterizedTypeReference<ServerSentEvent<String>> type
     = new ParameterizedTypeReference<ServerSentEvent<String>>() {};

    Flux<ServerSentEvent<String>> eventStream = client.get()
      .uri("/stream-sse")
      .retrieve()
      .bodyToFlux(type);

    eventStream.subscribe(
      content -> logger.info("Time: {} - event: name[{}], id [{}], content[{}] ",
        LocalTime.now(), content.event(), content.id(), content.data()),
      error -> logger.error("Error receiving SSE: {}", error),
      () -> logger.info("Completed!!!"));
}

subscribe 메서드를 사용하면 이벤트를 성공적으로 수신했을 때, 오류가 발생했을 때, 스트리밍이 완료되었을 때 어떻게 진행할 것인지를 나타낼 수 있습니다 .

이 예에서는  Response body을 가져오는 간단하고 직접적인 방법인 검색 방법을 사용했습니다.

이 메서드  는 onStatus  문을  추가하는 시나리오를 처리하지 않는 한 4xx 또는 5xx 응답을 받으면  자동으로 WebClientResponseException 을 발생시킵니다.

반면에  ClientResponse  에 대한 액세스를 제공  하고 실패한 응답에 대한 오류 신호를 보내지 않는 교환 방법도 사용할 수 있었습니다.

이벤트 메타데이터가 필요하지 않은 경우 ServerSentEvent 래퍼 를 우회할 수 있다는 점을 고려해야 합니다 .

3. Spring MVC의 SSE 스트리밍

우리가 말했듯이 SSE 사양은 SseEmitter 클래스가 도입된 Spring 4.2부터 지원되었습니다.

간단히 말해서 우리는 SseEmitter 가 데이터 푸시 작업을 수행하고 이미터 인스턴스를 반환 하는 스레드 인 ExecutorService 를 정의하여 다음과 같은 방식으로 연결을 열어 둡니다.

@GetMapping("/stream-sse-mvc")
public SseEmitter streamSseMvc() {
    SseEmitter emitter = new SseEmitter();
    ExecutorService sseMvcExecutor = Executors.newSingleThreadExecutor();
    sseMvcExecutor.execute(() -> {
        try {
            for (int i = 0; true; i++) {
                SseEventBuilder event = SseEmitter.event()
                  .data("SSE MVC - " + LocalTime.now().toString())
                  .id(String.valueOf(i))
                  .name("sse event - mvc");
                emitter.send(event);
                Thread.sleep(1000);
            }
        } catch (Exception ex) {
            emitter.completeWithError(ex);
        }
    });
    return emitter;
}

항상 사용 사례 시나리오에 맞는 ExecutorService 를 선택해야 합니다.

우리는 Spring MVC에서 SSE에 대해 더 많이 배울 수 있고 이 흥미로운 예제 을 읽으면 다른 예제를 볼 수 있습니다 .

4. 서버 전송 이벤트 이해

이제 SSE Endpoints을 구현하는 방법을 알았으므로 몇 가지 기본 개념을 이해하여 좀 더 깊이 들어가 보겠습니다.

SSE는 이벤트를 언제든지 단방향으로 스트리밍할 수 있도록 대부분의 브라우저에서 채택한 사양입니다.

'이벤트'는 사양에 정의된 형식을 따르는 UTF-8로 인코딩된 텍스트 데이터의 스트림일 뿐입니다.

이 형식은 줄바꿈으로 구분된 일련의 키-값 요소(id, retry, data 및 이름을 나타내는 이벤트)로 구성됩니다.

댓글도 지원합니다.

사양은 어떤 식으로든 데이터 페이로드 형식을 제한하지 않습니다. 간단한 문자열 이나 더 복잡한 JSON 또는 XML 구조를 사용할 수 있습니다.

마지막으로 고려해야 할 사항은 SSE 스트리밍과 WebSockets 사용의 차이점  입니다.

WebSocket 은 서버와 클라이언트 간에 전이중(양방향) 통신을 제공하는 반면 SSE는 단방향 통신을 사용합니다 .

또한 WebSockets 는 HTTP 프로토콜이 아니며 SSE와 달리 오류 처리 표준을 제공하지 않습니다.

5. 결론

요약하자면, 이 기사에서 우리는 의심할 여지 없이 차세대 시스템을 만들 수 있는 훌륭한 리소스인 SSE 스트리밍의 주요 개념을 배웠습니다.

우리는 이제 이 프로토콜을 사용할 때 내부에서 일어나는 일을 이해할 수 있는 훌륭한 위치에 있습니다.

또한 Github 리포지토리 에서 찾을 수 있는 몇 가지 간단한 예제로 이론을 보완했습니다 .

Generic footer banner