Javascript/Nest JS

[Chat App] NextJS로 알아보는 Socket.io

  • -
728x90

https://wonillism.tistory.com/321

 

Web Socket 그리고 Socket.io

이전에 express와 js를 이용하여 간단한 chat app을 만들어 본적이 있는데, 이번에는 nextjs를 이용해서 chat app을 만들어보려고 한다. https://wonillism.tistory.com/246 socket.io 기본 채팅 서비스 만들기 (1) https:

wonillism.tistory.com

WebSocket에 대해 간단히 정리한 글이다.

 

 

Socket.io의 공식문서를 확인하면서 Chat App 프로젝트에 연결해보자.

https://socket.io/docs/v4/server-api/

 

Server API | Socket.IO

Server

socket.io

 

기본 코드

서버에서의 기본 코드

import { Server } from "socket.io";

// socket 서버 생성
const io = new Server(3000, {
  // options...
});


// connection 연결 이벤트
io.on("connection", (socket) => {
  // 클라이언트 접속

  // 클라이언트로부터 메세지 받기
  socket.on("hello from client", (...args) => {
    console.log("클라이언트로부터 메세지 받음", args);
  });
});

 

클라이언트에서의 기본 코드이다. (socket.io-client 사용)

const socket =io();
socket.on('connect', ()=>{
    console.log('서버와 연결');
});
socket.on('message', function(data) {
     const element = document.getElementById('content');
     const br = document.createElement('br');
     const content = document.createTextNode(data);

    element.appendChild(content);
    element.appendChild(br);
});

 

 

socket.io를 사용하여 NextJS app에 적용하기 전에 우선 NextJS 13에서의 경로를 처리하는 방법부터 알아보자.

https://nextjs.org/docs/app/building-your-application/routing/router-handlers

 

Routing: Route Handlers | Next.js

Using App Router Features available in /app

nextjs.org

 

Route Handler

NextJS 13버전의 app routing 방식이 있기 전의 API 디렉토리(pages/api)는 NextJS로 API를 구축하기 위한 솔루션을 제공한다.

 

app routing 방식부터는 app디렉토리 안의 route.js/ts 파일에서 정의된다. Route Handler는 page.js나 layout.js파일처럼 app 디렉토리 내부에서 중첩되어 사용할 수 있지만, page.js와 동일한 세그먼트 수준에는 Route Handler 파일인 route.js 파일이 있을 수 없다.

 

즉, api 서버측 경로와 클라이언트측 경로를 구분해서 사용하라는 뜻인것 같다. 굳이 api 디렉토리를 안만들어도 되지만, api 디렉토리 내부에서 사용하는게 좋다는 말 같다.

 

Supported HTTP Methods

Route Handler안에서는 HTTP 메서드를 지원하는데, 지원되는 메서드는 GET, POST, PUT, PATCH, DELETE, HEAD 및 OPTIONS 이다. 지원되지 않는 메서드가 호출되면 NextJS 405(Method Not Allowed) 응답을 반환한다.

 

확장된 NextRequest와 NextResponse API

기본 요청 및 응답을 지원하는 것 외에도, NextJS는 고급 사례를 돕기 위한 NextRequest와 NextResponse를 제공한다.

 

확장된 기능들은 공식문서를 참고하자.

https://nextjs.org/docs/app/api-reference/functions/next-request

 

Functions: NextRequest | Next.js

Using App Router Features available in /app

nextjs.org

https://nextjs.org/docs/app/api-reference/functions/next-response

 

Functions: NextResponse | Next.js

Using App Router Features available in /app

nextjs.org

 

 

app/api/user/route.ts

import { NextResponse } from "next/server";

export const GET = () => {
  return NextResponse.json({ id: 0, name: "wonil" });
};

 

위와같이 route.ts 파일을 만들어주고 curl로 요청을 보내보면 다음과 같이 응답을 받을 수 있다.

 

간단한 CRUD를 만들어보면 다음과 같다.

import { NextRequest, NextResponse } from "next/server";

