let nick= prompt("Enter your nickname")
let input= document.getElementById("input")
input.focus()
let chat= new EventSource("/chat");
chat.addEventListener("chat", event => { // 채팅 메시지가 도착하면;
let div= document.createElement("div")
div.append(event.data) // 텍스트 메시지를 추가한다
input.before(div) // 입력필드 앞에 div를 추가한다
input.scrollIntoView() // 입력 필드가 보이게 한다
});
// 사용자의 메시지를 서버로 보낸다
input.addEventListener("change", ()=>{ // 사용자가 Enter 키를 누르면;
fetch("/chat", {
method: "POST",
body: nick + ": " + input.value
}).catch(e => console.error) // 응답은 무시하고, 에러는 기록한다
input.value = "";
});
/* 아주 단순한 익명 채팅방 서버: chatServer.js */
// /chat에 새로운 메시지를 POST로 보내거나 GET으로 text/event-stream 메시지를 받는다
// /에 GET 요청을 보내면 클라이언트 사이드 채팅 UI가 포함된 단순한 HTML 파일을 받는다
const http= require("http")
const fs= require("fs")
const url= require("url")
const clientHTML= fs.readFileSync("chatClient.html") // 채팅 클라이언트 html 파일
let clients= [] // 이벤트를 보낼 ServerResponse 객체 배열
let server= new http.Server() // http 서버 생성
server.listen(8080)
server.on("request", (request, response) => { // 서버가 요청을 받으면;
let pathname= url.parse(request.url).pathname
if (pathname === "/") { // 요청이 "/"라면; 클라이언트 사이드 채팅 UI를 전송한다
response.writeHead(200, {"Content-Type": "text/html"}).end(clientHTML);
} else if (pathname !== "/chat" || (request.method !== "GET" && request.method !== "POST")) {
response.writeHead(404).end()
} else if (request.method === "GET") { // /chat에 GET 요청이 들어왔다면;
acceptNewClient(request, response) // 클라이언트가 연결한다
} else { // 아니라면;
broadcastNewMessage(request, response) // 새로운 메시지를 보내는 POST 요청이다
}
});
// 클라이언트가 새로운 EventSource 객체를 생성할 때,
// 또는 EventSource 객체가 자동으로 다시 연결될 때 생성되는 /chat 엔드포인트의 GET 요청을 처리한다
function acceptNewClient(request, response) {
clients.push(response) // 나중에 메시지를 보낼 수 있도록 응답 객체를 기억한다
request.connection.on("end", () => { // 클라이언트가 연결을 끊으면;
clients.splice(clients.indexOf(response), 1)
response.end() // 해당 클라이언트의 응답 객체를 제거한다
});
// 헤더를 설정하고 이 클라이언트에 초기 채팅 이벤트를 전송한다
response.writeHead(200, {
"Content-Type": "text/event-stream",
"Connection": "keep-alive",
"Cache-Control": "no-cache"
});
response.write("event: chat\ndata: Connected\n\n")
// 서버 전송 이벤트는 연결을 계속 유지해두어야 하므로, response.end()는 호출하지 않는다!
}
// 이 함수는 사용자가 새로운 메시지를 보내는 /chat 엔드포인트에 대한 POST 요청의 응답으로 호출된다
async function broadcastNewMessage(request, response) {
// 먼저 요청 바디를 읽어 사용자의 메시지를 가져온다
request.setEncoding("utf8")
let body= ""
for await (let chunk of request) {
body += chunk
}
response.writeHead(200).end() // 바디를 읽으면 빈 응답을 보내고 연결을 닫는다
// 메시지를 text/event-stream 형식으로 바꾸고, 각 행 앞에 "data: "를 붙인다
let message= "data: " + body.replace("\n", "\ndata: ");
// 메시지 데이터 앞에 채팅 이벤트임을 뜻하는 전치사를 붙이고 뉴라인 두개를 이어붙여 이벤트가 끝났음을 알린다
let event= `event: chat\n${message}\n\n`;
// 연결된 클라이언트 전체에 이 이벤트를 전송한다
clients.forEach(client => client.write(event));
}