인프런 커뮤니티 질문&답변

백형준님의 프로필 이미지
백형준

작성한 질문수

Slack 클론 코딩[백엔드 with NestJS + TypeORM]

socket io 미 연결 문제 (nest & flutter)

해결된 질문

작성

·

632

1

안녕하세요! socket관련한 서비스를 진행해보고 싶어서 제로초님의 강의를 들은 수강생입니다.

현재 nest & flutter를 이용하고 있는데, flutter에서의 연결 및 다른 tool에서 socket io 연결이 되지 않으며

“외부 사이트로는 접근이 불가능한 오류”가 생겨서 조심스럽게 여쭤봅니다.

 

현재 로직은 채팅을 생성시, 채팅을 보여주는 리스트가 실시간으로 새로고침이 되는 부분을 작업중입니다.

하지만, postman, httpie, hoppscotch의 부분에서 연결이 되지 않는 문제가 발생합니다.

많은 방법을 찾아봤지만, 터미널에서 socket io cli를 통해서 로그는 볼 수 있지만, 다른 tool에서는 이용이 불가능한 방법에 대해서 알고 싶어서 질문드립니다!Socket io를 통해서 local, dev서버 연결 완료

 

하지만 postman의 socket io기능을 통해서 테스트를 진행하려고 할 때,

postman으로 연결local에서는 문제가 없이 연길이 되지만,

 

dev서버에서는 이러한 에러가 발생합니다.

 

또한 flutter 앱에서 연결을 하려면 다음과 같은 에러가 발생합니다.

오류 메시지 "WebSocketException: Connection to 'http://~~~~.com:81/socket.io/?EIO=4&transport=websocket#' was not upgraded to websocket"는 클라이언트가 WebSocket 연결을 시도하였으나, 서버가 해당 연결을 WebSocket 프로토콜로 업그레이드하지 않았다는 것을 의미합니다. 이는 여러 가지 원인에 의해 발생할 수 있습니다:

 

upgrade가 되지 않았다고 나와서 ,ngnix의 socket 부분에서 upgrade부분도 잘 넣어줬는데, 오류가 해결되지 않아서... 고민 끝에 질문 올립니다.

Ngnix 설정부터 2주정도 시간을 들였지만, 해결이 되지 않아서…여쭤봅니다.

방화벽도 해제가 되어 있는데 연결이 안되고 있습니다..

다음은 nest에서 작성한 코드 입니다!

[chat.gateway.ts]

import {
  WebSocketGateway,
  WebSocketServer,
  SubscribeMessage,
  OnGatewayConnection,
  OnGatewayDisconnect,
  OnGatewayInit,
  ConnectedSocket,
  MessageBody,
} from '@nestjs/websockets';
import { Server, Socket } from 'socket.io';

@WebSocketGateway()
export class ChatGateway implements OnGatewayInit, OnGatewayConnection, OnGatewayDisconnect {
  @WebSocketServer() server: Server;

  afterInit(server: Server) {
    console.log('WebSocket initialized');
  }

  handleConnection(client: Socket) {
    console.log(`Client connected: ${client.id}`); // 수정: client 객체 직접 출력 대신 id 출력
  }

  handleDisconnect(client: Socket) {
    console.log(`Client disconnected: ${client.id}`);
  }

  @SubscribeMessage('sendMessage')
  handleMessage(
    @ConnectedSocket() client: Socket,
    @MessageBody() data: { message: string }
  ): void {
    console.log(`Received message from ${client.id}: ${data.message}`);
    this.server.emit('newMessage', data); // 모든 클라이언트에게 메시지 전송
    console.log(`Received message: ${data.message}`);
  }
}

[main.ts]

import { ConfigService } from '@nestjs/config';
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { join } from 'path';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './common/exceptions/http-exception.filter';
import { SuccessInterceptor } from './common/interceptors/success.interceptor';
import { IoAdapter } from '@nestjs/platform-socket.io';
import { CustomIoAdapter } from './adapters/custom-io.adapter';

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule);
  app.useWebSocketAdapter(new CustomIoAdapter(app));
  const configService = app.get(ConfigService);
  const port = configService.get('server.port');
  const mongoUrl = configService.get('DB.MONGO_URL');

  console.log('MongoDB URL:', mongoUrl);

  app.enableCors({
    origin: true,
    credentials: true,
  });

  app.useStaticAssets(join(__dirname, '..', 'client'), {
    prefix: '/api/v1/client',
  });

  app.useGlobalInterceptors(new SuccessInterceptor());
  app.useGlobalFilters(new HttpExceptionFilter());
  app.setGlobalPrefix('api/v1');

  const swagger_options = new DocumentBuilder()
    .setTitle('Nyam-Docs')
    .setDescription('API description')
    .setVersion('2.0.1')
    .addApiKey(
      {
        type: 'apiKey',
        name: 'x-token',
        in: 'header',
        description: 'Enter token',
      },
      'x-token',
    )
    .addApiKey(
      {
        type: 'apiKey',
        name: 'x-type',
        in: 'header',
        description: 'Enter type',
      },
      'x-type',
    )
    .build();
  const document = SwaggerModule.createDocument(app, swagger_options);
  SwaggerModule.setup('api-docs', app, document);

  await app.listen(port, '0.0.0.0');
  console.log(`Application Listening on Port : ${port}`);
}
bootstrap();

다음은 custom한 io입니다

[custom.io.adpter.ts]

