본문 바로가기

Node.js

[코드잇] 초간단 웹서버 만들기

1. 서버와 클라이언트 

 

- 서비스에 관한 요청을 보내는 프로그램 또는 컴퓨터를 클라이언트라고 함

- 클라이언트는 원하는 서비스를 제공해 주는 서버에 요청을 보냄

- 요청을 받은 서버는 그에 알맞은 응답을 해줌

- 클라이언트는 서버로부터 받은 응답을 바탕으로 사용자에게 적절한 화면을 보여줌

 

서버-클라이언트 구조 

 

 

 

2. 웹 서버 만들기 시작

 

- http는 클라이언트인 웹 브라우저가 서버와 통신을 할 때 지켜야 할 프로토콜

- http 코어 모듈을 쓰면 http 프로토콜로 클라이언트와 통신하는 서버를 만들 수 있음

 

const http = require('http');

let server = http.createServer(); 
server.listen(3000);

 

- server에서 외부의 요청을 기다리며 실행되고 있는 프로그램에는 포트번호라는 숫자 값이 할당

- 포트번호는 프로그램마다 서로 다른 값을 갖고 있음

127.0.0.1

- 개발할 때 테스트용으로 사용되는 IP 주소

- 자기 자신의 컴퓨터를 나타내도록 특별히 정해진 주소  

 

 

3. 서버에서 할 응답 정해주기 

 

const http = require('http');

let server = http.createServer(function(request,response) {
	response.end('<h1>Hello world!</h1>');
});

server.listen(3000);

- server 객체에 클라이언트의 요청이 들어올 때마다 실행되는 함수 

- request 인자는 클라이언트의 요청에 관한 객체 

- response 인자는 이 server 객체가 할 응답에 관한 객체 

- 클라이언트 요청이 들어올 때 마다 매번 h1 태그로 감싼 Hello world!를 응답으로 주게 됨

 

 

4. 네트워크 기초 지식

 

1) 프로토콜(Protocol)

 

- 클라이언트와 서버가 서로 통신하기 위해 사용되는 규약

- 클라이언트는 어떤 식으로 데이터를 보내줘야 하고, 서버는 또 어떤 식으로 데이터를 보내줘야 하는지에 관한 규약 

- https는 http보다 좀 더 안전한방식 (s는 secure의 줄임말)의 프로토콜 

 

2) 특수한 IP 주소, 127.0.0.1

 

- 외부의 다른 컴퓨터가 아니라 컴퓨터 자기 자신을 나타내는 주소로 특별히 약속된 주소 

- 외부의 서버와 통신하기 위해 사용되는 것이 아니라 개발자들이 자신이 만든 서버 프로그램을 테스트하기 위한 용도로 사용됨

 

3) 포트(Port) 번호란?

 

- 클라이언트가 서버에 요청을 보내려고 할 때, 서버에서 실행되고 있는 여러 프로그램 중 어떤 프로그램과 통신할 것인지를 나타내기 위해 지정하는 번호 

- 서버에서 실행되는 모든 프로그램 중 클라이언트와의 통신이 필요한 것들은 처음에 '포트 번호'를 할당받은 상태에서 실행됨

- server 객체가 3000번 포트를 할당받아 실행되도록 하는 부분 

server.listen(3000);

 

http://127.0.0.1:3000

- 제 컴퓨터에서(127.0.0.1) 3000번 포트 번호를 가지고 실행되고 있던 (:3000) 서버 프로그램과 http 라는 프로토콜로 통신을 시작하겠다는 뜻 

 

 

5. URL의 구조 

 

- URL은 Uniform Resource Locator의 약자로 웹 상에 존재하는 특정 자원의 위치를 나타내는 문자열

- 특정 자원이란 HTML 파일, CSS 파일, JavaScript 파일 , 이미지 파일, 영상 파일 같은 것들을 의미 

 

- 스킴(scheme)