let arr: { id: number; name: string }[] = [{ id: 0, name: "wonil" }];

export const GET = () => {
  return NextResponse.json(arr);
};

export const POST = async (req: NextRequest) => {
  const body = await req.json();
  arr.push(body);
  return NextResponse.json(arr);
};

export const PUT = async (req: NextRequest) => {
  const body = await req.json();
  console.log("PUT!!!!!");

  arr = [...arr].map((user) =>
    user.id === body.id ? { ...user, name: body.name } : user
  );
  return NextResponse.json(arr);
};

export const DELETE = async (req: NextRequest) => {
  const body = await req.json();
  arr = arr.filter((el) => el.id !== body.id);
  return NextResponse.json(arr);
};

 

Socket.io 적용해보기

app router를 이용하여 만들던중 socket서버를 연결하는 과정에서 난관에 부딪혀버렸다.

api/socket/io 경로에서 소켓을 요청을 하도록 구상중이었는데, 개념상 서버위에 socket.io 서버를 올리는식으로 만들어야한다.

 

https://nextjs.org/docs/app/api-reference/file-conventions/route

 

File Conventions: route.js | Next.js

Route Handlers allow you to create custom request handlers for a given route using the Web Request and Response APIs. A route file allows you to create custom request handlers for a given route. The following HTTP methods are supported: GET, POST, PUT, PAT

nextjs.org

route.js 의 컨벤션을 확인해보면 아래 코드와 같다.

export async function GET(request: Request) {}
 
export async function HEAD(request: Request) {}
 
export async function POST(request: Request) {}
 
export async function PUT(request: Request) {}
 
export async function DELETE(request: Request) {}
 
export async function PATCH(request: Request) {}
 
// If `OPTIONS` is not defined, Next.js will automatically implement `OPTIONS` and  set the appropriate Response `Allow` header depending on the other methods defined in the route handler.
export async function OPTIONS(request: Request) {}

매개변수는 아래와 같다.

export async function GET(request, context: { params }) {
  const team = params.team // '1'
}

context는 선택적으로 사용할수 있고, request는 일반 Request 타입이나 Next에서 제공하는 NextRequest타입이 가능하며, 응답으로는 Response타입이나 NextResponse 타입이 가능하다.

 

공식문서 어디를 봐도 socket 서버를 띄울수 있는 컨벤션은 없는듯하다 ㅠㅜ..

 

그래서 nextJS github의 issue를 뒤져보았다.

https://github.com/vercel/next.js/issues/49334

 

[NEXT-1119] Socket.IO Not working anymore from Next.js 13.2.5-canary.27 to latest Next.js 13.4.1 · Issue #49334 · vercel/next.

Verify canary release I verified that the issue exists in the latest Next.js canary release Provide environment information Operating System: Platform: darwin Arch: arm64 Version: Darwin Kernel Ver...

github.com

아직 해결되지 않은 이슈 .... 제목 자체는 13.2.5 부터 13.4.1 까지 버전은 socket이 작동하지 않는다는 것 같다. 내가 찾는 문제와는 조금 거리가 있어보이지만 ... 어쨋든 만들어도 작동을 안한다는거 아닌가 .. 지금 내가 쓰고있는 버전도 13.4.12로 아마 아직까지는 해결안된거 같다... 

 

쭈욱 읽어보면, app routing 방식에서는 해결할 수 있는 방법이 없는듯해보이고, 예전 방식인 page routing 방식으로 해결한 사람이 있긴하나, vercel로 배포했을때는 여전히 작동하지 않는듯하다.

 

해결한 사람의 코드를 분석해보자.

 

https://github.com/danya0365/nextjs13-realtime-chat-with-socketio

 

GitHub - danya0365/nextjs13-realtime-chat-with-socketio

Contribute to danya0365/nextjs13-realtime-chat-with-socketio development by creating an account on GitHub.

github.com

이 사람의 코드인데, 예전방식도 사실 잘 모르는 터라 ... 이해가는데까지 분석해보자.

 

types/next.ts

