얼리테이블 - 트러블슈팅 : SSE (Server-Sent Events) 연결 오류

take_the_king·2025년 2월 4일

📌 React 프로젝트에서 SSE (Server-Sent Events) 트러블 슈팅

SSE (Server-Sent Events)를 React 애플리케이션에 적용하면서 발생한 문제들과 해결 방법을 정리


🚨 1. 로그인/회원가입 페이지에서도 SSE가 실행되는 문제

🔍 문제 원인

  • SSE를 알림기능에 사용하기 위해 전역 페이지에 적용함.
  • SSEProviderApp.jsRouter 전체를 감싸고 있어 로그인/회원가입 페이지에서도 SSE가 실행됨.
  • 로그인하지 않은 사용자에게는 불필요한 SSE 연결이 발생함.

💡 해결 방법

로그인 및 회원가입 페이지는 SSEProvider에서 제외하고, 인증된 페이지에만 적용

<Router>
  <Routes>
    {/* ✅ 로그인 & 회원가입에서는 SSEProvider 제외 */}
    <Route path="/register" element={<Register />} />
    <Route path="/login" element={<Login />} />

    {/* ✅ 나머지 페이지에서는 SSEProvider 적용 */}
    <Route
      path="/*"
      element={
        <SSEProvider>
          <Routes>
            <Route path="/" element={<Home />} />
            <Route path="/store/:storeId" element={<StoreDetails />} />
            {/* ...생략 */}
          </Routes>
        </SSEProvider>
      }
    />
  </Routes>
</Router

🚨 2. 중복 SSE 연결이 발생하는 문제

🔍 문제 원인

  • SSEProvideruseEffect가 여러 번 실행되어 기존 SSE 연결을 닫지 않은 채 새로운 연결이 계속 생성됨.
  • 페이지 이동 시 기존 SSE 연결이 유지되지 않고 새로운 연결이 생성되어 중복 발생.

💡 해결 방법

eventSourceRefuseRef로 관리하여 중복 연결 방지

기존 SSE 연결이 있으면 새 연결을 생성하지 않도록 조건 추가

onst eventSourceRef = useRef(null);

const connectSSE = () =&gt; {
  if (eventSourceRef.current) {
    console.log("⚠️ 기존 SSE 연결 존재, 중복 연결 방지");
    return;
  }

  eventSourceRef.current = new EventSourcePolyfill(
    "http://localhost:8080/notifications/subscribe",
    {
      headers: { Authorization: `Bearer ${localStorage.getItem("accessToken")}` },
      withCredentials: true,
    }
  );

  eventSourceRef.current.onopen = () =&gt; {
    console.log("✅ SSE 연결 성공");
  };

  eventSourceRef.current.onerror = () =&gt; {
    console.error("❌ SSE 연결 오류 발생, 재연결 시도...");
    eventSourceRef.current?.close();
    eventSourceRef.current = null;
    setTimeout(connectSSE, 3000); // 3초 후 재연결
  };
};

useEffect(() =&gt; {
  connectSSE();

  return () =&gt; {
    console.log("🛑 SSE 연결 해제");
    eventSourceRef.current?.close();
    eventSourceRef.current = null;
  };
}, []);

이제 페이지 이동 시에도 기존 SSE가 유지되며, 중복 연결이 방지됨!


🚨 3. SSE 연결 끊김 시 자동 재연결되지 않는 문제

🔍 문제 원인

  • SSE 연결이 끊기면 새로운 연결을 시도하지 않고 종료됨.
  • 브라우저의 자동 재연결 기능이 동작하지 않는 경우가 발생함.

💡 해결 방법

SSE 에러 발생 시 자동 재연결 로직 구현

최대 3회까지 재연결을 시도하고, 실패 시 토큰 갱신 후 재시도

const retryCountRef = useRef(0);

const connectSSE = () =&gt; {
  if (eventSourceRef.current) {
    console.log("⚠️ 기존 SSE 연결 존재, 중복 연결 방지");
    return;
  }

  eventSourceRef.current = new EventSourcePolyfill(
    "http://localhost:8080/notifications/subscribe",
    {
      headers: { Authorization: `Bearer ${localStorage.getItem("accessToken")}` },
      withCredentials: true,
    }
  );

  eventSourceRef.current.onopen = () =&gt; {
    console.log("✅ SSE 연결 성공");
    retryCountRef.current = 0; // 재연결 카운트 초기화
  };

  eventSourceRef.current.onerror = async (error) =&gt; {
    console.error("❌ SSE 연결 오류 발생:", error);
    eventSourceRef.current?.close();
    eventSourceRef.current = null;

    if (retryCountRef.current &gt;= 3) {
      console.warn("🚨 SSE 재연결 3회 실패, 토큰 갱신 후 재시도");
      const success = await getRefreshToken();
      if (success) connectSSE();
    } else {
      setTimeout(() =&gt; {
        retryCountRef.current += 1;
        console.log(`🔄 SSE 재연결 (${retryCountRef.current}번째 시도)`);
        connectSSE();
      }, 3000);
    }
  };
};

이제 SSE 연결이 끊겨도 자동으로 재연결됨!


🚨 4. 액세스 토큰 만료 시 401 오류가 반복되는 문제

🔍 문제 원인

  • 액세스 토큰 만료 시 새로운 토큰을 받아오지 못해 SSE 연결이 실패함.
  • SSE 요청 시 만료된 액세스 토큰을 계속 사용하여 401 에러가 반복됨.

💡 해결 방법

401 오류 발생 시 자동으로 리프레시 토큰을 요청하고, 성공하면 SSE 재연결

토큰 갱신 요청의 중복 실행을 방지하도록 isRefreshingRef 활용

const isRefreshingRef = useRef(false);

const getRefreshToken = async () =&gt; {
  if (isRefreshingRef.current) return false;
  isRefreshingRef.current = true;

  try {
    console.log("🔄 토큰 갱신 시도...");
    const response = await instance.post("/users/refresh", {}, {
      headers: { "Content-Type": "application/json" },
      withCredentials: true,
    });

    const newAccessToken = response.data.accessToken;
    if (!newAccessToken) throw new Error("새로운 토큰 없음");

    localStorage.setItem("accessToken", newAccessToken);
    console.log("✅ 토큰 갱신 성공");
    return true;
  } catch (err) {
    console.error("❌ 토큰 갱신 실패, 로그인 페이지로 이동");
    navigate("/login");
    return false;
  } finally {
    isRefreshingRef.current = false;
  }
};

eventSourceRef.current.onerror = async (error) =&gt; {
  console.error("❌ SSE 연결 오류 발생:", error);
  eventSourceRef.current?.close();
  eventSourceRef.current = null;

  if (error.status === 401) {
    const success = await getRefreshToken();
    if (success) {
      console.log("🔄 SSE 재연결 시도...");
      connectSSE();
    }
  } else {
    setTimeout(() =&gt; {
      console.log("🔄 SSE 자동 재연결...");
      connectSSE();
    }, 3000);
  }
};

이제 토큰이 만료되어도 자동으로 갱신되고 SSE가 재연결됨!


🎯 정리

문제해결 방법
로그인/회원가입에서도 SSE 실행됨로그인/회원가입에서는SSEProvider제외
중복 SSE 연결 발생useRef를 사용해 중복 연결 방지
SSE가 끊겨도 자동 재연결 안 됨최대 3번 재연결 후 토큰 갱신 후 재시도
액세스 토큰 만료 시 401 오류 반복401 발생 시 토큰 갱신 후 SSE 재연결
profile
개발을 좋아하는 taketheking 입니다.

0개의 댓글