Next.js 14

[ Next.js 14 ] Next.js SSE 실시간 알림 구현, Nginx 504 (Gateway Timeout) Error

ToMakeSure 2024. 12. 11. 16:43
반응형

클라이언트

  useEffect(() => {
    let eventSource: EventSource | null = null;

    const connect = () => {
      // 기존 연결 닫기
      if (eventSource) {
        eventSource.close();
        eventSource = null;
      }

      // SSE 연결 설정
      eventSource = new EventSource(
        `/api/notification?userPkey=${userInfo.pkey}&userLevel=${userInfo.level}`
      );

      // 메시지 수신 이벤트
      eventSource.onmessage = (event) => {
        const { message } = JSON.parse(event.data);

        // Notification API 처리
        if (Notification.permission === "granted") {
          new Notification("건축 견적 AI 솔루션 알림", {
            body: message,
            icon: "/icon.png",
          });
        } else if (Notification.permission !== "denied") {
          Notification.requestPermission().then((permission) => {
            if (permission === "granted") {
              new Notification("건축 견적 AI 솔루션 알림", {
                body: message,
                icon: "/icon.png",
              });
            }
          });
        }
      };

      // 오류 및 재연결 처리
      eventSource.onerror = () => {
        console.warn("SSE 연결 오류, 재연결 시도");
        eventSource?.close();
        eventSource = null;
        setTimeout(connect, 3000); // 3초 후 재연결 시도
      };
    };

    // 사용자 정보가 유효한 경우 SSE 연결 시작
    if (userInfo.pkey) {
      connect();
    }

    // 컴포넌트 언마운트 시 SSE 연결 닫기
    return () => {
      if (eventSource) {
        eventSource.close();
        eventSource = null;
      }
    };
  }, [userInfo]);

 

 

Next.js api

let clients: {
  controller: ReadableStreamDefaultController;
  userPkey: string;
  userLevel: string;
}[] = [];

export async function GET(req: Request) {
  try {
    // 쿼리 파라미터에서 userPkey 가져오기
    const url = new URL(req.url);
    const userPkey = url.searchParams.get("userPkey");
    const userLevel = url.searchParams.get("userLevel");

    if (!userPkey) {
      return new Response("pkey is required", { status: 400 });
    }

    // SSE 헤더 설정
    const headers = {
      "Content-Type": "text/event-stream",
      "Cache-Control": "no-cache",
      Connection: "keep-alive",
    };

    // ReadableStream 생성
    const stream = new ReadableStream({
      start(controller) {
        // 새 클라이언트를 연결 (요청이 들어오면 클라이언트로 처리)
        if (!clients.find((d) => Number(d.userPkey) === Number(userPkey)))
          clients.push({ controller, userPkey, userLevel: userLevel || "1" });

        // 연결 종료 시 클라이언트 제거
        req.signal.addEventListener("abort", () => {
          clients = clients.filter(
            (client) => client.controller !== controller
          );
        });
      },
    });

    return new Response(stream, { headers });
  } catch (error) {
    console.error("GET 요청 오류:", error);
    return new Response(JSON.stringify({ error: "서버 오류 발생" }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
}

// POST 요청: 알림 전송
export async function POST(req: Request) {
  try {
    const { message, userPkey } = await req.json();

    if (!message || !userPkey) {
      return new Response(
        JSON.stringify({ error: "메시지와 userPkey가 제공되지 않았습니다." }),
        { status: 400, headers: { "Content-Type": "application/json" } }
      );
    }

    clients
      .filter(
        (client) =>
          Number(client.userPkey) === Number(userPkey) ||
          client.userLevel === "3"
      )
      .forEach((client) => {
        try {
          client.controller.enqueue(`data: ${JSON.stringify({ message })}\n\n`);
        } catch (error) {
          console.error("클라이언트 전송 오류:", error);
        }
      });

    return new Response(JSON.stringify({ success: true }), {
      status: 200,
      headers: { "Content-Type": "application/json" },
    });
  } catch (error) {
    console.error("POST 요청 오류:", error);
    return new Response(JSON.stringify({ error: "서버 오류 발생" }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
}

 

오류 발생 / 504 (Gateway Timeout)

Nginx로 프록시를 설정해서 사용하는데 SSE를 구현하고 배포 후 504 (Gateway Timeout) 에러가 발생

server {
    listen ~~~ ;
    server_name ~~~ ;

    location / {
        proxy_pass ~~~ ;  # 최종 목적지 서버
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # SSE를 위한 설정
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 3600s;  # 연결 유지 시간
        proxy_send_timeout 3600s;       
    }
}
반응형