네이버 스포츠 실시간 응원 클론하기 - 분석

최근 가을야구가 진행되고 있는 와중에 오랜만에 네이버에서 야구 중계를 봤는데요.
페이지 왼쪽 상단에 응원하는 팀을 선택하는 영역의 숫자가 실시간으로 바뀌는게 눈에 들어왔습니다.
갑자기 해당 기능이 어떻게 동작하는지 궁금해졌습니다. 쓸데없는 호기심 발동!

네이버 스포츠 응원팀 선택하기

네트워크 요청을 열어보자

그래서 자연스럽게 크롬 개발자 콘솔을 열어서 통신이 어떻게 진행되고 있는지 보았는데요.

엄청난 양의 네트워크 요청

그래서 저 데이터를 다 확인하기보다는 예상으로는 API로 데이터를 받아올거 같아서 Fetch/XHR(비동기 통신) 탭을 눌러서 데이터를 확인해보았습니다.

근데 이상한점이 있었던게 데이터가 주기적으로 변경되는거면 API 호출 기록이 지속적으로 추가 됐을거 같은데 추가되는 네트워크 요청은 영상 스트리밍 관련된 요청이었고, 그 전에 요청된 데이터에는 응원 갯수를 노출시켜줄만한 데이터는 안보였습니다.

그래서 저 데이터가 자바스크립트를 이용해 임의로 만든게 아닐까 하는 의심을 해보았습니다. 네이버가 그렇게 대충 구현하지는 않았을거 같지만 정확하게 데이터를 어떻게 가져오는지 모르는 상황이라 그런 생각이 들었습니다.

JS(JavaScript)탭을 눌러서 뭔가 저 기능에 영향이 있을법한 코드가 있는지 찾아보던 와중에

의심이 가는 부분을 찾았다

의심이 가는 이름이 있는 요청을 찾았습니다. 뭔가 sportsLike라는 이름이 저 기능과 어울리는거 같은데? 라는 생각이 들었씁니다.

두개의 요청중 처음은 인증을 위한 토큰을 받아오고 있었고, 두번째 요청이 제가 원하는 데이터랑 부합했습니다.

응원과 관련된 데이터!

제일 상단의 빨간의 contentsId는 해당 경기의 ID 같았습니다.

중계페이지 URL에도 있다!

중계 페이지 URL에도 contentsId와 동일한 값이 있었습니다.

그 다음 빨간박스의 reactions가 실제 응원 갯수를 초기화 해주는 데이터 같았습니다. 여러개의 필드가 있었지만 다른건 어떨때 사용되는지 정확히는 모르겠고, 우선 noLimitcount가 처음 설정될 응원 갯수고, userCount는 제가 누른 수를 의미하는거 같았습니다.
응원 버튼을 누를때마다 저 카운트가 올라가는것을 확인했습니다.

우선은 저렇게 데이터를 확인했는데 아직 해소가 되지 않은 의문이 있습니다. 바로 실시간으로 바뀌는 응원수는 어디서 오는것인가?였는데요 제가 모르는 다른 데이터가 있을거 같아서 네트워크 탭의 다른 항목들을 뒤쳐보기로 했습니다.

그중에 WS(웹소켓)탭에 갔는데 웹소켓 요청이 1개 있었습니다.

웹소켓 요청이 1개 있다?!

웹소켓 요청이 있었다는걸 알게된 순간 아! 웹소켓을 이용해 실시간 통신을 하고 있었구나!라고 생각했습니다.

웹소켓 통신 데이터 분석

요청 URL 확인해서 얻은 정보가 있는데 프로토콜은 wss로 https와 같이 인증서 처리가 된 프로토콜 입니다.
URL에 socket.io가 있는걸로 보아서 socket.io로 구현이 된거 같습니다. 그리고 쿼리스트링에 EIO=3, transport=websocket 두개의 데이터가 있는데 EIO=3은 Engine.io 버젼이 3이라는 뜻이고,
transport=websocket은 websocket으로 통신하겠다는 의미입니다. 참고

자세한 내용은 Websocket과 socket.io를 정리하면서 다시한번 다루겠습니다.

요청 URL로 정보를 얻었다!

아래 이미지가 실제로 서버랑 데이터를 주고받는 데이터인데요.
가볍게 한번 살펴보도록 하겠습니다.

스크린샷 2021-11-10 오후 8 26 14

실제로 데이터를 주고받는걸 보면 왼쪽의 빨간색 화살표는 서버 to 클라이언트, 파란색 화살표는 클라이언트 to 서버 데이터를 표현하는 것입니다.

데이터 패킷은 정해진 타입과 페이로드로 나누는데요

1
2
3
4
2["test",42]
||
|└─ JSON-encoded payload
└─ packet type (2 => EVENT)

패킷타입이 숫자로 되어있는데 1개나 2개냐에 따라 조금 다른데요
1개 일경우엔 Engine.io의 패킷타입이고, 2개일땐 앞자리는 Engine.io의 패킷타입, 뒷자리는 Socket.io의 패킷타입 입니다.

각 숫자가 의마하는 바가 다르니 알고 있으면 데이터 분석에 도움이 될거 같습니다.

  • Engine.io와 Socket.io의 차이에 대해서는 나중에 포스팅 하겠습니다.
데이터 설명
⬇️ 0{…} 0:open -> Engine.io 서버로부터 새로운 통신을 시작한다는 데이터를 받습니다.
⬇️ 40 4:message / 0:open -> namespace 연결 요청입니다.
⬆️ 42[“join”, {….}] 4:message / 2:event -> 서버 join 이벤트 발생.
⬇️ 42[“cheer”, {….}] 4:message / 2:cheer -> 클라이언트 cheer 이벤트 발생.
⬆️ 2 2:ping -> 서버로 ping
⬇️ 3 3:pong -> 서버로부터 pong