- 호스트(host)

- 경로(path)

- 쿼리(query)

 

 

6. 도메인 네임과 IP 주소 

 

1) 도메인 네임(Domain Name)

 

- '호스트' 부분에는 example.com과 같은 문자열들이 들어가도 되고, 127.0.0.1 같은 IP 주소를 넣어도 됨 

- www.google.com 이라고  고 치고 접속하면 결국 172.217.175.100라는 IP 주소를 가진 서버에 접속하는 것

 (상황에 따라 IP 주소는 달라짐)

 

- 도메인 네임은 Domain Name System이라고 하는 체계를 바탕으로 생성되는 문자열 이름으로, 인터넷 세계에 존재하는 수많은 영역을 추상적으로 나타내기 위해 사용되는 개념

- 하나의 도메인 네임은 여러 개의 도메인으로 구성됨

- 더 상위의 도메인일수록 도메인 네임 중에서 오른쪽에 있음 

 

- 도메인에는 최상단에 루트 도메인(root domain)이라는 것이 있음 

- 루트 도메인은 인터넷 전체를 나타내는 단위 . 그냥 '빈 문자열'로 나타내고 우리가 도메인 네임을 입력할 때도 표시하지 않는 경우가 많음 

 

- 루트 도메인 바로 하위에는 Top-Level Domain(TLD)이라는 것이 있음 

- 도메인 네임 끝에서 일반적으로 볼 수 있는 .kr, .jp, .net, .gov등이 해당 

- Top-Level Domain은 사이트가 속한 국가 또는 사이트가 제공하는 서비스의 카테고리를 나타냄

 

- 각각의 Top-Level Domain 하위에는 Second- Level Domain이 있음 

- codeit, naver, daum, google 등 서비스를 대표하는 이름 

 

-www.codeit.kr의 www(Third-Level 도메인) test.www.codeit.kr의 test(Fourth-Level 도메인)까지 쭉 도메인이 생겨날 수 있음. 하지만 실제 서비스에서는 보통 Third-Level 도메인 정도까지만 사용 

 

- 도메인 네임을 쓰는 것이 사실은 계층적인 구조를 이루고 있는 각 도메인의 이름을 오른쪽부터(상위 도메인부터), 왼쪽으로 (점점 하위 도메인으로), 점(dot)으로 구분하면서 써나가는 작업임 

 

2) Domain Name Resolution의 원리 

 

- 우리가 브라우저에서 도메인 네임만으로도 특정 컴퓨터와 통신할 수 있는 것은 본격적인 통신을 시작하기 전에 도메인 네임을 IP 주소로 변환해주는 절차가 존재하기 때문

 

ex) 브라우저에 codeit.kr이라고 쓰고 엔터를 치면

 

1단계 :

 일단 내 컴퓨터는 기본적으로 설정된 Name Server에 codeit.kr의 IP 주소를 알려달라는 요청을 보냄

 Name Server라는 건 도메인 네임을 IP 주소로 변환하는 과정에 참여하는 서버들임

 내 컴퓨터에서 맨 처음 어떤 Name Server에 요청할 것인진느 미리 설정되어 있고, 기존의 설정에서 다른 Name Server로 바꾸는 것도 가능함. 내 컴퓨터가 사용하는 Name Server에 관한 설정은 OS마다 다름 

 

2,3 단계 :

 제 컴퓨터에 요청을 받은 Name Server는 이제 Root Name Server에 '.kr'로 끝나는 도메인 네임들을 관리하는 Name Server의 주소를 알려달라는 요청을 보냄. 그럼 Root Name Server는 '.kr' Name Server의 IP 주소를 알려줌

 

4,5 단계 :

 Name Server는 '.kr' Name Server에게 'codeit.kr'의 IP 주소를 알려줄 수 있는, 'codeit.kr' Name Server의 IP 주소를 알려달라고 요청. 그럼 '.kr' Name Server는 'codeit.kr' Name Server의 IP 주소를 알려줌 

 

