본문 바로가기

Study/JavaScript

[JavaScript] 웹소켓 사용하기

API를 통해 서버에서 받아온 어떤 정보를 화면에 뿌려주려고 할 때 fetch 메소드를 사용해서 데이터를 받아오거나, axios 모듈을 사용하는 경우가 많다.

 

서버가 RESTful API를 지원한다면 API KEY 등을 사용해서 정보를 받아올 수 있는데, 개인 서비스라면 모르겠지만 상용으로 개발된 것은 프론트단에서 API키를 공개하여 던져주게 된다면 보안결함 등 이슈가 충분히 발생할 수 있다.

 

때문에 어떤 곳에서는 웹소켓을 사용해서 API_KEY 등이 요구되는 개인적인 정보를 제외한, 공공정보를 개발자가 사용할 수 있도록 제공하고 있다.

 

웹소켓을 사용해본적이 한번도 없으며 이번 기회에 맛을 보고(?) 기억해둘 겸 정리하고자 한다.

 

1. 웹소켓이란?


웹소켓(WebSocket)은 하나의 TCP 접속에 전이중 통신 채널을 제공하는 컴퓨터 통신 프로토콜이다. 웹소켓 프로토콜은 2011년 IETF에 의해 RFC 6455로 표준화되었으며 웹 IDL의 웹소켓 API는 W3C에 의해 표준화되고 있다.

