문제 시작
기본 구조
바야흐로 2번째 프로젝트를 AWS에 배포 마무리하던 중 에러가 발생하였다.
먼저 간단히 구동 원리를 설명하자면
클라이언트(리액트)에서 채팅방을 입력하면
const enterChatRoom = (roomId) => {
window.location.href = `/chatroom/${roomId}`;
};
`App.js` 에서 아래와 같이 채팅방을 동적 라우트로 추가하고,
function App() {
return (
<Router>
<Routes>
```
<Route path="/chatroom/:roomId" element={<ChatRoom />} />
</Routes>
</Router>
);
}
ChatRoom 컴포넌트에서 WebSocket의 앤드포인트로 연결한다.
websocket.current = new WebSocket(`ws://example:8080/ws/chat/${roomId}`);
Spring에서는 `CustomWebSoketHandler` 를 `/ws/chat/{roomId}` 경로에 매핑한다.
public class WebSocketConfig implements WebSocketConfigurer {
@Autowired
private CustomWebSocketHandler customWebSocketHandler;
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(customWebSocketHandler, "/ws/chat/{roomId}")
.setAllowedOrigins("*");
}
}
즉 사용자가 채팅방 목록에서 특정 채팅방을 클릭하면, 브라우저가 `/chatroom/{roomId}` 로 이동하여
ChatRoom 컴포넌트를 렌더링 하며,
ChatRoom 컴포넌트는 URL 파라미터로 전달받은 roomId를 사용하여, 서버의 WebSocket 엔드포인트 `ws://[서버주소]/ws/chat/{roomId}` 에 연결한다.
Spring 서버는 해당 `WebSocket` 연결을 받아 `CustomWebSocketHandler` 에서 관리하며, 실시간 채팅 기능을 제공한다.
그리고 리액트 서버는 Nginx를 통해 배포된다.
404 문제
하지만 개발과정에서 리액트를 localhost에서 구동할 땐 문제가 되지 않았지만, AWS EC2 인스턴스에 올려 Nginx로 구동하니 아래와 같은 화면이 뜨게 되었다.
처음엔 url이나 서버 쪽에 문제가 있나 생각하였지만, 간단히 생각하면 근본적으로 존재하지 않은 경로이기 때문에 404 에러로 발생한 것이다.
그럼 왜 로컬 개발환경에서는 발생하지 않았을까?
해당 문제는 클라이언트 사이드 라우팅과 관련된 일반적인 현상이라고 한다.
개발 환경에서 create-react-app의 개발 서버(webpack dev server)는 모든 요청에 대해
index.html을 반환하여 React Router가 경로를 처리할 수 있도록 해준다.
반면, 실제 서버나 배포 환경에서는 웹 서버(Nginx, Apache...)가 기본적으로 존재하지 않는 경로에 대해
404 에러를 반환한다.
즉, /chatroom/f10 bb03 f...(roomId겠죠?)와 같은 URL로 직접 접근하면
웹 서버가 해당 파일이나 디렉터리를 찾지 못해 404를 발생시키는 것이다.
자 문제를 알았으니 이제 해결방법을 봐보자!
해결 방법
웹 서버에서 fallback 설정
배포환경의 웹 서버에서 모든 요청(404, 403 에러도 포함)을 index.html로 리디렉션 하도록 설정한다.
꼭 index.html처럼 할 필요는 없고 흔한 사이트처럼 에러 페이지로 이동해도 된다.
먼저 Nginx의 경우를 설명하겠다.
Nginx의 설정파일을 수정한다.
보통 `/etc/nginx/sites-available/default` 경로에 있으며 해당폴더는 시스템 설정 파일들이 위치한 곳으로,
root 소유이기 대문에 `sudo` 명령어를 사용하거나 해당 경로를 소유자로 변경해야 한다.
하지만 보안상 소유자를 변경하는 건 일반적이지 않기에 `sudo` 를 사용하겠다.
sudo nano /etc/nginx/sites-available/default
그렇게 된다면 아래와 같이 화면이 뜰 텐데 Vim과 거의 비슷한 에디터이다.
이제 아래 server 부분을 수정해야 한다.
server {
listen 80;
server_name your_ip; # 도메인 또는 EC2 퍼블릭 IP 사용
root /var/www/html; # React build 파일이 위치한 디렉토리로 변경
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
}
이렇게 되면 try_files에서 요청한 파일이 없으면 /index.html을 반환하게 된다.
ctrl+x 를 누르고 파일이름을 변경할 필요 없으니 엔터를 입력해 저장한다.
그다음 Nginx를 재시작하여 반영해 보자.
sudo systemctl restart nginx
HashRouter 사용
만약 서버 설정을 변경하거나 구조상 복잡하여 어렵다면 React Router에서
`BrowserRouter` 대신 `HashRouter` 를 사용하면 URL에 #을 포함하여 클라이언트 사이드 라우팅을 구현할 수 있다.
이 경우에는 경로에 #이후의 경로를 무시하므로 404문제가 발생하지 않는다.
import { HashRouter as Router, Routes, Route } from 'react-router-dom';
// 이후 생략
하지만 이 방법은 또다시 구조를 변경해야 하므로 첫 번째 방법을 적용하였다.
해결
자 이제 Nginx를 재시동하고 다시 주소로 들어가 보자.
에러 없이 성공적으로 웹소켓을 받을 수 있으며
전송 수신 또한 잘 적용되었다.
마무리
문제를 해결하기 위해 index.html로 리디렉션 하여 작동하도록 설정하였지만 사실 따로 페이지를 구현하여야 했다.
말 그대로 404 에러가 발생했기 때문에 일반적인 개발방식은 아니었다.
하지만 클라이언트 사이드 라우팅 문제는 개발 환경에서는 별다른 이슈 없이 처리되지만, 실제 배포 환경에서는 웹 서버가 존재하지 않는 경로에 대해 404를 반환하는 특성이 있음을 알게 되었으며,
웹소켓과 채팅 기능을 포함한 여러 서버를 배포하며 AWS 설정에서도 많은 경험이 쌓이게 되었다.
나중에 참고할 코드
각 에러와 관련하여 해당설정을 하면 html파일을 지정해 줄 수 있다.
server {
listen 80;
server_name my_ip; # 도메인 또는 EC2 퍼블릭 IP
root /var/www/html; # React build 파일들이 위치한 경로
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
# 404 에러 발생 시 404.html 페이지를 표시
error_page 404 /404.html;
location = /404.html {
internal;
}
# 500, 502, 503, 504 에러 발생 시 50x.html 페이지를 표시
error_page 500 502 503 504 /50x.html;
location = /50x.html {
internal;
}
}
'SK 루키즈 > Cloud' 카테고리의 다른 글
[Rookies 개발 2기] AWS S3 버킷 정책 오류 설정 (2) | 2025.02.23 |
---|---|
[Rookies 개발 2기] Docker를 통해 Kafka 연결하기 (2) | 2025.02.06 |
[Rookies 개발 2기] AWS 서버리스 개념과 ECS와 Lambda (0) | 2025.01.24 |
[Rookies 개발 2기] MSA와 EurekaServer 세팅 (0) | 2025.01.24 |
[Rookies 개발 2기] AWS S3 에 Spring 업로드 처리 (0) | 2025.01.23 |