import { IoAdapter } from '@nestjs/platform-socket.io';
import { INestApplication, Injectable } from '@nestjs/common';
import { ServerOptions } from 'socket.io';

@Injectable()
export class CustomIoAdapter extends IoAdapter {
    constructor(app: INestApplication) {
        super(app);
    }

    createIOServer(port: number, options?: ServerOptions): any {
        const serverOptions: ServerOptions = {
            ...options,
            cors: {
                origin: '*',  // 모든 도메인에서 접근 허용
                methods: ['GET', 'POST', 'PUT', 'DELETE'],
                credentials: true
            },
            transports: ['websocket', 'polling'], //pooling 없으면 연결 안 됨(socket)
            allowEIO3: true  // Engine.IO 3.x 버전 클라이언트 허용
        };
        return super.createIOServer(port, serverOptions);
    }
}

 

혹시 해결방법을 아시거나, 도움을 주실만한 정보가 있으시다면 알려주시면 정말 감사하겠습니다!

답변 3

1

백형준님의 프로필 이미지
백형준
질문자

안녕하세요. 위와 같이 다른 "외부 사이트에서 연결이 불가능한 에러"를 해결한 방법을 공유합니다!

flutter 오류를 중점으로 봤습니다.

"WebSocketException: Connection to 'http://~~~~.com:81/socket.io/?EIO=4&transport=websocket#' was not upgraded to websocket"

이러한 에러가 발생한 것을 보고, url 뒤에 socket.io라는 값이 추가되는 것이 보였습니다.

socket.io에서 자동으로 추가하는 부분이었습니다.

이를 바탕으로 ngnix의 location 뒤에 socket.io를 추가하였습니다.

image

이렇게 진행을 하고 test 및 reload를 하였더니 문제가 해결되었습니다.

2~3주 동안 해결을 하지 못한 에러였는데, 제로초님에게 질문도 하면서 정리를 하다보니 해결을 한 것 같습니다!

정말 감사합니다:)

1

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

nginx location / { 블록에는 Connection 'upgrade' 가 없는데요? ws 연결을 맺는 주소가 /이니까 이 블록을 보셔야 합니다.

백형준님의 프로필 이미지
백형준
질문자

감사합니다!!

1

안녕하세요, 인프런 AI 인턴입니다. 백형준 님의 질문에 대해 다소간 도움이 될 수 있는 정보를 알려드릴 수 있을 것 같습니다.

백형준 님께서 겪고 계신 Socket.io 연결 문제는 여러 개의 상이한 원인으로 인해 발생할 수 있기 때문에, 여러 방면에서 접근해보시는 것이 좋습니다. 특히, NGINX 설정과 관련하여 WebSocket의 연결 업그레이드 문제, Flutter 앱과의 소켓 연결 문제 등 다양한 요소가 복합적으로 작용할 수 있습니다.

socket.io 사용 시 cors 에러 with react에서의 질문과 유사하게, WebSocket 연결 시 CORS 문제는 소켓 연결시에 흔하게 발생할 수 있습니다. 이 경우, NestJS 백엔드 서버Flutter 클라이언트 사이드 모두에서 적절한 CORS 설정을 확인하고, NGINX 설정에서 WebSocket 연결에 필요한 헤더들을 적절히 설정해 주어야 합니다.

특히, WebSocket 연결에서는 Upgrade 헤더와 Connection 헤더가 중요한 역할을 합니다. NGINX 설정에서 이 두 헤더의 올바른 처리를 확인하세요. 위에 첨부해주신 NGINX 설정 스크린샷에는 이에 관한 정보가 일부 포함되어 있으나, 실제로 WebSocket 연결을 위해서는 이 헤더들이 올바르게 설정되도록 NGINX를 구성해야 합니다.

이외에도, Flutter에서의 연결 문제는 클라이언트 측에서의 socket.io 버전과 서버 측에서의 socket.io 버전 간의 호환성 문제일 수도 있습니다. 클라이언트와 서버 양쪽에서 사용되는 socket.io의 버전이 호환되는지 확인해보세요.

NestJS에서의 WebSocketGatewayCustomIoAdapter 설정도 적절한 것으로 보이나, 실제로 운영하는 환경에서 포트(예: 3003, 81 등) 및 Endpoint 설정에 주의를 기울여야 합니다. main.ts에서의 app.listen() 부분에서는 포트 번호를 올바르게 설정하고 있는지, NGINX에서 해당 포트로의 프록시 패스가 올바르게 설정되어 있는지를 꼼꼼히 확인해야 합니다.

추가적으로, WebSocket 통신을 위한 포트가 방화벽에서 허용됐는지도 확인해 보시는 것이 좋습니다. 특히, cloud 서비스를 사용하시거나 외부 네트워크를 통한 접속을 시도할 경우 해당 서비스의 보안 그룹이나 네트워크 정책에서 WebSocket 통신을 위한 포트가 오픈되어 있는지 검토할 필요가 있습니다.

저는 질문자님의 이해를 돕고자 지식공유자님의 기존 답변을 참고하여 유사한 답변 링크를 추천해드리고 있습니다.
현재 베타 기능으로 답변이 만족스럽지 않을 수 있는 점 양해 부탁드립니다. 🙏
추가적으로 궁금한 점이 있으시면, 이어서 질문해 주세요. 곧 지식공유자께서 답변해 주실 것입니다.

백형준님의 프로필 이미지
백형준

작성한 질문수

질문하기