이번에 기획서를 바탕으로 처음부터 배포까지 하나의 웹 서비스를 개발해보았다.
처음에 많이 혼동스러웠던 부분이 웹 애플리케이션의 구조였다.
이전에 팀 프로젝트를 할 때는 프론트엔드와 백엔드 포지션이 나눠져 있어서, 자연스럽게 각자 맡은 포지션에서 프레임워크를 사용해서 개발을 했었다.
하지만 이번에는 혼자 개발을 해야했기 때문에 어디서부터 어디서까지가 프론트엔드고 백엔드일까? 고민거리가 생겼다. 초반에 이 고민에 대한 해답을 찾기위해 시간을 많이 썼지만, 이 후 프로젝트를 완성해나가면서 하나하나의 기능을 구현할 때마다 UI개발, API 개발 등 명확하게 구분해서 단계적으로 개발할 수 있어서 구조를 잡고 개발하길 잘했다는 생각이 들었다.
먼저 웹 애플리케이션의 일반적인 시스템 아키텍처인 Web Application Three Tier Architecture를 보자.
Client/Presentation Layer (웹 프론트 서버, 웹 서버)
Server/Application Layer
Database/Data Access Layer
Cross-Cutting
2,3번을 실제 예시로 들어보면, 앱 서버에 Routes-Controller-Services 구조를 적용했을때, 다음과 같이 역할을 분리할 수 있다.
이렇게 웹 애플리케이션 구조를 나눴을 때, 동작 원리는 다음과 같다.
웹 서버는 HTML, CSS, JavaScript, 이미지와 같은 정적인 리소스를 전달하고, 웹 애플리케이션 서버는 요청에 따라 생성되거나 DB에서 추출 또는 변경이 될 수 있는 동적인 데이터를 웹 서버에 제공하여 웹 서버는 이를 클라이언트에게 전달한다. 클라이언트는 동적 데이터를 기반으로 웹 페이지를 렌더링하게 된다.
WAS 하나로 정적, 동적인 데이터를 클라이언트에 제공할 수 있는데 굳이 서버를 두 개로 나눠야 하나요?
WAS만으로 정적, 동적인 데이터를 처리할 수 있으나, 하나의 서버에 역할이 몰리면 동적 콘텐츠 처리가 지연될 수 있다.
따라서, 서버 부하를 방지하기 위해 단순 정적 콘텐츠는 웹 서버가 제공하도록 역할을 분리하는 것이다.
클라이언트의 상호 작용에 따라 바로 서버에 요청하는 것이 아니라 웹 서버를 거쳐서 앱 서버에 전달하는 것이 포트 번호를 한 번 더 우회해 보안을 더 강화할 수 있다.
규모가 커질수록 개발 영역을 명확히하여 관리와 개발 효율을 높일 수 있고, 배포 및 유지보수가 편리하다.
이 외에도 웹 서버의 중요한 역할인 리버스 프록시에 대해 알아보면 좋을 것 같다.
웹 서버와 앱 서버 분리 시 CORS(Cross-Origin Resource Sharing)이슈가 발생하지 않나요?
브라우저가 origin을 비교하는 기준은 다음과 같다.
출처(Origin)는 Protocol, Host, Port를 모두 합친 URL을 의미하고, 셋 중 하나라도 다르다면 Origin이 다르다고 판단한다.
https의 기본 포트는 4403이고, http의 기본포트는 80으로 포트를 명시하지 않아도 프로토콜에 따라 포트가 달라진다.
따라서 웹 서버와 앱 서버를 분리한다면 클라이언트의 요청을 받아 웹 서버에서 API요청을 하게 되는데, 이때 API의 주소가 달라지므로 cors 이슈가 발생한다.
이를 해결하는 방법은 Node.js 기반의 웹 애플리케이션을 개발한다고 가정했을때, 프론트엔드 입장에서와 백엔드 입장에서 제시하자면 다음과 같다.
module.exports = {
devServer: {
proxy: { //api요청 프록시 설정
'/api/': {
// /api/로 시작하는 url은 아래의 전체 도메인을 추가하고, 옵션을 적용
target: 'http://localhost:5000', // 클라이언트에서 api로 보내는 요청은 주소를 5000포트로 바꿔서 보내겠다 라는 뜻
changeOrigin: true, // cross origin 허용 설정
},
},
},
};
nginx로 배포하는 경우, nginx conf 다음과 같이 설정할 수 있다.server {
location /api {
proxy_pass http://localhost:5000; // /api 경로로 오는 요청을 이 url로 전달
}
}
app.get('/api', (req, res) => {
res.header('Access-Control-Allow-Origin', '허용하고자 하는 도메인');
res.send(data)
});
cors 미들웨어를 사용하는 경우 먼저 cors 라이브러리를 설치하고, 아래와 같이 설정할 수 있다.app.use(cors({
origins: '허용하고자 하는 도메인',
}))
이는 CORS 정책을 준수하여 서로 다른 origin 간의 리소스를 공유할 수 있게 하는 것이고, XSS나 CSRF와 같은 공격에 대한 대비는 따로 해줘야 한다.