EC2 deploy, Cannot GET 에러

zzwwoonn·2022년 7월 28일
0

React

목록 보기
23/23

EC2 인스턴스로 웹 사이트를 배포하였다. 디자이너들이 내가 현재까지 작업한 상황을 지켜봐야 했고, 나중에 배포를 했을 때도 서버와 통신하는 코드들(카카오 소셜 로그인 등의)이 로컬에서와는 다르게 배포된 사이트 url 로도 정상적으로 동작해야했기 때문이다.

EC2 인스턴스를 통해 배포된 웹 사이트의 url 주소는 http://15.164.231.60:8000/ 이었다.

express 를 사용하였는데, 로직은 간단하고 내용은 IP주소(15.164.231.60)의 8000번 포트에 루트 경로로 왔을 때 (⇒ http://15.164.231.60:8000/ ) 일 때 어떤 조치를 취할 것인가? 에 대한 얘기이다.

아래 코드는 express 를 사용한 서버 사이드의 코드이다.

# server.js

const http = require("http");
const express = require("express");
const path = require("path");

const app = express();
const port = 8000;

app.get("/ping", (req, res) => {
  res.send("pong");
});

app.use(express.static(path.join(__dirname, "./build")));

app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, "./build", "index.html"));
});

http.createServer(app).listen(port, () => {
  console.log(`app listening at ${port}`);
});

본격적인 내용에 들어가기에 앞서 네트워크 통신을 살짝 엮어서 얘기해보면

  • 3계층에서 IP를 찾아 들어온다. (15.164.231.60) ← EC2 인스턴스, 빌린 컴퓨터
  • 4계층에서 포트 번호를 찾는다. 3000번 포트, 8000번 포트, ~~

이 때 8000번 포트로 들어오면? express 가 그 요청을 받는다.

위의 코드를 살펴보면 express는 정해진 경로에 맞게 html 파일을 리턴해준다. express 는 루트 경로로 요청이 들어오면 index.html 파일 (build 했을 때 생성되는 배포 전용 index 파일)을 리턴할 것이다.