6,7 단계 :

 그럼 Name Server는 'codeit.kr' Name Server에게 'codeit.kr'의 IP 주소를 알려달라고 요청. 마침내, Name Server는 'codeit.kr' 의 실제 IP 주소를 응답으로 얻게 됨

 

8단계 :

 Name Server는 제 컴퓨터에게 codeit.kr의 IP 주소를 알려주고, 제 컴퓨터는 이 IP 주소를 갖고 코드잇 서버와 본격적인 통신을 시작

 

 

7. URL 해부

 

- URL 클래스로 url이라는 객체 생성

- url 객체의 각 속성을 출력하는 코드 작성

- URL 객체에는 쿼리라는 이름의 속성이 없음. 대신 쿼리와 쿼리 앞에 붙는 물음표를 합친 값을 search 라는 속성으로 갖고 있음

- URL을 쪼개는 방법을 알아야 서버 프로그램 내부에서 클라이언트의 요청으로 들어온 URL을 자유롭게 다룰 수 있음

let url = new URL('http://example.com/business/mart/item?category=14&id=2965');

console.log(url.protocol);
console.log(url.host);
console.log(url.pathname);
console.log(url.search);

 

 

8. 포트를 지정 안 했는데도 접속되는 이유

 

- 특정 프로토콜로 통신을 하는 서버 프로그램은 특정 포트 번호를 사용하도록 정해져 있기 때문

ex) http 프로토콜로 통신하는 서버 프로그램은 포트 번호 80번

     https 프로토콜로 통신하는 서버 프로그램은 포트 번호 443번

 

- 포트 번호를 생략해도 맨 앞의 http, https 같은 프로토콜 정보를 보고 브라우저는 자동으로 그에 맞는 포트 번호를 설정하고 서버에 접속을 시도

 

 

9. 라우팅 해보기 

 
- URL에 따라 서버가 다른 응답을 주도록

- 클라이언트가 요청한 URL은 request 객체의 url이라는 속성에 담겨 있음

- 속성 이름은 url이지만 실제로는 path 이후부터의 문자열만 있음

- URL에서 path 부분을 입력해주지 않으면 브라우저는 요청을 보낼 때 자동으로 /를 붙여줌

- /인 경우는 해당 서비스의 메인 페이지를 보여주는 것이 일반적

const http = require("http");

users = ['Tom', 'Andy', 'Jessica', 'Paul'];

let server - http.createServer(function (request, response) {
   if(request.url === '/') {
     response.end('<h1>Welcom!</h1>');
    }else if(request.url === '/users') {
     response.end('<h1>' + users + '</h1>');
    }else {
     response.end('<h1>Page Not Available</h1>');
});

server.listen(3000);

 

 

10. http 모듈로 라우팅할 때의 불편함

 

- 각각의 회원 이름을 조회하는 URL에 대해 라우팅

- users/1인 경우에는 Tom, users/2인 경우에는 Andy,...

- 문자를 배열로 만들어주는 split 메소드 필요 

const http = require("http");

let users = ['Tom', 'Andy', 'Jessica', 'Paul'];

let server = http.createServer(function (request, response) {
    
  if(request.url === '/') {
    response.end('<h1>Welcome!</h1>');
  } else if (request.url === '/users') {
    response.end('<h1>' + users + '</h1>');
  } else if (request.url.split('/')[1] === 'users') {
    //url : /users/1, /users/2, ..
    //특정 회원 이름 가져오기
    let userIdx = request.url.split('/')[2];
    let userName = users[userIdx-1];
    
    response.end('<h1>' + userName + '</h1>');     
  } else {
    response.end('<h1>Page Not Available</h1>');
  }
});

server.listen(3000);

 

 

11. 권장되는 스타일의 코드로 개선

 

1) 함수를 Arrow Function 형태로 바꾸기 

 

