참고
1. 목차
NOTE
•
•
Server-sent events(SSE)
NOTE
SSE는 HTML5의 기능 중 하나이며, 기술적으로는 HTTP/1.1의 청크 형식을 이용한 통신 기능을 바탕으로 합니다. 웹소켓과 유사하지만, 단방향 통신(서버 → 클라이언트)만을 지원한다는 차이가 있습니다.
SSE는 HTTP 프로토콜을 사용하여 서버 → 클라이언트 방향으로 실시간 데이터를 푸시할 수 있는 기술입니다. 청크 전송 방식을 사용하여 클라이언트가 1번 연결을 열면, 서버가 지속적으로 데이터를 전송합니다.
1.
클라이언트는 서버에 HTTP Request을 보낸다.
2.
서버는 클라이언트에 Content-Type을 text/event-stream으로 설정한뒤 Response를 보낸다.
3.
서버는 연결이 열려있는 동안 클라이언트에 이벤트를 전송합니다.
4.
클라이언트는 서버로부터 전송된 이벤트를 수신하고 처리합니다.
서버 구현
서버에서는 HTTP 헤더에 Content-Type을 text/event-stream으로 설정하고, 이 포맷에 맞춰 데이터를 전송합니다.
package main
import (
"fmt"
"log"
"net/http"
"time"
)
func sseHandler(w http.ResponseWriter, r *http.Request) {
// CORS 헤더 설정
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
// 클라이언트 연결이 끊길 때를 감지
notify := w.(http.CloseNotifier).CloseNotify()
go func() {
<-notify
log.Println("Client disconnected")
}()
// 이벤트 ID 설정
id := time.Now().Format("15:04:05")
// 주기적으로 데이터를 클라이언트에 전송
for {
select {
case <-time.After(1 * time.Second):
fmt.Fprintf(w, "id: %s\n", id)
fmt.Fprintf(w, "data: %s\n\n", time.Now().Format(time.RFC3339))
// 플러시하여 데이터 전송
if f, ok := w.(http.Flusher); ok {
f.Flush()
}
}
}
}
func main() {
http.HandleFunc("/events", sseHandler)
log.Println("SSE server running on port 3000")
log.Fatal(http.ListenAndServe(":3000", nil))
}
JavaScript
복사
•
Cache-Control: no-cache: SSE 연결동안, 브라우저나 중간 프록시가 이벤트 스트림을 캐시하지 않도록 합니다.
◦
SSE는 실시간 데이터 전송을 위해 사용하므로, 캐싱이 필요없습니다.
◦
no-cache 헤더는 캐시없이 매번 최신데이터를 가져옵니다.
•
Connection: Keep-alive: HTTP 연결을 지속적으로 연결하여, 여러번의 요청을 하나의 연결로 처리하게 해준다.
◦
SSE는 지속적인 연결을 필요로하며, 연결이 닫히면 자동으로 재연결을 시도합니다.
◦
Go언어의 경우 기본적으로 Keep-alive를 지원하지만, 명시적으로 설정하기 위해 작성했습니다.
클라이언트 구현
웹 브라우저에서는 EventSource 인터페이스를 사용하여 이 이벤트 스트림에 연결하고 데이터를 받습니다.
<!DOCTYPE html>
<html>
<head>
<title>SSE Example</title>
</head>
<body>
<h1>Server-Sent Events</h1>
<ul id="eventList"></ul>
<script>
const evtSource = new EventSource("http://localhost:3000/events");
// 일반 메시지의 이벤트 핸들러
evtSource.onmessage = (e) => {
const newElement = document.createElement("li");
newElement.innerHTML = "message: " + e.data;
document.getElementById("eventList").appendChild(newElement);
};
// 'ping' 이벤트 리스너 추가
evtSource.addEventListener("ping", (e) => {
const newElement = document.createElement("li");
const obj = JSON.parse(e.data);
newElement.innerHTML = "ping at " + obj.time;
document.getElementById("eventList").appendChild(newElement);
}, false);
</script>
</body>
</html>
HTML
복사
1.
EventSource 객체 생성:
•
EventSource는 지정된 URL(ssedemo.php)에서 서버로부터 이벤트를 받기 위한 객체를 생성합니다.
•
이 객체는 서버에서 발송한 이벤트를 자동으로 수신합니다.
2.
일반 메시지 처리:
•
onmessage 핸들러는 특별한 이벤트 타입이 지정되지 않은 모든 메시지를 처리합니다.
•
수신된 데이터(e.data)를 사용하여 새로운 <li> 요소를 생성하고, 그 요소를 문서의 eventList라는 ID를 가진 요소에 추가합니다.
3.
특정 이벤트 처리 (ping):
•
addEventListener를 사용하여 ping 이벤트를 특별히 처리합니다.
•
ping 이벤트에 대한 데이터는 JSON 포맷으로 가정하며, 이를 파싱하여 시간 정보를 추출합니다.
•
추출된 시간 정보를 포함하는 새로운 <li> 요소를 생성하여 eventList에 추가합니다.
웹소켓
NOTE
웹소켓은 HTML5의 일부로, 클라이언트-서버간의 양방향 통신을 가능하게 해주는 프로토콜이며, HTTP를 통해 초기 핸드셰이크 이후 전용 프로토콜로 업그레이드 됩니다.
지속적인 연결로 양방향 통신이 가능!
•
웹소켓은 서버/클라이언트 사이에 오버헤드가 적은 양방향 통신을 실현합니다. 통신이 확립되면 서버/클라이언트 사이에서 1:1 통신을 수행합니다.
•
프레임 단위로 송수신하지만, 상대방이 정해져 있으므로 전송할 곳에 관한 정보는 가지지 않습니다. HTTP의 기본 요소중에서 바디만 보내는 것과 같습니다.
웹소켓 구현
go get github.com/gorilla/websocket
Bash
복사
package main
import (
"fmt"
"net/http"
"github.com/gorilla/websocket"
)
// 업그레이더 설정: HTTP 연결을 웹소켓으로 업그레이드하는 데 사용
var upgrader = websocket.Upgrader{
// 모든 도메인에서의 요청을 허용
CheckOrigin: func(r *http.Request) bool {
return true
},
}
// 연결된 클라이언트들을 저장하기 위한 맵
var clients = make(map[*websocket.Conn]bool)
// 메시지를 전달하기 위한 채널
var broadcast = make(chan Message)
// 메시지 구조체
type Message struct {
Username string `json:"username"`
Message string `json:"message"`
}
func main() {
// 파일 서버 설정: 현재 디렉토리의 파일을 제공
fs := http.FileServer(http.Dir("./"))
http.Handle("/", fs)
// 웹소켓 핸들러 설정
http.HandleFunc("/ws", handleConnections)
// 메시지 처리 고루틴 시작
go handleMessages()
// 서버 시작
fmt.Println("HTTP server started on :8080")
err := http.ListenAndServe(":8080", nil)
if err != nil {
fmt.Println("ListenAndServe: ", err)
}
}
// 웹소켓 연결을 처리하는 함수
func handleConnections(w http.ResponseWriter, r *http.Request) {
// HTTP 요청을 웹소켓 연결로 업그레이드
ws, err := upgrader.Upgrade(w, r, nil)
if err != nil {
fmt.Println(err)
return
}
defer ws.Close()
// 새로운 클라이언트를 맵에 추가
clients[ws] = true
// 무한 루프: 클라이언트로부터 메시지를 읽고 브로드캐스트 채널로 전달
for {
var msg Message
err := ws.ReadJSON(&msg)
if err != nil {
fmt.Println("error: ", err)
// 에러 발생 시 클라이언트를 맵에서 제거
delete(clients, ws)
break
}
broadcast <- msg
}
}
// 메시지를 처리하고 모든 클라이언트에 전달하는 함수
func handleMessages() {
for {
// 브로드캐스트 채널에서 메시지 수신
msg := <-broadcast
// 모든 클라이언트에 메시지 전송
for client := range clients {
err := client.WriteJSON(msg)
if err != nil {
fmt.Println("error: ", err)
client.Close()
delete(clients, client)
}
}
}
}
Go
복사
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebSocket Chat</title>
<style>
body { font-family: Arial, sans-serif; }
#chat { max-width: 600px; margin: 0 auto; }
#messages { border: 1px solid #ccc; padding: 10px; height: 300px; overflow-y: scroll; }
#input { display: flex; }
#username { width: 100px; }
#message { flex: 1; }
</style>
</head>
<body>
<div id="chat">
<h2>WebSocket Chat</h2>
<div id="messages"></div>
<div id="input">
<input type="text" id="username" placeholder="Username">
<input type="text" id="message" placeholder="Message">
<button onclick="sendMessage()">Send</button>
</div>
</div>
<script>
// 웹소켓 서버와의 연결 설정
const ws = new WebSocket('ws://localhost:8080/ws');
// 서버로부터 메시지를 수신했을 때 호출되는 함수
ws.onmessage = function(event) {
const message = JSON.parse(event.data);
const messages = document.getElementById('messages');
// 새로운 메시지를 채팅창에 추가
messages.innerHTML += `<p><strong>${message.username}</strong>: ${message.message}</p>`;
// 스크롤을 아래로 이동
messages.scrollTop = messages.scrollHeight;
};
// 메시지 전송 함수
function sendMessage() {
const username = document.getElementById('username').value;
const message = document.getElementById('message').value;
if (username && message) {
// 메시지를 JSON 형식으로 웹소켓을 통해 서버로 전송
ws.send(JSON.stringify({ username: username, message: message }));
// 메시지 입력란 초기화
document.getElementById('message').value = '';
}
}
</script>
</body>
</html>
HTML
복사
HTTP 웹 푸시
NOTE
웹 푸시는 푸시 알람을 브라우저를 통해 사용자에게 전달하는 기술이며, 실시간 알림을 전송하는데 사용됩니다. 이는 사용자가 웹 애플리케이션을 활성화하지 않아도 브라우저를 통해 알람을 받습니다.
HTTP 웹 푸시는 서비스 워커(Service Worker)와 푸시 API를 활용하여 구현되며, 사용자에게 실시간 알림을 제공하여 사용자 경험을 향상시키는 데 중요한 역할을 합니다.
•
크롬이 이용하는 푸시 서비스는 GCM, FCM을 기반으로 합니다. 따라서 각각 애플리케이션을 등록해야하고 애플리케이션 별로 전용 키 및 URL이 제공됩니다.
주요 구성요소
•
서비스 워커
// addEventListener() 이벤트 핸들러를 등록해두면, 필요할 때만 실행되어 처리
self.addEventListener('push', function(event) {
const data = event.data.json();
const options = {
body: data.body,
icon: '/images/icon.png',
badge: '/images/badge.png'
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
});
JavaScript
복사
◦
백그라운드에서 실행되는 스크립트로, 푸시 알람을 수신/처리 할 수 있습니다.
◦
웹 애플리케이션이 열려 있지 않더라도 푸시 알림을 수신할 수 있게 해줍니다.
◦
각 서비스에서 서비스의 ID와 전송용 키를 가져옵니다. 수신할 땐 ID가 전송할 땐 ID와 키가 필요합니다.
•
푸시 API
◦
서버가 브라우저 푸시 메시지를 전송할 수 있게 해주는 API입니다.
◦
푸시 메시지는 서비스 워커로 전달되어 브라우저에서 알림을 표시합니다.
•
알림 API
◦
푸시 메시지를 사용자에게 시각적으로 알리기 위해 사용됩니다.
◦
브라우저에서 네이티브 알람을 표시할 수 있습니다.
목차
NOTE
•
•
1. 목차
NOTE
•
•
목차
NOTE
•
•