본문 바로가기

Compute/ELB

AWS ELB WebSocket 사용 시 헬스체크 방안

테스트용 WS 데모

 

[채팅 서버]

const WebSocketServer = require("websocket").server;
const http = require("http");
const port = 3000;
//포트
const server = http.createServer(function (request, response) {
  //일반 HTTP 요청 처리
  console.log(
    new Date() + " Can not get information reqeust of http " + request.url
  );
  response.writeHead(416);
  response.end();
});
server.listen(port, function () {
  console.log(new Date() + " Server is listening on port 3000");
});
const wsServer = new WebSocketServer({
  //웹소켓 서버 생성
  httpServer: server,
  autoAcceptConnections: false,
});
const rooms = new Map();
//채팅방 목록을 담을 객체
const requestType = {
  //메시지 타입
  A: "welcome",
  B: "send",
  C: "bye",
  D: "beforList",
};
wsServer.on("request", function (request) {
  const user = request.resourceURL.query.user;
  //사용자 ID
  const room = request.resourceURL.query.room;
  //방번호
  if (NUL(user) || NUL(room)) {
    return;
  }
  var connection = request.accept();
  //들어온 커넥션 객체
  sendBeforUserList(connection, room);
  //이미 들어와있는 사용자 목록을 전송
  rooms.set(user, { user: user, room: room, con: connection });
  //방 목록에 자신 추가
  msgSender(rooms.get(user), null, requestType.A);
  //로그인 타입으로 메시지 전송
  connection.on("message", function (message) {
    //채팅메시지가 도달하면
    msgSender(rooms.get(user), message, requestType.B);
  });
  connection.on("close", function (reasonCode, description) {
    //커넥션이 끊기면
    msgSender(rooms.get(user), null, requestType.C)
      .then((callbak) => {
        //방에서 나감을 알리고
        rooms.delete(user);
        //방 목록에서 삭제
      })
      .catch((err) => {
        console.log(err);
      });
  });
});
//파라미터 확인용 함수
function NUL(obj) {
  if (obj == undefined || obj == null || obj.length == 0) {
    return true;
  }
  return false;
}
//메시지를 보내는 함수
function msgSender(identify, message, type) {
  return new Promise((resolve, reject) => {
    for (let target of rooms.entries()) {
      //방 목록 객체를 반복문을 활용해 발송
      if (identify.room == target[1].room) {
        //같은방에 있는 사람이면 전송
        //타입별 전송 구간(최초접속,메시지전송,방나감)
        if (type == requestType.A) {
          var res = JSON.stringify({
            param: "room in",
            fromUser: identify.user,
            type: type,
          });
          target[1].con.sendUTF(res);
        } else if (type == requestType.B && message.type === "utf8") {
          var res = JSON.stringify({
            param: message.utf8Data,
            fromUser: identify.user,
            type: type,
          });
          target[1].con.sendUTF(res);
        } else if (type == requestType.C) {
          var res = JSON.stringify({
            param: "room out",
            fromUser: identify.user,
            type: type,
          });
          target[1].con.sendUTF(res);
        }
      }
    }
    resolve("succ");
  });
}
//방 접속 시 이미 들어와있는 목록을 받기위한 함수
function sendBeforUserList(connection, room) {
  new Promise((resolve, reject) => {
    var beforList = new Array();
    for (let target of rooms.entries()) {
      //반복문을 통해 사용자 리스트를 배열에 담아서
      if (room == target[1].room) {
        beforList.push(target[1].user);
      }
    }
    resolve(beforList);
  })
    .then((list) => {
      var res = JSON.stringify({ param: "", users: list, type: requestType.D });
      //막 들어온 사용자한테 해당 대상을 전달.
      connection.sendUTF(res);
    })
    .catch((err) => {
      console.log(err);
    });
}

 

