SSE (Server-Sent Events)๋?
์๋ฒ์์ ํด๋ผ์ด์ธํธ๋ก ๋ฐ์ดํฐ๋ฅผ ๋น๋๊ธฐ์ ์ผ๋ก ์ ์กํ๋ ๊ธฐ์ ์ด๋ค. ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ์๊ฒ ์ง์์ ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ ์กํ ๋ ์ฃผ๋ก ์ฌ์ฉ๋๋ค.
๋ํ ์ค์๊ฐ ์ ๋ฐ์ดํธ๊ฐ ํ์ํ ๊ฒฝ์ฐ(์ฃผ์ ์์ธ, ์ค์๊ฐ ์ฑํ , ์๋ฆผ ๋ฑ)์ ์ฌ์ฉ๋๋ฉฐ HTTP ๊ธฐ๋ฐ์ผ๋ก ์น ๋ธ๋ผ์ฐ์ ์ ์น ์๋ฒ ๊ฐ์ ๋จ๋ฐฉํฅ ํต์ ์ ๊ฐ๋ฅํ๊ฒ ํ๋ฏ๋ก ํด๋ผ์ด์ธํธ๋ ์๋ฒ์ ์์ฒญ์ ๋ณด๋ด์ง ์๊ณ ๋ ์๋ฒ๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ ์ ์๊ณ , ํด๋ผ์ด์ธํธ๋ ์น ๋ธ๋ผ์ฐ์ ์ EventSource API๋ฅผ ์ฌ์ฉํด ์๋ฒ๋ก๋ถํฐ ์ด๋ฒคํธ๋ฅผ ์์ ํ๊ณ ์ฒ๋ฆฌํ๋ค.
ํ๋ก์ ํธ์์ ์จ๋ฐAI์๊ฒ ์ง๋ฌธ์ ์์ฒญํ๋ฉด ์๋ต์ ์ค์๊ฐ์ผ๋ก ๋ฐ๊ธฐ ์ํด ์ฌ์ฉํ๊ณ ์ ํ๋ค.
์ค์ ๋ก ํ๋ก์ ํธ์์ ์ด๋ป๊ฒ ๊ตฌํํ๋์ง ์์๋ณด์ !
WebClientService ์ฝ๋ ๊ตฌํ - ๋น๋๊ธฐ๋ก ์ธ๋ถ API ํธ์ถ
๐น WebClientService
@RequiredArgsConstructor
@Service
@Slf4j
public class WebClientService {
@Value("${alan.key}")
private String alanId;
private final WebClient webClient = WebClient.builder().build();
private final ObjectMapper objectMapper = new ObjectMapper().configure(JsonParser.Feature.ALLOW_SINGLE_QUOTES, true);
public Flux<AlanResponseDto> getResponse(String content){
return webClient.get()
.uri("https://kdt-api-function.azurewebsites.net/api/v1/question/sse-streaming",
uriBuilder -> uriBuilder
.queryParam("content", content)
.queryParam("client_id", alanId)
.build())
.retrieve()
.bodyToFlux(String.class)
.flatMap(this::parseJsonToResponseDto);
}
private Flux<AlanResponseDto> parseJsonToResponseDto(String jsonString){
try {
// JSON ๋ฌธ์์ด์ ResponseDto ๊ฐ์ฒด๋ก ํ์ฑ
AlanResponseDto responseDto = this.objectMapper.readValue(jsonString, AlanResponseDto.class);
return Flux.just(responseDto);
} catch (JsonProcessingException e) {
return Flux.error(e);
}
}
}
์ด์คํธ์ํํธ์์ ์ ๊ณตํ ์จ๋ฐAI API๋ฅผ WebClient๋ฅผ ํ์ฉํด ๋น๋๊ธฐ๋ก ์์ฒญํ๊ณ , ์๋ต์ ๋ฐ์ ๋ฌธ์์ด ์คํธ๋ฆผ์ผ๋ก ๋ณํํ๋ค.
๋ฌธ์์ด๋ก ๋ณํํ๋ ์ด์ ๋ ํด๋นํ๋ JSON ์๋ต๊ฐ์ด ํฐ๋ฐ์ดํ๊ฐ ์๋ ์์๋ฐ์ดํ๋ก ๊ตฌ์ฑ๋์ด์๊ธฐ ๋๋ฌธ์ด๋ค.
๊ทธ๋ฌ๋ฏ๋ก ๊ฐ parseJsonToResponseDto() ๋ฉ์๋๋ฅผ ํตํด์ ์์๋ฐ์ดํ๋ก ๊ตฌ์ฑ๋ JSON ๋ฌธ์์ด์ ๊ฐ์ฒด๋ก ํ์ฑํ๋ ๊ณผ์ ์ ๊ฑฐ์ณ์ผํ๊ณ ์ต์ข ์ ์ผ๋ก๋ Flux<AlanResponseDto>๋ก ๋ณํํ๋ค.
@Getter
public class AlanResponseDto {
@JsonProperty(value = "type")
private String type;
@JsonProperty(value = "data")
private Data data;
@Getter
public static class Data {
@JsonProperty(value = "content")
private String content;
@JsonProperty(value = "name")
private String name;
@JsonProperty(value = "speak")
private String speak;
}
}
AlanResponseDto๋ ์์ ๊ฐ๋ค. ์๋ต๊ฐ ๊ตฌ์กฐ์ ๋ง์ถฐ ์์ฑํ์๋ค.
SseController ์ฝ๋ ๊ตฌํ
๐น SseController
@RequiredArgsConstructor
@RestController
@RequestMapping("/api")
@Slf4j
public class SseController {
private final WebClientService webClientService;
@GetMapping("/alan")
public Flux<ServerSentEvent<AlanResponseDto>> getAlanResponse(HttpServletResponse response, @RequestParam String content){
response.setContentType("text/event-stream");
return webClientService.getResponse(content)
.doOnNext(data -> {
log.info("Received data: {}", data.getData().getContent());
})
.map(data -> ServerSentEvent.<AlanResponseDto>builder()
.data(data)
.build())
.delayElements(Duration.ofMillis(50));
}
}
WebClientService๋ฅผ ํตํด ์ป์ ๊ฐ์ SSE์ WebFlux๋ฅผ ํ์ฉํด ์ค์๊ฐ์ผ๋ก ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ค ์ ์๋๋ก ํ๋ ์ฝ๋์ด๋ค.
MIME ํ์ ์ text/event-stream์ผ๋ก ์ง์ ํ๊ณ , map์ ํตํด ์ ์กํ ๋ฐ์ดํฐ๋ฅผ ์ค์ ํ๊ณ ServerSentEvent๋ก ๋งคํํ๋ค.
ServerSentEvent ํด๋์ค๋ฅผ ์ดํด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ์ ํ๋๋ฅผ ํฌํจํ๋ฉฐ
๋ฉ์๋๋ฅผ ๊ฐ์ง๊ณ ์๋ค.
Representation for a Server-Sent Event for use with Spring's reactive Web support. Flux<ServerSentEvent> or Observable<ServerSentEvent> is the reactive equivalent to Spring MVC's SseEmitter.
์ฐธ๊ณ ์๋ฃ์ ์์ ๊ฐ์ ๋ด์ฉ์ด ์๋ ๊ฒ์ผ๋ก ๋ณด์ Spring MVC์ SseEmitter์ ์ ์ฌํ๋ค๋ ๊ฒ์ ์ ์ ์๋ค.
SseEmitter๋ ๋๊ธฐ์ ๋ฐ ๋ธ๋กํน ๋ฐฉ์์ Spring MVC์์ SSE๋ฅผ ๊ตฌํํ๊ธฐ ์ํ ํด๋์ค์ด๋ฉฐ,
Flux๋ ๋น๋๊ธฐ ๋ฐ ๋ ผ๋ธ๋กํน ๋ฐฉ์์ Spring WebFlux์์ SSE๋ฅผ ๊ตฌํํ๊ธฐ ์ํ ๋ฐ์ํ ์คํธ๋ฆผ์ด๋ฏ๋ก ๋ ๋ง์ ๋์์ฑ์ ์ง์ํ๋ฉฐ ๋ฆฌ์์ค๋ฅผ ํจ์จ์ ์ผ๋ก ์ฌ์ฉํ๋ค.
์ฃผ์ด์ง event, id, data ํ๋๋ Nullable์ด๋ฏ๋ก ๋ฐ๋์ ํ์ํ ๊ฒ์ ์๋์๋ค.
JavaScript๋ฅผ ํ์ฉํด ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ฉด์ ํ์คํ ์ฐ์์๋ฅผ ์๊ฒ ๋์๋ค.
์งํํ ํ๋ก์ ํธ์์๋ ๋ฑํ id, event๊ฐ ์์ด๋ ๋๋ ํ๋์๊ธฐ ๋๋ฌธ์ ์๋ตํ๊ณ , data๋ง ์ฌ์ฉํด ํด๋น ํ๋์ ์๋ต๊ฐ์ ๋ฃ์ด์ฃผ์๋ค.
์์ฑ๋ ํ๋ฉด
์ฐธ๊ณ ์๋ฃ
https://developer.mozilla.org/en-US/docs/Web/API/EventSource
https://tecoble.techcourse.co.kr/post/2022-10-11-server-sent-events/
'Spring > Spring & Spring Boot' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Spring] Example<T> ์ธํฐํ์ด์ค (0) | 2025.03.11 |
---|---|
[Spring] Spring Batch 5 ์ฌ์ฉํด๋ณด๊ธฐ (0) | 2024.07.31 |
[Spring] Spring Batch๋ก ํ ๋ฌ ์ง๋ ๋ฐ์ดํฐ ์ญ์ ํ๊ธฐ (0) | 2024.07.29 |
[Spring] Spring Security @AuthenticationPrincipal (0) | 2024.04.10 |
[Spring] ํํฐ(Filter)์ ์ธํฐ์ ํฐ(Interceptor) (0) | 2024.03.18 |