4장 http모듈로 웹 서버만들기(후속)

정규준·2020년 5월 22일
0

4.3 REST API와 라우팅

서버에 요청을 보낼 때는 주소를 통해 요청의 내용을 표현함.

주소가 /index.html이면 서버의 index.html을 보내달라는 뜻임.

요청이 항상 html을 요구할 필요는 없음. 여기서 서버가 이해하기 쉬운 주소를 사용하는 것이 좋음으로 REST API가 등장함.

-REST API

Representational State Trasnfer의 약어로, 네트워크 구조의 한형식으로, 서버의 자원을 정의하고, 자원에 대한 주소를 지정하는 방법.
주소는 의미를 명확히 전달하기 위해 명사로 구성.

REST API를 따르는 서버를 RESTful하다고 표현함.

REST API 주소 외에도 HTTP 요청 메서드라는 것을 사용함.

폼데이터 전송시에는 GET, POST 등 일반적으로 사용하며 PUT, PATCH, DELETE 등 주로 5개가 많이 사용됨.

GET: 서버자원을 가져오고자 할 때, 서버로 데이터를 보낼 시 쿼리스트링을 사용
POST: 서버에 자원을 새로 등록하고자 할 때, 요청 본문에 데이터를 넣어서 보냄.
PUT: 서버의 자원을 요청에 들어있는 자원으로 치환하고자 할 때, 본문에 넣음.
PATCH: 서버 자원의 일부만 수정하고자 할 때, 본문에 넣음.
DELETE: 서버 자원을 삭제하고자 할 때 사용.

따라서 HTTP 프로토콜을 사용하면 클라이언트가 누구든 서버와 소통할 수 있음. 즉, 클라이언트와 서버가 분리되어 있다고 말 할 수 있음.

-restFont.css

a {
  color: blue;
  text-decoration: none;
}

-restFront.htl

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>RESTful SERVER</title>
    <link rel="stylesheet" href="./restFront.css" />
</head>
<body>
<nav>
    <a href="/">Home</a>
    <a href="/about">About</a>
</nav>
<div>
    <form id="form">
        <input type="text" id="username">
        <button type="submit">등록</button>
    </form>
</div>
<div id="list"></div>
<script src="./restFront.js"></script>
</body>
</html>

-restFront.js

function getUser() { // 로딩 시 사용자 가져오는 함수
  var xhr = new XMLHttpRequest();
  xhr.onload = function () {
    if (xhr.status === 200) {
      var users = JSON.parse(xhr.responseText);
      var list = document.getElementById('list');
      list.innerHTML = '';
      Object.keys(users).map(function (key) {
        var userDiv = document.createElement('div');
        var span = document.createElement('span');
        span.textContent = users[key];
        var edit = document.createElement('button');
        edit.textContent = '수정';
        edit.addEventListener('click', function () { // 수정 버튼 클릭
          var name = prompt('바꿀 이름을 입력하세요');
          if (!name) {
            return alert('이름을 반드시 입력하셔야 합니다');
          }
          var xhr = new XMLHttpRequest();
          xhr.onload = function () {
            if (xhr.status === 200) {
              console.log(xhr.responseText);
              getUser();
            } else {
              console.error(xhr.responseText);
            }
          };
          xhr.open('PUT', '/users/' + key);
          xhr.setRequestHeader('Content-Type', 'application/json');
          xhr.send(JSON.stringify({ name: name }));
        });
        var remove = document.createElement('button');
        remove.textContent = '삭제';
        remove.addEventListener('click', function () { // 삭제 버튼 클릭
          var xhr = new XMLHttpRequest();
          xhr.onload = function () {
            if (xhr.status === 200) {
              console.log(xhr.responseText);
              getUser();
            } else {
              console.error(xhr.responseText);
            }
          };
          xhr.open('DELETE', '/users/' + key);
          xhr.send();
        });
        userDiv.appendChild(span);
        userDiv.appendChild(edit);
        userDiv.appendChild(remove);
        list.appendChild(userDiv);
      });
    } else {
      console.error(xhr.responseText);
    }
  };
  xhr.open('GET', '/users');
  xhr.send();
}
window.onload = getUser; // 로딩 시 getUser 호출
// 폼 제출
document.getElementById('form').addEventListener('submit', function (e) {
  e.preventDefault();
  var name = e.target.username.value;
  if (!name) {
    return alert('이름을 입력하세요');
  }
  var xhr = new XMLHttpRequest();
  xhr.onload = function () {
    if (xhr.status === 201) {
      console.log(xhr.responseText);
      getUser();
    } else {
      console.error(xhr.responseText);
    }
  };
  xhr.open('POST', '/users');
  xhr.setRequestHeader('Content-Type', 'application/json');
  xhr.send(JSON.stringify({ name: name }));
  e.target.username.value = '';
});
  • restServer.js