[Client]

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="cache-control" content="no-cache" />
    <meta http-equiv="pragma" content="no-cache" />
    <meta http-equiv="expires" content="0" />
    <meta http-equiv="X-UA-Compatible" content="IE=10" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>websocket</title>
  </head>
  <script
    src="https://code.jquery.com/jquery-2.2.4.js"
    integrity="sha256-iT6Q9iMJYuQiMWNd9lDyBUStIq/8PuOW33aOqmvFpqI="
    crossorigin="anonymous"
  ></script>
  <style type="text/css">
    #console-container {
      border: 1px solid #cccccc;
      overflow-y: scroll;
      height: 250px;
      width: 60%;
      padding: 13px;
      border-radius: 3px;
      display: inline-block;
    }
    .myMsg {
      width: 100%;
      text-align: left;
      height: 35px;
      padding: 3px;
      color: gray;
    }
    .friendsMsg {
      width: 100%;
      text-align: right;
      height: 35px;
      padding: 3px;
      color: black;
    }
    #users {
      border: 1px solid #cccccc;
      overflow-y: scroll;
      height: 250px;
      width: 30%;
      padding: 13px;
      border-radius: 3px;
      display: inline-block;
    }
    .members {
      width: 100%;
      padding: 5px;
    }
  </style>
  <body>
    <!-- 채팅값이 들어가는 테그 -->
    <div id="console-container"></div>
    <div id="users"></div>
    <!-- 인풋 박스 -->
    <p></p>
    <div>
      <input type="text" placeholder="type and press enter to chat" id="chat" />
      <input type="button" value="send" id="clicker" />
    </div>
    <br /><br />
  </body>
</html>
<script type="application/javascript">
  var user = "admin";
//사용자id, id는 당연히 고유값으로 부여해야 한다.
var room = "1";
//방번호, 해당번호가 틀리면 채팅메시지를 받을 수 없다.
var url = "ws://13.209.49.130:3000?user=" + user + "&room=" + room;
var socket = new WebSocket(url);
socket.onopen = function () {
  console.log("connection ok");
};
socket.onclose = function () {
  console.log("connection fail");
};
socket.onmessage = function (response) {
  var msg = JSON.parse(response.data);
  makeMsg(msg);
};
$("#chat").keydown(function (event) {
  if (event.keyCode == 13) {
    var value = $(this).val();
    socket.send(value);
  }
});
$("#clicker").click(function () {
  var value = $("#chat").val();
  socket.send(value);
});
var stop = true;
//들어온 메시지 그리기 함수
function makeMsg(msg) {
  console.log(msg);
  if (msg.type == "beforList" && stop) {
    //나보다 먼저 들어온 사용자
    msg.users.forEach(function (beforUsr) {
      var usr = $("<div/>")
        .attr({ id: beforUsr, class: "members" })
        .text(beforUsr);
      $("#users").append(usr);
    });
    stop = false;
  } else {
    if (msg.type == "welcome") {
      //등록
      var usr = $("<div/>")
        .attr({ id: msg.fromUser, class: "members" })
        .text(msg.fromUser);
      $("#users").append(usr);
    } else if (msg.type == "bye") {
      //나감
      $("#" + msg.fromUser).remove();
    }
    var cls = "friendsMsg";
    if (msg.fromUser == user) {
      cls = "myMsg";
    }
    var child = $("<div/>")
      .attr("class", cls)
      .append(
        $("<span/>").text(msg.fromUser),
        $("<small/>").text(" (" + yymmhhddss() + ") "),
        $("<span/>").text(" : " + msg.param)
      );
    $("#console-container").append(child);
    $("#console-container").animate(
      { scrollTop: $("#console-container").get(0).scrollHeight },
      10
    );
  }
} //시간 그리기 함수
function yymmhhddss() {
  var time = new Date();
  var year = time.getFullYear();
  var month = time.getMonth() + 1;
  var day = time.getDay();
  var hhmmss =
    ("0" + time.getHours()).slice(-2) +
    ":" +
    ("0" + time.getMinutes()).slice(-2) +
    ":" +
    ("0" + time.getSeconds()).slice(-2);
  if (month < 10) {
    month = "0" + month;
  }
  if (day < 10) {
    day = "0" + day;
  }
  param = year + "-" + month + "-" + day + " " + hhmmss;
  return param;
}
</script>


ALB 사용 시 헬스체크는 HTTP(S) 만 가능하며, 서비스 포트와 헬스체크 포트는 구별 가능.

따라서 ALB 사용을 위해서는 WS 포트와 헬스체크용 포트를 구별해야 한다.
(WS 포트 모니터링은 추가적으로 필요)

ALB에서는 WS 통신은 지원하나, 헬스체크에서는 WS를 지원하지 않는다.

따라서 몇몇 레퍼런스를 확인하니 웹소켓 이외에 특정 포트를 올리는 것이 아니라, 웹소켓을 사용하는 채팅 서버에서 리턴하는 에러 코드를 416 등으로 바꾸어 사용하기도 한다.