소켓오류. 도대체 토스 서버에서는 무슨 일이 일어나는 걸까? 토스 개발자 커뮤니티 무용론?

#문제 요약 : 현재 토스 서버에서만(!) socket.io 무응답 오류 발생.

  • 모든 환경에서 웹의 기능들은 잘 되지만, 토스 서버에만 제출하면 socket.io 관련 기능만 안됨.

- 내 맥북에서 서버를 돌리고 웹이나 실기기의 토스 미니 앱으로 접속하면 다 잘 됨.

- 웹 서버에서 배포한 경우도 잘 되고, 앱(WebView)으로 패키징하여 접속해도 다 잘 됨.

- 모든 코드는 동일. 오직 토스 서버 환경에 업로드하여 배포한 경우에만 socket.io 가 무응답하는 상황. (도대체 토스 서버에서는 무슨 일이 일어나는 걸까?)

실기기의 샌드박스 앱(미니앱)에서는 모든 기능이 잘 되는데, 토스 앱에서는 socket.io 서버 기능만 안 됩니다. 심지어 토스 앱으로부터 아무런 오류 로그도 빼 올 수 없는 상황입니다.

샌드박스 앱 vs 토스 앱의 어떤 차이가 socket.io 서버 기능을 on/off하는 걸까요?

샌드박스 앱과 토스 앱 환경이 일치하게 해주시던지, 아니면 토스 앱에서만 발생하는 오류 로그를 확인하는 방법을 알려주시면 좋겠습니다.

공모전 마감이 눈 앞인데, 토스 개발자 커뮤니티에 올려봤자 몇주째 이런 중요한 문제에 아무런 피드백도 없네요… 답하기 쉬운 간단한 문제만 답해주는 건가요? 여기 몇가지 문의 글 올려보고 답이 없어서 이제는 별 기대를 하지 않게 되었지만 그냥 기록으로 남겨봅니다.

appName 한번 공유주시겠어요?

따로 저희 측에 오류 잡힌건 없어보이고, 오류 디버깅을 하기 위해 web에 직접 sentry 설치하셔서 디버깅이 가능하실 거 같습니다.

추가로 더 파악하시고 싶은 것이 있으시면 채널톡으로 자세한 정보 남겨주시면 좀 더 확인해볼게요.

작성한 대로, 일반 웹과 구글 플레이스토어 에서는 모든 기능이 순조롭게 잘 작동하기에 오류 메세지가 존재하지 않고, 토스에 제출했더니 오류가 발생한다고 심사 거부사유에 적혀있는데 정작 저는 토스서버의 백엔드 로그를 확인할 길이 없으니 오류가 발생했다는 주장만 볼 수 있고, 제 웹사이트에서는 아무 오류 없이 너무 잘 작동하고 있음에도 심사는 통과 안되는 상황입니다. 저와 친구들은 다 잘 플레이하고 있는데 도대체 왜 토스 서버에서는 안되는 건지ㅠ 공모전 마감일은 다가오고 답답하네요…

안녕하세요. 토스 임직원입니다.

업로드해주신 번들 확인해보니, 소켓 connect시에 아무 설정도 안되어 있는 것으로 보이고, 따라서 라이브러리 기본값이 사용되고 있네요. 기본값은 window.location.origin + “/socket.io” 입니다.

로컬에서는 웹 서버와 소켓 서버 도메인이 똑같이 localhost이기에 잘 동작할 수 있지만, 토스앱 환경에서는 웹 도메인이 달라지기 때문에 배포된 소켓 서버의 URL로 잘 설정 부탁드려요.

토스앱의 웹 도메인은 여기서 확인하실 수 있어요. 참고하셔서 서버의 CORS 설정도 부탁드립니다.

문제 해결에 도움이 되시면 좋겠습니다.

3개의 좋아요

다른 문제인 것 같습니다. 로컬에서만 잘 되는게 아니라 웹서버로 배포후에도 잘 됩니다. CORS origin도 * 으로 열어뒀기에 토스가 다른 웹서버와는 다른 문제를 가지고 있는것 같습니다. socket.io 뿐만 아니라, 토스에서는 기존 웹서버에서 잘 되던 db 연결조차 안되고 있습니다.

안녕하세요, 올려주신 번들 보면 wss:// 나 https:// 로 나가는 호스트가 없는데 혹시 어떤 호스트를 등록해주셨을까요?

“*“ 를 사용하여 모든 도메인을 열어두면 안되나요? 기존 웹서비스는 그렇게 돌아가고 있습니다.

“*“ 를 사용하여 모든 도메인을 열어두면 안되나요? 기존 웹서비스는 그렇게 돌아가고 있습니다