위키피디아(https://ko.wikipedia.org/wiki/웹소켓)

사전적 개념이 어렵다고 느껴지기 때문에 쉽게 정리하면

 

인터넷이 보급되면서 HTTP라는 개념을 통해 서버에서 데이터를 가져오려면 무조건 URL을 통해 요청을 주고 받았다. 그렇기 때문에 과거의 웹은 회원가입을 할 때 새로운 팝업을 띄워 아이디 중복검사 등을 하게 하였다.

꽤나 불편하다고 느끼면서 발전시킨 것이 우리가 익히 아는 Ajax 통신이며 이는 서버에 XMLHttpRequest 객체로 요청을 보내면 서버가 응답을 주는 방식이다.

 

위에서 언급한 아이디 중복검사 시 새로운 페이지를 요청하는 방식이 아닌 데이터만을 요청하는 방법이다. 사용자의 이벤트로부터 JavaScript는 사용자가 이벤트를 발생시킨 DOM을 읽으며 XMLHttpRequest 객체는 해당 DOM의 값을 읽어 서버에 넘겨주게 된다.

 

다시 서버는 문자열이나 JSON, XML 형식으로 XMLHttpRequest 객체에 요청 결과를 넘겨준다.

JavaScript는 넘겨받은 값을 해당 DOM에 입력해준다.

 

다시 말해서 Ajax를 사용함에 따라 DOM 일부의 값만 변경할 수 있다는 장점이 생겼는데, 이 방식도 결국은 HTTP를 사용하기 때문에 요청을 보내야 응답이 전달되고 변경된 데이터를 가져오기 위해서 일정시간마다 데이터를 갱신하게 한다든지 사용자로 하여금 DOM 요소에 이벤트를 발생시키도록 유도하는 것은 자원낭비라는 것이 결론일 수 있다.

 

이러한 문제를 해결하기 위해 웹소켓이 등장하였으며 지금부터 웹소켓의 기본적인 사용 방법에 대해 살펴보겠다.

 

 

2. 웹소켓 생성하기


웹소켓을 사용하기 위해서는 먼저 웹소켓을 생성해야 한다. 자바스크립트의 new 연산자를 사용한다.

객체를 생성할 때 반드시 주소가 입력되어야 한다.

const socket = new WebSocket("wss://javascript.info");

또 한가지 중요한 것은 ws 프로토콜보다 wss 프로토콜이 보안에 더 유리하므로 후자를 사용하자는 권고가 있다.

📍 항상 wss://를 사용합시다.
wss://는 보안 이외에도 신뢰성(reliability) 측면에서 ws보다 좀 더 신뢰할만한 프로토콜입니다.ws://를 사용해 데이터를 전송하면 데이터가 암호화되어있지 않은 채로 전송되기 때문에 데이터가 그대로 노출됩니다. 그런데 아주 오래된 프락시 서버는 웹소켓이 무엇인지 몰라서 ‘이상한’ 헤더가 붙은 요청이 들어왔다고 판단하고 연결을 끊어버립니다.반면 wss://는 TSL(전송 계층 보안(Transport Layer Security))이라는 보안 계층을 통과해 전달되므로 송신자 측에서 데이터가 암호화되고, 복호화는 수신자 측에서 이뤄지게 됩니다. 따라서 데이터가 담긴 패킷이 암호화된 상태로 프락시 서버를 통과하므로 프락시 서버는 패킷 내부를 볼 수 없게 됩니다.

모던 자바스크립트 튜토리얼(https://ko.javascript.info/websocket)

 

 

3. 웹소켓이 제공하는 4가지 이벤트


2번 과정에서 소켓이 정상적으로 생성되었다면 아래 4가지 이벤트를 사용할 수 있다.

  • open: 커넥션이 제대로 생성되었을 때 발생하는 이벤트
  • message: 데이터를 수신하였을 때 발생하는 이벤트
  • error: 에러가 발생했을 때 이벤트
  • close: 커넥션이 종료되었을 때 발생하는 이벤트

 

 

4. 예시


// 웹소켓 생성
const socket = new WebSocket("wss://dummydata.or.kr");

// 커넥션이 제대로 생성되었을 때
socket.onopen = function (e) {
    let data = {
        action: "good",
        subscribe: "test",
    };
    socket.send(JSON.stringify(data))
};


let result;
let price;

// 데이터를 수신 받았을 때
socket.onmessage = async function (e) {
    try {
        if (e !== null && e !== undefined) {
            result = await JSON.parse(e.data);
            price = result[0];
            document.getElementById("test").innerHTML = `$ ${price}`
        }
    } catch (err) {
        console.log(err);
    }
};

// 에러가 발생했을 때
socket.onerror = function (e) {
    console.log(e);
};

 

우선 onopen에 함수를 걸어 데이터를 전송하게 해주었다. 여기서 데이터가 자바스크립트 객체, 즉 JSON 형식으로 보내는 것이라면 반드시 JSON.stringify를 통해 문자열로 넘어가게 해 주어야 한다.

데이터를 넘겨주기 위해 send 메소드를 사용했다.

 

onmessage에 마찬가지로 함수를 걸어서 서버에서 응답을 받았을 때 처리할 내용을 넣어준다.

이때 JSON 형태로 넘어온다면, 넘어온 데이터의 타입은 문자열이기 때문에 해당 응답 결과가 null 값이 아닌지, undefined가 아닌지 체크해주고 JSON으로 파싱해주면 된다.

 

JSON.parse는 예외처리를 하지 않거나 비동기 처리 하지 않으면 값이 넘어오기 전에 parse 메소드가 실행되어 에러가 발생할 수 있으므로 반드시 예외처리와 비동기 처리를 해주어야 한다.

 

통신 에러가 발생했을 때는 에러 메시지를 콘솔에 찍도록 해주었다.

 

바닐라스크립트로 작성된 것이며, HTML 요소를 셀렉터로 찾아 값을 넣어주게 하였다. 요청을 계속 보내서 갱신하려면 socket.sendsetInterval을 1000ms 단위로 걸어주면 된다.

 

// 웹소켓 생성
const socket = new WebSocket("wss://dummydata.or.kr");

// 커넥션이 제대로 생성되었을 때
socket.onopen = function (e) {
    let data = {
        action: "good",
        subscribe: "test",
    };
    setInterval(() => socket.send(JSON.stringify(data)), 1000)
};


let result;
let price;

// 데이터를 수신 받았을 때
socket.onmessage = async function (e) {
    try {
        if (e !== null && e !== undefined) {
            result = await JSON.parse(e.data);
            price = result[0];
            document.getElementById("test").innerHTML = `$ ${price}`
        }
    } catch (err) {
        console.log(err);
    }
};

// 에러가 발생했을 때
socket.onerror = function (e) {
    console.log(e);
};