그럼 결론적으로 8000번 포트에 루트 경로로 들어오면
( ⇒ http://15.164.231.60:8000/ )
메인 페이지가 보이게 되는 것이다.

정리해보자. 위의 코드대로라면? http://15.164.231.60:8000/ 요청이 들어왔을 때!
build 밑에 있는 index.html 을 찾아서 열어준다.
⇒ 새로고침을 백번 하면 백번 다 index.html 을 보여줄것이다.

이대로 잘 나가나? 싶었지만, 카카오 소셜 로그인에서 리다이렉트 url을 서버에 던져줘야 하는데 이 때 여러가지를 실행해보다가 갑자기? 페이지가 안뜨더라.

제일 처음에 루트 경로로 타고 왔을 때는 < 8000번 포트에 루트 경로 → index.html 반환 >, 잘 동작한다. 하지만 상대 경로로 다른 페이지를 들어갔을 때 그 페이지에서 새로고침을 하면 에러가 나온다.

제일 처음 루트를 타고 들어온 다음 그 상태에서 다른 페이지들로 라우팅은 되지만(상대 경로로 타고 들어간다→ index.html 안에서 동작),

이 페이지를 새로고침하면 그건 express가 요청을 받을거고? 상대 주소로 그 페이지를 찾는 게 아니라 그 url로 왔을 때 어떤 html 파일을 또는 어떤 페이지를 반환해야 하나!! 어떤 페이지를 보여주면 되나!! 하는 문제이다.

그럼 express 는 당연히 /loginpage 의 요청이 왔을 때 뭐 어떻게 해야할 지를 모른다. 너무나 당연한 것이

app.get("/", (req, res) => {
  res.sendFile(path.join(__dirname, "./build", "index.html"));
});

루트 경로로 들어오면 index.html 반환하는 건 정의를 해주었으나 /loginpage 경로로 들어오면 “엥 ? 나 그거는 뭔지 모르겠는데? ” 가 되버리는 것이다.

/loginpage 경로로 왔을 때도 위와 같이 index.html 파일을 리턴해라!! 라고 하면 해결될 것이다.

< **Cannot GET / 이슈 발생 원인은 무엇일까요? >**

OKKY | Cannot GET / 이슈 발생 원인은 무엇일까요?


정상 동작하는 페이지 안에서(index.html 안에서) 라우터를 통해 동작하는 링크를 눌렀을 때는 서버로 직접요청을 보내지 않고, 브라우저에 표시되는 주소값만 바꾸고 내부에서 스크립트를 통해 페이지를 생성해서 보여주지만, 사용자가 주소창에 직접 Url을 입력하면 서버로 요청이 가는걸 막을 수 없어서 그렇습니다.
보통 그 경우에도 정상실행이 가능하도록 서버쪽에서 항상 index.html 을 내려주는 처리를 하게 됩니다.

React Router에서 특정 URL 접속 시 페이지를 찾을 수 없는 문제 원인 및 해결 방법(nginx, node express, apache, jboss web app)


react-router를 사용했을 때, 로컬에서 http://localhost:8000/login 와 같이 접근하게 되면 /login에 해당되는 컴포넌트가 나온다.하지만 https://linkuniversity.me/login 과 같이 배포 후에 특정 도메인을 통해 접근한다면, 페이지를 찾을 수 없다는 페이지를 볼 수 있게 된다.
이런 현상이 일어나는 것은, BASE가 되는 URL은 index.html을 연결시켜 배포하지만, 다른 URL으로 접속하면 해당 URL에 맞는 HTML을 찾으려고 하기 때문에 생기는 문제이다.예를 들어 /login URL에 접속하려고 했을 때 /login URL에 맞는 html 파일을 nginx 내에서 찾으려고 하기 때문이다. 따라서 BASE가 되는 URL이 아닌 다른 URL을 통해 사이트에 접속해도, index.html을 연결시켜 배포해 주는 작업이 필요하다.

express로 8000번 포트 열어서 할 때 저런 문제, 해결하는거도 오케이 확인, 이해했다.

그럼 로컬에서는 새로고침해도 제대로 동작하는(페이지가 잘 보이는) 이유가 뭐지??

계속 찾아보다가 도저히 모르겠어서 프론트엔드 단톡 방에다가 질문했다.

질문 하나만 하겠습니다!
EC2로 리액트 배포하려고 합니다. ssh 를 통해서 EC2 인스턴스를 실행시켰고, 아래의 1번 경우와 2번 경우의 차이점에 대해 여쭙고 싶습니다.
두 가지 경우 모두 버튼(예를 들면 로그인 페이지로 이동)을 통해서 페이지를 이동할 땐 정상적으로 동작하지만(리액트 라우터를 이용했습니다), 1번의 경우는 페이지 새로고침을 하면 Cannot GET 이라고 합니다.
가장 먼저 터미널에서 ssh 로 EC2 인스턴스를 실행시켰습니다. 그 다음으로
1. "node server.js" 명령어를 통해 index.html을 열었습니다. ( server.js 코드는 express를 통해서 8000번 포트의 루트 경로에 index.html 을 매핑시켜주는 내용입니다. ) => http://{퍼블릭 IP}:8000/ 가 열립니다.
2. npm run start 명령어를 통해 로컬(EC2 인스턴스 로컬)에서 index.html 을 열어줍니다. => http://{퍼블릭 IP}:3000/ 가 열립니다.
2번의 경우(로컬로 돌렸을 때)와 달리 1번에서는 새로고침을 하면 페이지를 못 읽어오는 이유가 뭔지 궁금합니다.

답변 1

API 서버위에서 정적 클라이언트를 가져온 것이 1번 ⇒ 8000번 포트
클라이언트 서버에서 동적으로 라우팅하는 것이 2번 ⇒ 3000번 포트

답변 2

로컬에서는 webpack 개발 서버로 띄우기 때문입니다

⇒ 웹팩 개발 서버에 정의된 내용 중 "historyApiFallBack - status 404면 index.html로 리다이렉트" 에 해당하는 내용인가?

로컬에서 돌려도 404 뜨는데? 웹팩 개발 서버에서 404 뜨면 다시 index.html 리다이렉트 해주고 거기서 상대 경로로 /loginpage 찾아가는거야. 그럴싸 하지 않나?

병우형한테 설명을 듣고나서!!! 다시 답변을 해석해보자. 답변 1을 해석해보면

⇒ API 서버 : 익스프레스 , 정적 클라이언트 : index.html

익스프레스로 요청이 들어온거니까 당연히 다른 건 몰라

⇒ 클라이언트 서버 : 리액트 서버, 동적으로 라우팅 : SPA 페이지(라우팅)

답변 내용은 병우형이 설명해준 내용, 내가 이해한 내용이랑 정확하게 일치했다.


그리고 사실 가장 근본적으로 8000번 포트, 즉 express(server.js) 를 사용할 필요가 없었다.

EC2를 사용하는 이유부터 생각해보면,

EC2 인스턴스를 쓴다는 것은 새로운 컴퓨터를 한 대 더 빌리는 것이다.

그 컴퓨터의 정보가 바로

개발할 때 개발자가 내리는 모든 판단에는 명확한 근거가 있어야 한다.
처음으로 다시 돌아가보자

< EC2 인스턴스를 사용하는 이유 >

  1. 제일 처음에 적은 내용인데, 디자이너들이 내가 현재까지 작업한 상황을 지켜봐야 했고, 나중에 배포를 했을 때도 서버와 통신하는 코드들(카카오 소셜 로그인 등의)이 로컬호스트에서와는 다르게 배포된 사이트 url 로도 정상적으로 동작해야했기 때문이다.(로컬에서만 동작하는 코드들이 있었다, 카카오 소셜 로그인의 redirect URL이 그랬는데 지금은 서버와 잘 통신이 되는 것 까지 확인했다)
  2. 또한 내 노트북, 로컬로 이 웹 사이트를 돌려놓으면? 그 웹 사이트가 정상적으로 동작하기 위해서 항상 내 노트북이 켜져 있어야 한다. (노트북 계속 켜놓고 터미널도 계속 켜놔야 한다) 하지만 EC2 인스턴스(새로운 컴퓨터 하나 빌려서)를 통해 서버 계속 열어두고 터미널도 계속 켜놔서 그 웹 페이지를 열어 놓는다면? 내가 노트북으로 작업하는 것과는 상관없이 계속 웹 페이지가 켜져 있을 것이다.

그럼 만약에 EC2 인스턴스(빌린 컴퓨터, 서버)는 계속 켜둔다고 쳐, 그럼 또 터미널로 EC2 인스턴스를 실행시킬건데, 그 터미널이 꺼지면 인스턴스가(서버) 켜져있던 꺼져있던 그 웹 사이트가 꺼지는데 ?

그럼 로컬에서 (내 노트북에서) 돌리는 거랑 뭐가 다르냐? 터미널 계속 켜놔야 하는 건 똑같은 거 아니야? 라고 말할 수 있다. 아주 정확하다. 나도 이 생각을 했다.

⇒ 하지만 이 문제는 nohup 을 통해 해결한다. 터미널이 닫혀도 그 포트는 계속 열려있게 하는 것이다.

nohup 에 대해 내가 알고있는 대로 간단하게 적어보자면(틀릴 수도 있다. 나중에 시간이 되면 nohup 에 대해 공부해보자 상당히 심오해보인다) 그 포트에 할당되서 돌아가고 있는 프로세스의 부모 프로세스는 터미널이다. 따라서 터미널이 닫히면 ⇒ 세션이 끊기면 ⇒ 연결이 종료되면 당연히 그 자식 프로세스들도 전부 꺼지는 것이다. (인스턴스, 서버가 닫히는 것과는 무관하다. 인스턴스는 항상 돌아가는 중)
하지만 nohub 을 이용하면 그 자식 프로세스들을 전부 root ( EC2 인스턴스 ) 에다가 묶어 버리는 것이다. (”데몬의 형식으로 background 에서 동작한다”가 맞는 말이다) 따라서 터미널이 종료되어도 그 프로세스는 끝나지 않고 EC2 인스턴스 자체가 닫혀야 끝나는 것이다. 또는 kill 명령으로 강제로 죽여버릴 수 있다.

쉽게 설명한 nohup 과 &(백그라운드) 명령어 사용법


EC2 인스턴스(서버)는 터미널이 꺼져 있어도 계속 돌아가고 있다. 로컬에서 작업하고(코드 고치고 나서) 이를 다시 서버에다가 올리려면?

lsof -i :3000 으로 3000번에 돌아가고 있는 프로세스(웹 사이트겠지) pid 값 찾아서 강제로 kill -9 {id} 으로 죽이고 git pull, build, 다시 nohup {} & 하면 된다.

0개의 댓글