import { Server as NetServer, Socket } from "net";
import { NextApiResponse } from "next";
import { Server as SocketIOServer } from "socket.io";

export type NextApiResponseServerIO = NextApiResponse & {
  socket: Socket & {
    server: NetServer & {
      io: SocketIOServer;
    };
  };
};

page routing 방식에서 존재하던 NextApiResponse를 socket 서버를 위해 확장해서 사용해야하는 것 같다. 

app routing에서는 route.ts 파일이 api path를 처리하는 듯하고, page routing에서는 api 경로를 따로 사용해야하는것 같다.

https://nextjs.org/docs/pages/building-your-application/routing/api-routes

 

Routing: API Routes | Next.js

Using Pages Router Features available in /pages

nextjs.org

 

Page라우팅을 사용한 소켓

pages/api/socket/io.ts

import { NextApiResponseServerIO } from "@/types/next";
import { Server as NetServer } from "http";
import { NextApiRequest } from "next";
import { Server as ServerIO } from "socket.io";

export const config = {
  api: {
    bodyParser: false,
  },
};

const io = async (req: NextApiRequest, res: NextApiResponseServerIO) => {
  if (!res.socket.server.io) {
    const path = "/api/socket/io";
    console.log(`New Socket.io server... to ${path}`);
    // adapt Next's net Server to http Server
    const httpServer: NetServer = res.socket.server as any;
    const io = new ServerIO(httpServer, {
      path: path,
      // @ts-ignore
      addTrailingSlash: false,
    });
    // append SocketIO server to Next.js socket server response
    res.socket.server.io = io;
  }
  res.end();
};

export default io;

api/socket/io 경로에 요청을 하게되면 io Server가 없을 때 httpServer를 이용하여 io 서버를 만들어주는 코드 같다.

addTrailingSlash는 io Server의 option값에 없는듯하다.. 계속 타입스크립트 에러가나서 ignore해주었다.

 

pages/api/socket/chat.ts

import { NextApiResponseServerIO } from "@/types/next";
import { NextApiRequest } from "next";

const chat = async (req: NextApiRequest, res: NextApiResponseServerIO) => {
  if (req.method === "POST") {
    // get message
    const message = req.body;

    // dispatch to channel "message"
    res?.socket?.server?.io?.emit("message", message);

    // return message
    res.status(201).json(message);
  }
};

export default chat;

https://socket.io/docs/v4/

 

Introduction | Socket.IO

What Socket.IO is

socket.io

socket io의 공식문서와 함께 보면, 클라이언트 측에서 메세지를 생성해달라는 POST요청을 보내고, 서버에서 그 요청을 받아서 "message"라는 io 채널에 message를 dispatch한 후 클라이언트로 생성되었다(201)이라는 http code와 massage값을 보내주어서 클라이언트 측에서는 message를 받아 그 메세지를 화면에 띄우는 형태인듯하다.

 

page라우팅을 이용해 만든 사람의 코드를 그대로 클론하여 로컬에서 작동시켜보면 아래와 같이 채팅 앱이 잘 작동한다.

 

이걸 또 vercel로 배포해봤는데 socket 서버에 계속 연결됐다가 끊겼다가 반복하며 제대로 작동하지 않았다 ... 

 

https://github.com/vercel/next.js/issues/49334#issuecomment-1645330676

 

[NEXT-1119] Socket.IO Not working anymore from Next.js 13.2.5-canary.27 to latest Next.js 13.4.1 · Issue #49334 · vercel/next.

Verify canary release I verified that the issue exists in the latest Next.js canary release Provide environment information Operating System: Platform: darwin Arch: arm64 Version: Darwin Kernel Ver...

github.com

해당 이슈에 이런 글이 있는데 header 설정이 중요하다고 하고... 환경 변수의 올바른 설정 및 CSP설정을 제대로 구성해야된다고하고  AWS나 Heroku환경에서만 테스트해봤고 Vercel에서는 테스트 하지 않았다고한다.... 너무 어렵다 ㅠㅠ

 

728x90
300x250
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.