반응형

Server Sent Event(SSE)란?

  • HTTP 스트리밍을 통해 서버에서 클라이언트로 단방향의 Push Notification을 전송할 수 있는 HTML5 표준 기술.
  • HTTP 프로토콜을 기반으로 서버에서 클라이언트로 Real-Time Push Notification을 전송할 수 있다.
  • EventStream의 최대 개수는 HTTP/1.1 사용시 6개, HTTP/2 사용시 최대 100개까지 유지 가능하다.
  • JavaScript의 EventSource를 이용하여 사용 가능하고, 접속에 문제가 있으면 자동으로 연결을 재시도하는 특징을 갖고 있다.
  • IE에서는 EventSource를 기본 제공하지 않지만 polyfill을 통해 사용 가능하다.
  • Server To Client로의 단방향 연결만 지원하기 때문에 서버에서 데이터를 프론트로 일방적으로 내려주는 케이스에서 적절하다.
  • 양방향 연결을 사용해야할 경우 WebSocket이 적절하다.

Server

@RestController
class ChattingController {
    private val log = LoggerFactory.getLogger(this::class.java)
    private val clients = ConcurrentHashMap<String, SseEmitter>()

    @GetMapping("/chat/connect/{clientId}")
    fun connect(@PathVariable clientId: String): SseEmitter {
        val emitter = SseEmitter(60_000)
        clients[clientId] = emitter

        log.info("connected: {}", clientId)

        emitter.onCompletion {
            log.info("disconnected: {}", clientId)
            clients.remove(clientId)
        }

        emitter.onTimeout {
            log.info("timeout: {}", clientId)
            clients.remove(clientId)
        }

        emitter.onError {
            log.info("error: {}", clientId, it)
            clients.remove(clientId)
        }

        return emitter
    }

    @PostMapping("/chat/send")
    fun send(@RequestBody message: Message) {
        if (message.receiverId == null) {
            clients.values.forEach { it.send(message, MediaType.APPLICATION_JSON) }
        } else {
            clients[message.receiverId]?.send(message, MediaType.APPLICATION_JSON)
        }
    }

    data class Message(
        val senderId: String,
        val receiverId: String?,
        val message: String,
    )
}

Client

경로 : ~/src/main/resources/static/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SSE Chat Example</title>
</head>
<body>
<h1>Chat Example</h1>
<ul id="messages"></ul>
<div>
    <input id="receiverId" placeholder="receiverId"/>
    <input id="message" placeholder="message"/>
    <button id="send">전송</button>
</div>

<script>
    const clientId = "client_" + Math.random().toString(36).substring(7);
    const eventSource = new EventSource(`/chat/connect/${clientId}`);

    eventSource.onopen = function (event) {
        console.log(`open - ${clientId}`, event);
    };

    eventSource.onerror = function (event) {
        console.error(`error - ${clientId}`, event);
    };

    eventSource.onmessage = function (event) {
        const messages = document.querySelector("#messages");
        const newMessage = document.createElement("li");
        const json = JSON.parse(event.data);
        newMessage.textContent = json.message;
        messages.appendChild(newMessage);
    };

    document.querySelector('#send').addEventListener("click", function () {
        const receiverId = document.querySelector('#receiverId').value;
        const message = document.querySelector('#message').value

        fetch('/chat/send', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify({
                senderId: clientId,
                receiverId: receiverId || null,
                message,
            })
        });
    });
</script>
</body>
</html>

참고

 

반응형

'Development > Spring' 카테고리의 다른 글

[Spring] DataSource  (0) 2021.05.19
[Spring] Testcontainers  (0) 2021.04.15
[Spring] H2 Database  (0) 2021.02.27
[Spring] Spring Cloud Feign  (0) 2020.12.27
[Spring] Spring Cloud Gateway  (0) 2020.12.27

+ Recent posts