[데브코스 웹 풀스택 과정 TIL] Day 6~9 - 웹 생태계 전반에 대한 이론 및 실습 (3) - Node.js로 만드는 웹서버
1. 백엔드
웹서버 만든다면서 뜬금없이 웬 백엔드 설명인가 싶겠지만 뒤에서 필요한 내용이니 한번 훑어보고 가자.
백엔드의 구조
위 그림을 보면 알겠지만 백엔드는 크게 세가지,
웹 서버 / 웹 어플리케이션 서버(WAS: Web Application Server) / 데이터베이스로 구분할 수 있다.
- 웹 서버: 정적 페이지에 대응하는 서버. 동적 페이지 처리는 직접 하지 않고 WAS에 전달하여 처리함.
- 웹 어플리케이션 서버: 동적 페이지에 대응하는 서버. 데이터 처리 / 연산을 하여 웹 서버에 전달해줌으로써 화면의 내용이나 데이터의 변화를 구현함.
- 데이터베이스: 데이터를 통합하여 효율적인 관리를 하기 위한 데이터 집합체.
그런데 위의 그림에서 보다시피 웹 서버와 WAS가 분리되어 있는데, 여기서 하나 의문이 들 수 있는 것이 '서버를 하나로 통합해서 운영하면 되는데 굳이 왜 두개를 분리해서 운영하지?' 일 것이다.
사실 가능은 하다. 한 서버에 두개를 통합해서 돌리면 그만이다.
그런데 그렇게 되면 서버 사용량(=트래픽)이 적을 때에는 문제가 없겠지만 트래픽이 늘고 처리해야되는 데이터 양이 늘어나다 보면 서버에 부하가 걸리게 되고, 결국에는 서버가 뻗는 상황이 올 수도 있다.
이때 두 서버를 분리해서 운영하면 서버의 부하도 줄임과 동시에 하나의 웹 서버에서 여러 웹 애플리케이션을 사용할 수 있게 된다.
2. Node.js
자바스크립트는 알다시피 웹 브라우저 상에서 돌아가는 스크립트 언어이다.
그리고 Node.js는 그 자체로는 자바스크립트를 웹 브라우저 외의 네이티브 환경에서 실행하기 위해 만들어진 프로그램(플랫폼)이다. (크롬 브라우저에 들어가있는 V8 자바스크립트 엔진을 바탕으로 만들어졌다고.)
이 둘이 만나니 자바스크립트는 네이티브 환경에서 실행할 수 있게 되면서 본격적인 프로그래밍이 가능해졌다. 또한 Node.js에는 HTTP 서버 라이브러리가 내장되어 있어서 별도 소프트웨어 없이도 서버를 구축 할 수 있다.
이게 무슨 소리냐 하면,
Node.js를 통해 프론트엔드에 쓰이던 자바스크립트로 백엔드까지 구현할 수 있다는 것이다!
그렇다면 이제 Node.js를 가지고 웹 서버를 만들어를 만들어 볼 차례다.
웹 서버 만들기
/* server.js */
let http = require('http'); // http 모듈 불러오기
function onRequest(request, response) { // http 모듈에서 요청을 받았을 때 수행할 함수
/* 응답의 header 설정(200: 정상 / 'Content-Type' : 'text/html' --> '이 페이지는 text/html 임') */
response.writeHead(200, {'Content-Type': 'text/html'});
response.write('Hello!'); // 응답 컨텐츠 작성
response.end(); // 응답 종료
}
/* onRequest를 콜백 함수로 하는 서버 생성 (여기서는 8080포트를 사용함) */
http.createServer(onRequest).listen(8080);
이 코드를 터미널 상에서 node server.js
로 실행하고 브라우저에서 localhost:8080을 치고 들어가면
정상적으로 응답이 출력되었음을 알 수 있다
그렇다면 서버를 모듈화 하는 방법을 보자.
서버 모듈화
/* index.js */
let server = require('./server');
server.js를 모듈로서 불러오는 구문이다. 이렇게만 해서 실행할 수 있으면 좋겠지만 아쉽게도(?) 그렇게는 안된다.
그렇다면 방법은 server.js에서 서버 실행 부분을 함수로 감싸고 그 함수를 export시키는 것이다.
/* index.js */
let server = require('./server');
server.start();
/* server.js */
let http = require('http');
function start() {
function onRequest(req, resp) {
response.writeHead(200, {'Content-Type': 'text/html'});
response.write('Hello!');
response.end();
}
http.createServer(onRequest).listen(8080); //localhost:8080
}
exports.start = start; // 외부에서 모듈 내부 함수를 쓸 수 있게 함
이렇게 하고 node index.js
를 실행하면 브라우저로 가보면 방금과 같은 결과를 얻는다.
방금과의 차이점이라면 앞에서는 단일 파일이였다면 이번엔 실행 코드와 서버 코드를 모듈로써 분리했다는 것이다.
라우터 생성 및 라우팅
웹에서 라우팅이라 함은 클라이언트의 요청에 따라 요청에 맞게 정해진 페이지로 안내하는 것이다.
/* router.js */
function route(pathname) {
console.log('[router] pathname : ' + pathname);
}
exports.route = route;
먼저 라우팅을 해줄 route()
함수를 router.js
에 만들어 export 해준다. 아직은 콘솔에 경로명만 띄워주기로 한다.
/* index.js */
let server = require('./server');
server.start(router.route);
그리고 route.js
에서 export 해준 route()
함수를 server.js
의 start()
함수에 인수로 넘겨준다.
/* server.js */
let http = require('http');
function start(route) {
function onRequest(req, resp) {
let path = new URL(req.url, `http://${req.headers.host}/`).pathname; // url.parse().pathname 대체
route(path);
response.writeHead(200, {'Content-Type': 'text/html'});
response.write('Hello!');
response.end();
}
http.createServer(onRequest).listen(8080); //localhost:8080
}
마지막으로 URL에서 경로명만 떼내어 index.js
에서 넘겨준 route()
함수에 인수로 넣어 실행해주면 된다.
여기까지 하면 라우팅은 성공이다.
그렇다면 요청에 따라 각기 다른 페이지로 라우팅을 해보자.
/* requestHandler.js */
function main(response) {
console.log('main');
response.writeHead(200, {'Content-Type' : 'text/html'});
response.write('main')
response.end();
}
function login(response) {
console.log('login');
response.writeHead(200, {'Content-Type' : 'text/html'});
response.write('login')
response.end();
}
function default_call(response, pathname) {
response.writeHead(404, {'Content-type': 'text/html'});
console.error('No hander of ' + pathname + '!')
response.write('Not found');
response.end();
}
let handler = {}; // key-value pair로 이루어진 함수
handler['/'] = main;
handler['/login'] = login;
handler['default'] = default_call; // handler에 없는 함수 호출 시를 위한 함수
exports.handler = handler;
먼저 라우터에서 넘어온 요청을 처리하기 위한 핸들러를 만들어준다.
/* router.js */
function route(pathname, handler, response, prod_id) {
console.log('[router] pathname : ' + pathname);
(typeof handler[pathname] === 'function') ? handler[pathname](response, prod_id) : handler['default'](response, pathname);
}
exports.route = route;
다음 라우터에 핸들러로 경로 별 정해진 함수를 실행하도록 수정해준다.
/* server.js */
let http = require('http');
function start(route, handler) {
function onRequest(req, resp) {
let path = new URL(req.url, `http://${req.headers.host}/`).pathname; // url.parse().pathname 대체
route(path, handler, resp);
response.writeHead(200, {'Content-Type': 'text/html'});
response.write('Hello!');
response.end();
}
http.createServer(onRequest).listen(8080); //localhost:8080
}
server.js
에도 라우터가 경로 별로 처리할 수 있도록 인수를 넘겨주고,
/* index.js */
let server = require('./server');
server.start(router.route, requestHandler.handler);
index.js
에서 server.start()
함수에서 사용할 핸들러 함수를 넘겨주면 끝이다.
[다음글에 이어서]