본문 바로가기
TIL with Programmers

[TIL] 8/22 클라이언트-서버, Node.js, HTTP, request, reponse, URL, 라우터, 핸들러

by 보먀 2024. 8. 22.
728x90
반응형

백엔드

 

1. 웹 서버의 역할

 

정적 페이지

-> 화면의 내용/데이터 등에 변동이 없는 페이지 (늘 같은 내용을 보여줌)

-> 사용자와 상호작용 x, 데이터베이스와 소통 x

 

동적 페이지

-> 데이터 퍼리/연산을 통해 화면의 내용/데이터가 변하는 페이지 

-> 사용자와 상호작용 o, 데이터베이스와 소통 o

 

웹 서버(Web Server) 는 정적 페이지에 대해 대응하고, 

동적 페이지에 대한 처리는 직접 처리하지 않고, 웹 어플리케이션 서버에게 전달한다. 

 

출처: https://github-wiki-see.page/m/HeegonKim/IT-Info/wiki/%E3%85%81-WEB-%EA%B5%AC%EC%A1%B0,-WS,WAS-%EA%B0%9C%EB%85%90

 

2. Node.js 란?

  • Node.js 는 자바스크립트를 스크립트 언어 이상으로 프로그래밍 언어 역할을 할 수 있도록 지원하는 플랫폼
  • Node.js 를 이용해서 자바스크립트로 백엔드를 구현할 수 있음

(플랫폼은 어떠한 것도 띄워 놓을 수 있는 운동장이라고 생각하면 됨)

 

Node.js 로 웹 어플리케이션 서버를 만들 수 있고, 웹 서버의 역할도 할 수 있다. 

 

 

3. Node.js 로 웹서버 만들기

 

require() -> node.js 가 가지고 있는 모듈을 불러오는데 사용되는 함수

 

이렇게 코드를 짜면 아주아주 간단한 서버가 만들어진다. 

터미널에 node server.js 를 치면 서버가 띄워지고, localhost:8888 로 들어가면 서버에 들어가 볼 수 있다. 이제 코드를 파헤쳐보자.

// server.js
let http = require('http');

function onRequest(request, response) {
    response.writeHead(200, {'Content-Type' : 'text/html'});
    response.write('Hello Node.js');
    response.end();
}

http.createServer(onRequest).listen(8888); // 이제 서버가 만들어짐

 

http 라는 클라이언트와 서버가 통신을 하는데 사용하는 프로토콜이 있다. 

http 프로토콜을 우리가 활용할 수 있도록 node.js 가 제공하고 있고, require 함수를 이용해서 모듈을 받아올 수 있다. 

let http = require('http');

 

출처: https://github-wiki-see.page/m/HeegonKim/IT-Info/wiki/%E3%85%81-WEB-%EA%B5%AC%EC%A1%B0,-WS,WAS-%EA%B0%9C%EB%85%90

 

아래 코드는 위의 그림 부분이라고 생각하면 된다. 

함수의 인자로 request, response 를 적어 놓으면 node.js 가 알아서 진짜 요청과 응답이 될 수 있도록 해준다.

 

request: 클라이언트로부터 들어오는 요청을 나타내는 객체

response: 서버가 클라이언트로 응답을 보낼 때 사용하는 객체

 

response.writeHead 는 HTTP response 의 헤더를 설정하는 부분이다. 

200 은 HTTP 의 상태코드를 의미하며, 상태코드 200 은 요청이 성공적으로 처리되었음을 의미한다.

 

{'Content-Type': 'text/html'} 은 Content-Type 헤더를 설정하여, 클라이언트가 서버로부터 HTML 형식의 데이터를 받을 것임을 알려준다. 

 

response.write 는 response 바디에 텍스트 데이터를 작성하는 부분이다. 

아래의 코드에서는 'Hello Node.js' 라는 문자열이 클라이언트에게 전송된다. 

 

response.end() 는 reponse 를 클라이언트에게 전송하고 연결을 종료하는 부분이다. 

function onRequest(request, response) {
    response.writeHead(200, {'Content-Type' : 'text/html'});
    response.write('Hello Node.js');
    response.end();
}

 

 

http 모듈을 이용해서 서버를 생성하고, request 가 들어올 때마다 onRequest 함수로 해당 요청을 처리하도록 설정한다.  

그리고 클라이언트와 서버가 포트번호 8888 로 통신하도록 지정한다. 

(무전기로 통신할 때 같은 주파수로 맞춰야하는 것을 생각하면 된다)

http.createServer(onRequest).listen(8888);

 

 

 

3. HTTP 

 

헤드랑 바디가 있고, 헤드에는 여러 정보가 들어있다. 

 

1) status code 

 