간단하게 설명했는데 자세한 내용은 공식 문서를 확인하시면 더 좋을거 같습니다.

네이버는 위와 같은 형태로 데이터를 주고받는데요. EIO가 3일때 해당하는 얘기고, EIO=4가 현재 최신 버젼인데 조금 차이가 있습니다.

우선 서버와 클라간 응답을 체크하는 2(ping), 3(pong)의 순서가
Engine.io v3일땐 클라(ping) -> 서버(pong)이었는데, Engine.io v4부터는 서버(ping) -> 클라(pong)으로 변경되었습니다. 참고

주기적으로 ping, pong을 하는 이유는 서버와 클라간에 통신이 지속적으로 가능한지 확인하는 목적인데 클라이언트에서 ping을 요청하는 주기에 대한 신뢰성이 떨어진다고 합니다. timeout의 발생원인이 클라이언트 측에서의 타이머 지연이라고 판단하는거 같습니다.

  • ping을 날리는 주기와 타임아웃시간은 맨처음 통신을 맺을때 데이터에 포함되어있습니다.(pingInterval, pingTimeout)

네이버에서 구현할땐 Engine.io v3를 사용하였고, 업데이트를 진행한거 같지 않지만 Engine.io v3와 Engine.io v4의 기능이나 성능적인 차이가 크게 없는거 같아서 큰 문제는 없을거 같습니다.

  • Engine.io버젼과 Socket.io의 버젼은 별개입니다.

분석한 내용 정리

지금까지 제가 분석한 내용을 도식화 하면 아래와 같은데요
JS탭에 있었던 첫번째 요청과 관련된 내용(인증토큰 처리)은 제외했습니다.

그리고 웹소켓 연결 요청은 점선으로 되어있는데요. socket.io의 기본적인 연결은 Long Polling으로 부터 시작합니다. Long Polling을 이용해 서버와 websocket 통신이 가능한지 먼저 체크를 하고, 통신이 가능하면 웹소켓으로 통신 방식을 업그레이드 합니다.

그렇게 될경우 네트워크 요청 목록에 아래와 같이 5개의 요청이 발생하게 됩니다.

일반적인 socket.io 연결 요청 목록

하지만 네이버 스포츠 페이지에서 본 요청 목록엔 websocket연결 뿐이었는데요 (4번째 요청),
클라이언트 쪽에서 소켓 요청을 할때 option값 중에 transports를 websocket으로만 설정하면
Long Polling을 이용한 연결을 비활성화 합니다. 그래서 1개의 요청만 남아있는것입니다.

다만 Long Polling을 비활성화는 이유에 대해서 정확하게 이해는 못했는데요, 공식 문서를 보았을때는 여러 Socket.io 서버를 구성해서 사용할때 여러개의 Long Polling요청이 각기 다른 서버로 전송될 경우 세션ID (sid)를 식별하지 못하는 문제가 발생을 하게됩니다. 그래서 서버와 클라이언트간 1:1로 연결(고정세션)을 시키기 위해 Long Polling을 이용하지 않고 websocket을 이용해 연결을 시도하는것이고, 어떤 서버에 연결될지는 로드밸런싱 솔루션을 이용해 처리를 해야한다고 하는거 같았습니다.

응원하기

마지막으로 제가 응원하는 팀을 클릭했을때도 JSONP를 이용해 데이터를 보내고 callback을 통해 데이터를 받아와 처리하는것 같이 보였습니다.
팀 응원 - 요청 값

요청값 중에 pool이 nolimit인걸 보아하니 투표수에 제한이 없는거 같고, 응답 필드의 nolimitUserCount가 3으로 제가 지금까지 눌렀던 횟수의 총합이 응답으로 오는거 같았습니다.

팀 응원 - 응답 값

투표는 로그인을 해야 진행이 되기때문에 로그인한 유저의 정보를 이용해 투표한 기록을 가지고 있는거 같았습니다.

궁금증

이 글을 적으면서도 이해가 안되는 부분은 왜 JS로 데이터 초기값을 요청했을까? 라는 부분 입니다.

뭔가 응답값을 보았을때 callback으로 감싸져서 오는걸 보아하니 JSONP로 요청을 한거 같은데. 제가 알기론 JSONP는 CORS문제를 해결하기위해 사용한다 인데요. JSONP로 요청하지 않고 서버에서 Response Header에 Access-Control-Allow-Headers를 설정하거나, 서브도메인은 naver.com으로 같으므로 document.domain을 이용한 처리도 가능하지 않았을까 생각이 됩니다. (document.domain은 더이상 사용하지 않는게 좋다 라고 하네요. 참고

아니면 네이버 스포츠를 개발하는 팀과, socket.io 서버를 개발하는 팀이 달라서 뭔가 socket.io 개발팀에서 JSONP를 이용해 도메인이 다른 팀에서도 사용해서 처리할 수 있게끔 callback으로 감싸서 내려주는 공통화 처리를 해둔게 아닐까 라는 생각도 해보았습니다. (응답의 구조를 보아하니 스포츠 경기 뿐만 아니라, 다른 곳에서도 사용이 가능한 공통 응원(투표) 시스템이지 않을까 라는 추측…)

다음 포스팅은 요구사항을 리스트업 하고, 설계를 해보는 작업을 해보도록 하겠습니다.

공유하기