const http = require("http");

let users = ['Tom', 'Andy', 'Jessica', 'Paul'];

let server = http.createServer((request, response) => { // Arrow Function
  if (request.url === '/') {
    response.end('<h1>Welcome!</h1>');
  } else if (request.url === '/users') {
    response.end('<h1>' + users + '</h1>');
  } else if (request.url.split('/')[1] === 'users') {
    //url : /users/1, /users/2, ..
    let userIdx = request.url.split('/')[2];
    let userName = users[userIdx-1];

    response.end('<h1>' + userName + '</h1>');
  } else {
    response.end('<h1>Page Not Available</h1>');
  }
});

server.listen(3000);

 

2) 변수는 가능하다면 최대한 상수로 바꾸기 

 

- 나중에 새로운 것을 대입할 일이 없는 것들은 변수보다는 상수로 설정하는 것이 좋음 

const http = require("http");

const users = ['Tom', 'Andy', 'Jessica', 'Paul']; // const

const server = http.createServer((request, response) => { // Arrow Function, const
  if (request.url === '/') {
    response.end('<h1>Welcome!</h1>');
  } else if (request.url === '/users') {
    response.end('<h1>' + users + '</h1>');
  } else if (request.url.split('/')[1] === 'users') {
    //url : /users/1, /users/2, ..
    const userIdx = request.url.split('/')[2]; // const
    const userName = users[userIdx-1]; // const

    response.end('<h1>' + userName + '</h1>');
  } else {
    response.end('<h1>Page Not Available</h1>');
  }
});

server.listen(3000);

 

3) 변수가 섞인 문자열은 + 기호보다는 템플릿 문자열 사용하기 

 

- 템플릿 문자열은 전체 문자열을 작은 따옴표가 아닌 백틱(`)으로 감싸주고 그 안에 등장하는 변수는 ${} 기호안에 넣음으로써 원하는 문자열을 나타냄

const http = require("http");

const users = ['Tom', 'Andy', 'Jessica', 'Paul']; // const

const server = http.createServer((request, response) => { // Arrow Function, const
  if (request.url === '/') {
    response.end('<h1>Welcome!</h1>');
  } else if (request.url === '/users') {
    response.end(`<h1>${users}/h1>`); // Template String
  } else if (request.url.split('/')[1] === 'users') {
    //url : /users/1, /users/2, ..
    const userIdx = request.url.split('/')[2]; // const
    const userName = users[userIdx-1]; // const

    response.end(`<h1>${userName}</h1>`); // Template String
  } else {
    response.end('<h1>Page Not Available</h1>');
  }
});

server.listen(3000);

 

 

12. express 모듈로 하는 더 세련된 라우팅

 

- Express라고 하는 서드 파티 모듈 사용하기 

- Express는 Node.js 개발자가 아주 많이 사용하는 모듈

- Node.js가 자바스크립트 실행환경이라면 Express는 node.js에서 실행될 서버 프로그램을 간편하게 만들 수 있게 해주는 프레임워크 

- Express 모듈 설치 

 npm install express

 

- app 객체의 get 메소드는 특정 path에 대해서 그 요청과 응답을 다루는 함수를 하나씩 설정하기 위해 쓰는 메소드

- '/users/:id' 

 -> id라는 문자열을 의미하는것이 아님

 -> 이 위치에 오는 값을 id라는 속성에 담으라는 Express의 표기법

 -> 위치에 오는 값은 request 객체의 params객체의 id 속성

const http = require("http");

const express = require('express');

const app = express();

app.get('/', (request, response) => {
  response.end('<h1>Welcom!</h1>');
});

app.get('/users', (request, response) => {
  response.end(`<h1>${users}</h1>`);
});

app.get('/users/:id', (request, response) => {
  response.end(`<h1>${userName}</h1>`);
});

app.get('*', (request, response) => {
  response.end('<h1>Page Not Available</h1>');
});