HTTP 응답 상태 코드는 특정 HTTP 요청이 성공적으로 완료되었는지 알려주며, 응답은 5개의 그룹으로 나누어진다.

  • 정보를 제공하는 응답 (1xx)
  • 성공적인 응답 (2xx)
  • 리다이렉트 (3xx)
  • 클라이언트 에러 (4xx)
  • 서버 에러 (5xx) 

(더 자세한 상태코드에 대한 내용은 여길 참고하자! -> https://developer.mozilla.org/ko/docs/Web/HTTP/Status)

 

아마 이런 살면서 not found 어쩌고 하는 페이지를 본 적 있을텐데, 여기서 404 가 바로 상태코드.

출처: https://wikidocs.net/81079
출처: https://wikidocs.net/81079

  

 

2) 응답이 어떤 형태인지 알려준다

 

writeHead 는 웹 서버가 클라이언트한테 응답할 때 헤드를 

200 : 상태코드로 OK(성공)을 의미

text/html : response 타입이 html 

write: write body 라고 생각하면 됨 -> 화면을 꾸며줄 데이터를 담음

end: response 담을거 끝 -> 전송!

 

function onRequest(request, response) {
    response.writeHead(200, {'Content-Type' : 'text/html'});
    response.write('Hello Node.js');
    response.end();
}

 

 

 

4. 서버의 모듈화

 

Node.js 가 미리 만들어둔 모듈을 우리가 require('모듈 이름') 이렇게 사용한 것처럼, 우리가 만든 server 도 모듈처럼 다른 js 파일에서 사용할 수 있다. 

// server.js
let http = require('http');
function onRequest(request, response) {
    response.writeHead(200, {'Content-Type' : 'text/html'});
    response.write('Hello Node.js');
    response.end();
}

http.createServer(onRequest).listen(8888);
// index.js
let server = require('./server.js');

 

server.js 와 index.js 파일에 코드를 짜두고, 터미널에서 node index.js 를 입력하면 서버가 띄워진다. 

근데 생각해보면 index.js 파일에서는 서버 모듈을 읽어왔을 뿐인데, 서버가 켜져버렸다. 

(내가 원할 때 켜지는 것이 아님 -> 유연성이 떨어짐)

 

그럼 서버를 원할 때 실행시키려면 어떻게 해야할까?

-> 함수를 만들어서, 그 함수를 호출할 때 함수 안에서 서버가 실행되도록 만들면 됨!

 

자바스크립트에서 만든 함수는 해당 파일 안에서만 함수의 역할을 할 수 있기 때문에, 해당 파일 밖에서 함수를 사용하려면 함수를 export 시켜야 한다. index.js 파일에서 start 함수를 이용해서 서버를 킬 수 있다. 

// server.js

let http = require('http');

function start() {
    function onRequest(request, response) { // 서버를 만들고 실행시키는 부분을 start 함수 안으로 넣음
        response.writeHead(200, {'Content-Type' : 'text/html'});
        response.write('Hello Node.js');
        response.end();
    }
    
    http.createServer(onRequest).listen(8888);
}

exports.start = start; // start 함수 export
// index.js

let server = require('./server.js');
server.start(); // 서버 켜기

 

 

 

5. Url 읽어내기

  • Uniform Resource Locator == Url
  • 인터넷 상에서 웹 페이지가 어디있는지 "위치"를 알려주는 주소 (== 웹 페이지 주소)

 

http://localhost:8888/pathname

  • http 는 사용된 프로토콜
  • 여기서 localhost 는 내 컴퓨터 주소
  • 8888 은 포트번호
  • pathname: 서버에서 특정 리소스를 가리키는 경로

 

url 모듈을 가져와서 url 을 읽어낼 수 있다. 

let url = require('url');

let pathname = url.parse(request.url).pathname;

 

 

 

6. URL 에 따라 다른 콘솔 찍기

 

라우터라는 것이 있다. 

라우터는 들어오는 URL 요청을 분석해서 해당 요청에 맞는 핸들러로 보내주는 역할을 한다. 쉽게 말하면, URL 을 보고 어떤 처리를 하는 곳으로 보낼지 결정하는 안내자라고 생각하면 된다. 

 

예를 들어, /login 이라는 pathname 을 갖고 들어오면 로그인 처리 코드로, /main 이라는 pathname 을 갖고 들어오면 메인 코드로 연결해준다. 

 

 

라우터 사용하기

// index.js

let server = require('./server');
let router = require('./router');

server.start(router.route);
// server.js

let http = require('http');
let url = require('url');

function start(route) {
    function onRequest(request, response) {
        let pathname = url.parse(request.url).pathname; // 리퀘스트 url 의 경로를 확인
        route(pathname); // 인자로 pathname 을 라우터에 보내줌
        
        response.writeHead(200, {'Content-Type' : 'text/html'});
        response.write('Hello Node.js');
        response.end();
    }
    
    http.createServer(onRequest).listen(8888);
}

exports.start = start;
// router.js

function route(pathname) {
    console.log('pathname : ' + pathname); // 인자로 받은 url의 pathname 출력
}

exports.route = route;

 

이렇게 코드를 짜고 url 의 pathname 부분을 바꿔보면, pathname 을 바꿀 때마다 다른 pathname 이 찍히는 것을 확인할 수 있다. 

 

 

핸들러 만들기

 

이제 라우터가 pathname 을 보고 다른 곳으로 보냈을 때, pathname 에 맞는 요청을 처리해줄 수 있는 핸들러를 만들어보자. 

// index.js

let server = require('./server2');
let router = require('./router2');
let requestHandler = require('./requestHandler');

server.start(router.route, requestHandler.handle);
// server.js

let http = require('http');
let url = require('url');
const { handle } = require('./requestHandler2');

function start(route) {
    function onRequest(request, response) {
        let pathname = url.parse(request.url).pathname; // 리퀘스트 url 의 경로를 확인
        route(pathname, handle);
        
        response.writeHead(200, {'Content-Type' : 'text/html'});
        response.write('Hello');
        response.end();
    }
    
    http.createServer(onRequest).listen(8000);
}

exports.start = start;
// router.js

function route(pathname, handle) {
    console.log('pathname : ' + pathname);

    handle[pathname]();
}

exports.route = route;
// requestHandler.js

function main(response) { 
    console.log('main');
}

function login(response) {
    console.log('login');
}

let handle = {}; // key:value
// pathname(경로)에 맞게 처리
handle['/'] = main; 
handle['/login'] = login;

exports.handle = handle;

 

 

근데 이렇게 코드를 짜면 문제가 있다. 만약 지정해놓지 않은 경로를 입력하는 순간 에러가 나면서 서버가 꺼져버린다.

그렇기 때문에 잘못된 경로가 들어온 경우를 처리해주는 부분이 필요하다. 404 에러 처리를 알아보자. 

 

404 처리

 

어떤 pathname 이 들어왔을 때, 들어온 pathname 이 handle 객체 데이터의 key 값과 일치하는 것이 없을 때 undefined 가 반환되기 때문에 TypeError: handle[pathname] is not a function 이라는 에러와 함께 서버가 꺼지게 된다. 

// router.js

function route(pathname, handle) {
    console.log('pathname : ' + pathname);

    handle[pathname](); 
}

exports.route = route;

 

그래서 들어온 handle[pathname] 의 타입을 검사해서

함수라면 pathname 에 따른 처리를 실행하고, 함수가 아니라면 404 에러처리를 해주면 된다. 

function route(pathname, handle, response) {
    console.log('pathname : ' + pathname);

    // 에러처리
    if (typeof handle[pathname] == 'function') {
        handle[pathname](response);
    } else {
        response.writeHead(404, {'Content-Type' : 'text/html'});
        response.write('not found');
        response.end();
    }
}

exports.route = route;

 

 

 

URL 에 따라 프론트엔드에 다른 response 보내기

 

지금까지는 URL 이 바뀌어도 계속 같은 화면을 유지했는데, URL 이 바뀔 때 화면도 바꿔주고 싶다면 어떻게 해야하는지 알아보자.

// index.js

let server = require('./server');
let router = require('./router');
let requestHandler = require('./requestHandler');

server.start(router.route, requestHandler.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; // 리퀘스트 url 의 경로를 확인
        route(pathname, handle, response);
    }
    
    http.createServer(onRequest).listen(8888); // 이제 서버가 만들어짐
}

exports.start = start; // start 함수 export
// router.js

function route(pathname, handle, response) {
    console.log('pathname : ' + pathname);

    // 에러처리
    if (typeof handle[pathname] == 'function') {
        handle[pathname](response);
    } else {
        response.writeHead(404, {'Content-Type' : 'text/html'});
        response.write('not found');
        response.end();
    }
}

exports.route = route;
// requestHandler.js

// URL 에 따라 화면에 메시지를 바꿔줌
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 handle = {}; // key:value
handle['/'] = main;
handle['/login'] = login;

exports.handle = handle;

 

 

 

정리!

 

왜 한 군데서 처리하지 않고 여러 개의 파일을 만들지? 

-> 파일들이 서로 독립적으로 유연하게 움직일 수 있도록 하기 위해 파일을 분리(역할 분리)

 

역할

  • index.js : 프로젝트의 진입점으로 사용되는 파일, 모듈 소환 + 서버 기동
  • server.js : 클라이언트의 request, response 를 담당
  • router.js : 리퀘스트의 URL 에 따라 루트를 정해줌 = 어디로 갈지 길만 정해줌
  • handler.js : 경로에 따른 처리 실행

 

실습 화면

728x90
반응형