미니앱 클라이언트 → 소켓 통신 서버와 연결하기 위해 어떤 서버인지 host를 알아야 연결할텐데 컴파일된 미니앱 코드를 보면 그 정보가 없습니다(e.g https://example.com/wss, wss://example.com) .

socketio를 활용한 socket 통신 예제:

const manager = new Manager("ws://example.com", {
  reconnectionDelayMax: 10000,
  query: {
    "my-key": "my-value"
  }
});

const socket = manager.socket("/my-namespace", {
  auth: {
    token: "123"
  }
});


import { Manager } from "socket.io-client";

const manager = new Manager("https://example.com");

const socket = manager.socket("/"); // main namespace
const adminSocket = manager.socket("/admin"); // admin namespace

기존 웹 서비스가 어떻게 작동하고 있는지를 설명해야 할 것 같군요.

현재 소켓 서버 주소는 항상 코드에 문자열로 하드코딩되는 게 아니라, 앱이 실행 중인 origin을 기준으로 자동 결정됩니다.
그래서 컴파일된 코드에 https://example.com이 보이지 않고 서비스하는 서버의 document origin 을 기준으로 연결되어 서비스 되고 있습니다.

:one: 서버 코드 (Node.js)

import { Server } from "socket.io";

const io = new Server(ENV_PORT);

io.on("connection", (socket) => {
  console.log("connected:", socket.id);

  socket.on("ping", () => {
    socket.emit("pong");
  });
});

:two: 클라이언트 코드 (웹)

import { io } from "socket.io-client";

const socket = io(); // 👈 host를 명시하지 않음

socket.on("connect", () => {
  console.log("connected:", socket.id);
});

socket.emit("ping");

socket.on("pong", () => {
  console.log("pong received");
});

필요하시면, 먼저 현재 정상적으로 잘 서비스 되고 있는 코드라는 점부터 더 설명드리겠습니다.

저희가 무슨 말씀하시는지 정확히 이해를 못했는데, 혹시 플레이스토어에 올라간 앱을 공유주실 수 있을까요?

네 지금 개인톡으로 전달드리겠습니다

여기서 io()로 하면 localhost로 열릴텐데, 어떤 서버로 요청할지 클라이언트가 어떻게 식별할 수 있나요?

배포된 앱인토스 미니앱은 로컬 개발 환경이 아닌 점 참고 부탁드려요.

io() 의 기본값에 대해서 오해를 하셔서 현재 서비스 되고 있는 상황을 이해하지 못하신 것 같습니다.

io() 기본값은 로컬호스트가 아니라 해당 서버의 주소입니다.

:one: io()의 기본 연결 대상은 현재 페이지의 origin입니다

io();

이 호출은 내부적으로 다음 값을 사용합니다:

window.location.origin

즉,

  • 로컬 개발 환경에서는
    http://localhost:5173

  • 실제 배포된 앱에서는
    https://mydomain.com

이 자동으로 소켓 서버 주소로 사용됩니다.


:two: 배포된 앱에서는 localhost로 요청이 나갈 수 없습니다

배포된 웹앱이 다음 주소에서 실행 중이라면:

https://mydomain.com

실제 소켓 연결은 다음과 같이 이루어집니다:

io()
 ↓
https://mydomain.com
 ↓
HTTPS 환경 → 자동으로 WSS 선택
 ↓
wss://mydomain.com/socket.io

:backhand_index_pointing_right: 클라이언트가 임의로 localhost를 선택할 수 있는 구조가 아닙니다.

———————————————————————————————————————————————

여기서 코드에 mydomain이 없는데 어떻게 mydomain에 연결이 되는지 설명해주실 수 있을까요?

미니앱 환경은 귀사에서 운영하는 서버와 클라이언트가 한 위치에 있는 것이 아닌 토스에서 호스팅하는 환경에서 클라이언트가 별도로 동작합니다. 즉 서버의 호스팅 위치와 클라이언트의 호스팅 위치가 다르므로 제가 말씀드린 것처럼 명시적으로 서버 주소를 io의 인자값으로 넣어주셔야 정상적으로 동작할거 같아요.

1. 그건 토스만의 환경이 아닙니다. 저도 웹서버는 클라우드로 호스팅 하고 db도 클라우드 서비스로 따로 사용하고 있습니다. 그런데도 개인톡으로 보내드렸듯이, 구글플레이에서도 잘 서비스되고 있습니다.
2. io 관련 공식문서를 필독하셔야 할 것 같습니다.

io() 의 기본값에 대해서 오해를 하셔서 현재 서비스 되고 있는 상황을 이해하지 못하신 것 같습니다.

io() 기본값은 로컬호스트가 아니라 해당 서버의 주소입니다.

공식 문서를 참고해주세요 : Client API | Socket.IO

:one: io()의 기본 연결 대상은 현재 페이지의 origin입니다 (공식 문서상의 작동원리니깐 외우시면 됩니다.)

io();

이 호출은 내부적으로 다음 값을 사용합니다:

window.location.origin

즉,

  • 로컬 개발 환경에서는
    http://localhost:5173

  • 실제 배포된 앱에서는
    https://mydomain.com

이 자동으로 소켓 서버 주소로 사용됩니다.


실제 토스에 배포된 앱은 아래 주소로 배포되고, window.location.origin은 아래와 같은 형식을 따릅니다.

이때 해당 도메인은 static resource만 전달하는 클라이언트 주소이며, 말씀 주신대로 url 인자 없이 io 객체를 만드는 경우 window.location.origin으로 처리되므로 socket connection이 정상적으로 처리되지 않습니다.

기존에 안드로이드 앱으로 배포하신 경우 mydomain.com 주소로 server와 client를 self serving하시는 구조였기 때문에 제어권이 귀사에 있다면 말씀주신대로 mydomain.com에서 클라이언트와 서버가 둘다 접속 가능하므로 성공적으로 동작할 수도 있지만, 미니앱의 경우 토스 미니앱 환경의 주소 체계를 따르는 분리된 구조이므로 제가 말씀드린 것처럼 io의 인자에 연결하는 서버 주소를 포함하셔야 하는 것이 맞습니다.

여러번 말씀드린 사항이고, 시도해보시고 그래도 안되면 말씀 부탁드려요.

감사합니다.

4개의 좋아요

해결되었습니다!ㅠ 감사합니다!

1개의 좋아요

고생많으셨습니다 :folded_hands: