2024년 8월 22일
오늘 드디어 백엔드를 들어갔다.
앞전까지는 다 아는 내용들이라서 가벼운 마음으로 들었는데 이제부터 백엔드사이드를 배우기 때문에 정신 똑띠 차리고 임해야겠다!
: 웹서버, 웹어플리케이션서버, 데이터베이스 까지 다 포함해서 백엔드라고함
: 자바스크립트를 프로그래밍 언어 역할을 할 수 있도록 하는 플랫폼
: 모듈을 이용해 웹서버를 만들 수 있음
❗️ 클라이언트와 서버가 서로 소통할 때 하는 약속(=프로토콜)이 존재 하는데, 우리는 이 프로토콜을 이용해 서버를 만들 예정임 -> http
Node.js를 설치했으면, 일단 모듈을 불러와야한다.
: http 프로토콜을 사용할 수 있게 미리 Node.js가 모듈(=조립부품)로 만들어 놓았다. 그래서 우리는 이 프로토콜을 불러서 사용할 수 있는데, require()함수를 이용하면 된다.
즉, () 안에 들어오는 모듈을 가져다주는 역할을 한다.
// server.js
let http = require('http');
모듈을 불러왔으면 이제 서버를 만들어보자.
: http모듈은 서버를 만들 수 있는 함수를 가지고 있고, 우리는 이것을 사용해 서버를 만들 수 있다.
http모듈의 createServer() 함수를 이용해 우리가 만든 onRequest() 함수를 원하는 통신방법이 이렇다하고 넣어주어서 서버를 만든다. 그리고 만들어진 서버와 클라이언트가 얘기할때는 listen()을 통해 이런 주파수를 이용하겠다 알려주고, 클라이언트는 넣은 주파수로 브라우저에 접속(=소통)할 수 있다.
❗️ 주파수 = 포트번호(port number) : 클라이언트와 서버가 대화하고 싶을때, 무전기처럼 같은 주파수를 맞춰야 하는데 그 때 사용하는 번호
// server.js
let http = require('http');
// http프로토콜을 불러서 변수 http에 담음
function onRequest(request, response) {
response.writeHead(200, {'Content-Type' : 'text/html'});
response.write('Hello Node.js');
response.end();
}
// 콜백함수
http.createServer(onRequest).listen(8888);
// localhost:8888을 url에 입력하면 서버가 작동해 Hello, Node.js가 보이는 것을 확인 할 수 있음
: 우리는 계속 백엔드에서 작업하는 내용들을 웹서버만 언급 하는데 왜 그럴까?
그 이유는 '웹서버'를 통해 왔다갔다 작업하기 때문이다. 자세한 설명으로는, 클라이언트의 요청을 웹서버가 받아서 만약 바로 처리하지 못할 것 같으면 웹어플리케이션서버(WAS)와 데이터베이스(DB)에 넘긴다. 반대로, 응답도 WAS와 DB가 일을 처리하고 웹서버에 전달하면 웹서버가 클라이언트에 응답하므로 그렇다고 한다.
: 앞전에 썼던 콜백함수 onRequest()안에 적은 3줄은 어떤 것일까?
이것은 http 템플릿을 적은 것이다.
Node.js에서 request와 response를 이용하면 각각 알아서 요청과 응답이 될 수 있게 만든다
템플릿 형태
// server.js
let http = require('http');
function onRequest(request, response) {
response.writeHead(200, {'Content-Type' : 'text/html'});
// '웹서버가 클라이언트에 응답(response)할때 Head를 적겠다'라는 의미
// HTTP 프로토콜의 Head 작성
// 통신 -> 200포트번호, 응답 -> html
// Head
response.write('Hello Node.js');
// 데이터를 담음
// Body
response.end();
// response에 담을 것 끝났음. 전송
}
http.createServer(onRequest).listen(8888);
이렇게 node의 모듈로 서버를 만들고 확인해보면,
(node 시작시) node server.js -> Hello, Node.js 문구 확인 할 수 있음
(반대로 node종료시: ctrl + c)
결과화면
: 다른 자바스크립트코드에서도 우리가 위에서 만든 서버를 불러와서 사용하고 싶다면(node.js의 모듈을 사용하는 것처럼 server.js도 모듈로 만들고 싶다면) 모듈화를 시켜야한다. (=서버의 유연성)
// index.js
let server = require('./sersver');
❗️아쉬운점
: 그런데 우리가 서버 모듈 불러왔다는 이유만으로 계속 서버가 켜지는 상태라면 이는 역시 서버의 유연성이 떨어진다. 우리 마음대로 서버를 On/Off 할 수 없다는 뜻이다.
따라서, 이것을 고도화 시켜보면
// index.js
let server = require('./server');
server.start();
// server모듈의 start()함수를 실행시켜 서버가 on됨
// server.js
let http = require('http');
function start() {
function onRequest(request, response) {
response.writeHead(200, {'Content-Type' : 'text/html'});
response.write('Hello Node.js');
response.end();
}
http.createServer(onRequest).listen(8888);
}
exports.start = start;
// 자바스크립트에서 만든 함수는 해당 파일 안에서만 역할을 할 수 있으므로
// 밖으로 빼내고 싶으면추출(export)해야 한다
다시 터미널창에서 node index.js 를 입력하면 서버가 실행되는 것을 확인할 수 있다.
: Uniform Resource Locator
인터넷 공간 안에서 웹페이지가 어디에 있는지 알려주는 주소(웹페이지 주소=위치를 알려줌)
ex) http://localhost:8888 -> http: http프로토콜 / localhost: 내컴퓨터주소 / 8888: 포트번호
// server.js
let http = require('http');
let url = require('url');
function start() {
function onRequest(request, response) {
let pathname = url.parse(request.url).pathname;
// parse() : 문자열 캐치 함수
console.log('pathname: ' + pathname);
response.writeHead(200, {'Content-Type':'text/html'});
respoonse.write('Hello Node.js');
response.end();
}
http.createServer(onRequest).listen(8888);
}
다시 터미널창에 node index.js로 서버를 켜서 확인하면 터미널창에 pathname: / 이 출력되는 것을 확인할 수 있다.
결과화면
웹브라우저의 주소창 url에 어떤 것을 덧붙이면
http://localhost:8888/hello -> pathname: /hello 라는 것을 알 수 있다
결과화면
이말은, pathname은 포트번호 뒤에 오는 경로라는 것을 알 수 있다.
❗️ 그런데, 서버는 request와 response를 담당하는 것만으로도 바쁘기 때문에 이 경로를 담당하는 일을 이제 라우터(Router)에게 맡길 예정이다.
: 경로를 담당
서버에서 pathname을 받는 것은 필수적(request를 받는 애가 서버이기 때문에)이나, 경로를 알려주는 것은 라우터가 한다. 따라서, 서버가 pathname을 받아와서(let pathname = url.parse(request.url).pathname), 라우터가 할 일을(console.log('pathname: ' + pathname)) 전달한다.
// router.js
function route(pathname) {
console.log('pathname: ' + pathname);
}
exports.route = route
// server.js
let http = require('http');
let url = require('url');
function start(route) {
function onRequest(request, response) {
let pathname = url.parse(requeste.url).pathname;
route(pathname);
response.writeHead(200, {'Content-Type' : 'text/html'});
rseponse.write('Hello Node.js');
response.end();
}
}
exports.start = start;
// index.js
let server = require('./server');
let router = require('./router');
server.start(router.route)
코드를 잠시 설명하자면, 라우터만 따로 떼내어서 파일을 만들고 export 시켜 주었다. 그럼 이제 router.js는 경로를 안내해주는 역할만 한다.
그리고 server.js의 start()함수의 매개변수(parameter)에 route함수를 넣어 함수를 실행시켜 pathname을 라우터에게 넘겨주었다.
그럼 여기서 의문이 드는 점은 route함수를 어떻게 가져왔을까 하는 것이다.
그것은 index.js에서 server모듈을 가져왔듯, 마찬가지로 router모듈을 가져오고나서 server모듈의 start함수를 실행할때, 인수(argument)로 전달해준다 -> server.start(router.route)
요로코럼 하면 각각의 역할이 잘 정리된다.
❗️ 위에서 말했듯, router은 url에 따라 루트를 정하기만 할 뿐(어디로 갈지 길만 정함), 할 일을 하진 않는다.
그럼 각 경로(route)에서 할 일을 누가 하는건가?
할 일을 하는 아이를 또 만들어준다☺️
: server은 request를 받고, response를 보내는 역할을 하고 있고, router은 길을 정하고 있다. 그렇기 때문에 각 루트에서 할 일을 하는 모듈을 만들어줘야 한다.
// requsetHandler.js
function main() {
console.log('main');
}
function login() {
console.log('login');
}
let handle = {}; // key:value
handle['/'] = main;
handle['/login'] = login;
// 사전처럼 해석됨 -> handle의 / 뜻이 뭐야? (키) : main (값) 이야 라는 뜻
// '/ ' 라는 path를 찾아가면 오른쪽 값이 들어 있다는 뜻
exports.handle = handle;
// server.js
let http = require('http');
let url = require('url');
function start(route, handle) {
function onRequest(request, response){
let pathname = url.parse(request.url).pathname;
route(pathname, handle);
response.writeHead(200, {'Content-type' : 'text/html'});
response.write('Hello Node.js');
response.end();
}
http.createServer(onRequest).listen(8888);
}
exports.start = start;
// index.js
let server = require('./server');
let router = require('./router');
let requestHandler = require('./requestHandler');
server.start(router.route, requesteHandler.handle);
// router.js
function route(pathname, handle) {
handle[pathname]();
// handle은 변수로 지정했으나 export되면서 누군가가 이 값을 호출할 수 있게 함수처럼 쓰였음
}
exports route = route;
이 코드들을 설명하자면, handler을 만들어서 할 일을 할수 있는 모듈을 만들어주었다. url에 따라 콘솔이 다르게 찍히도록 만들었다.
위에서 고도화 한 것처럼 start()함수의 매개변수에 handle을 넣어주고(start(route, handle)), 마찬가지로 이 handle도 index.js에서 requesteHandle 모듈을 불러와서 start()의 인수에 넣어주었다.(server.start(router.route, requestHandler.handle))
start() 함수의 매개변수 handle을 이용해 route() 함수에 pathname, handle을 넘겨주면 handle()함수를 실행시켜, pathname을 requestHandler.js에 전달해준다
그런데 나의 경우에는, 자꾸 handle[pathname]가 함수가 아니라는 에러가 나와서 구글링 해보았다. pathname에 /favicon.ico가 찍히는 것을 볼 수 있었는데, 이 favicon은 웹 브라우저가 방문시 자동으로 요청하는 아이콘 파일이다. 그러나 requestHandler.js 파일에는 /favicon.ico 경로에 대한 로직이 없어서 오류가 발생하는 것이었다.(참조: https://velog.io/@hehe/Node.js-TypeError-handlepathname-is-not-a-function-%EC%98%A4%EB%A5%98)
// router.js
function route(pathname, handle) {
if (typeof handle[pathname] === "function") {
handle[pathname]();
}
}
exports.route = route;
고치고 다시 서버를 실행시켜서 url을 변경해보니 터미널창에 잘 나왔다
결과화면
: 위에서 서버를 실행시키면 계속 같은 화면을 보여준다. 그래서 다르게 보여주게 하기 위해서 requsetHandler에게 일을 시키려고한다.
// server.js
let http = require('http');
let url = require('url');
function start(route, handle) {
function onRequest(request, response) {
let pathname = url.parse(request.url).pathname;
route(pathname, handle, response);
}
http.createServer(onRequest).listen(8888);
}
exports.start = start;
// router.js
function route(pathname, handle, response) {
handle[pathname](response);
}
exports.route = route;
// requestHandler.js
function main(response) {
console.log('main');
response.writeHead(200, {'Content-Type' : 'text/html'});
response.write('Main Page');
response.end();
}
function login(response) {
console.log('login');
response.writeHead(200, {'Content-Type' : 'text/html'});
response.write('Login Page');
response.end();
}
let handler = {}; // key:value
handle['/'] = main;
handle['/login'] = login;
exports.handle = handle;
onRequest()의 매개변수 response를 route()의 인수에 전달해준다. 그리고 router.js는 이것을 받아서 다시 handle()함수의 인수에 전달해준다. 그러면 requestHandler.js는 각각의 할일을 하는 함수의 매개변수로 들어가서 클라이언트 소통하기 위한 일을 해준다.
결과화면
❗️ 그렇다면 없는 url을 입력하면 어떻게 될까?
error가 뜨고 서버는 죽는다.
이것을 해결해주기 위해 우리는 router.js에 if문을 넣어(이미 넣었지만, else를 추가해 에러메시지를 남기고 서버를 살리자) error메시지를 넣어 주고 서버를 살릴 것이다.
// router.js
function route(pathname, handle, response) {
if(type of handle[pathname] == 'function') {
handle[pathname](response);
} else {
response.writeHead(404, {'Content-Type' : 'text/html'});
response.write('Not Found');
response.end();
}
}
exports.route = route;
결과화면
테니스마켓 메인페이지 만들기
start()함수 실행시켜서 내 이름 보이게 만들기