const http = require('http');
const fs = require('fs');

const users = {};

http.createServer((req, res) => {
  if (req.method === 'GET') {
    if (req.url === '/') {
      return fs.readFile('./restFront.html', (err, data) => {
        if (err) {
          throw err;
        }
        res.end(data);
      });
    } else if (req.url === '/about') {
      return fs.readFile('./about.html', (err, data) => {
        if (err) {
          throw err;
        }
        res.end(data);
      });
    } else if (req.url === '/users') {
      return res.end(JSON.stringify(users));
    }
    return fs.readFile(`.${req.url}`, (err, data) => {
      if (err) {
        res.writeHead(404, 'NOT FOUND');
        return res.end('NOT FOUND');
      }
      return res.end(data);
    });
  } else if (req.method === 'POST') {
    if (req.url === '/users') {
      let body = '';
      req.on('data', (data) => {
        body += data;
      });
      return req.on('end', () => {
        console.log('POST 본문(Body):', body);
        const { name } = JSON.parse(body);
        const id = +new Date();
        users[id] = name;
        res.writeHead(201);
        res.end('등록 성공');
      });
    }
  } else if (req.method === 'PUT') {
    if (req.url.startsWith('/users/')) {
      const key = req.url.split('/')[2];
      let body = '';
      req.on('data', (data) => {
        body += data;
      });
      return req.on('end', () => {
        console.log('PUT 본문(Body):', body);
        users[key] = JSON.parse(body).name;
        return res.end(JSON.stringify(users));
      });
    }
  } else if (req.method === 'DELETE') {
    if (req.url.startsWith('/users/')) {
      const key = req.url.split('/')[2];
      delete users[key];
      return res.end(JSON.stringify(users));
    }
  }
  res.writeHead(404, 'NOT FOUND');
  return res.end('NOT FOUND');
})
  .listen(8085, () => {
    console.log('8085번 포트에서 서버 대기중입니다');
  });
  • about.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>RESTful SERVER</title>
    <link rel="stylesheet" href="./restFront.css" />
</head>
<body>
<nav>
    <a href="/">Home</a>
    <a href="/about">About</a>
</nav>
<div>
    <h2>소개 페이지입니다.</h2>
    <p>사용자 이름을 등록하세요!</p>
</div>
</body>
</html>

4.5 Cluster

Cluster 모듈은 싱글스레드인 노드가 CPU코어를 모두 사용할 수 있게 해주는 모듈이다.

포트를 공유하는 노드 프로세스를 여러 개 둘 수도 있고, 요청이 많이 들어왔을 경우 병렬로 실행된 서버의 개수만큼 요청이 분산되게 할 수도 있음.

Ex) 코어가 8개인 서버가 있으면 노드는 코어를 하나만 활용함. 하지만 Cluster 모듈을 설정하여 코어 하나당 노드 프로세스 하나가 돌아가게도 할 수 있음.

따라서 상황에 유연하게 대처가 필요하며 코어 하나만 사용하는 것보다는 성능이 개선되지만 세션을 공유하지 못하는 등 단점도 따른다.

  • cluster.js
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  console.log(`마스터 프로세스 아이디: ${process.pid}`);
  // CPU 개수만큼 워커를 생산
  for (let i = 0; i < numCPUs; i += 1) {
    cluster.fork();
  }
  // 워커가 종료되었을 때
  cluster.on('exit', (worker, code, signal) => {
    console.log(`${worker.process.pid}번 워커가 종료되었습니다.`);
    cluster.fork();
  });
} else {
  // 워커들이 포트에서 대기
  http.createServer((req, res) => {
    res.write('<h1>Hello Node!</h1>');
    res.end('<p>Hello Cluster!</p>');
    setTimeout(() => {
      process.exit(1);
    }, 1000);
  }).listen(8085);

  console.log(`${process.pid}번 워커 실행`);
}
profile
JeongGJ__K

0개의 댓글