웹 서비스 개발(FB,BE,SERVER,DB)/Node.js

4. Node.js WebServer

Zoo_10th 2024. 3. 12.

1. 요청(Requests)과 응답(Response)

1-1. 서버와 클라이언트

서버와 클라이언트 관계는 인터넷이나 네트워크 기반의 시스템에서 흔히 볼 수 있는 중요한 개념이다. 이 관계는 요청(request)과 응답(response)을 기반으로 하는 통신 방식으로 이루어진다.

 - 서버(Server)

1) 역할 : 클라이언트의 요청을 수신하고, 이를 처리하여 적절한 응답을 반환한다.

2) 특징 : 일반적으로 하나의 서버는 동시에 여러 클라이언트의 요청을 처리할 수 있다.

3) 예시 : 웹 서버, 데이터베이스 서버, 메일 서버 등

 - 클라이언트(Client)

1) 역할 : 서버에 서비스나 데이터를 요청하고, 서버로부터의 응답을 받는다.

2) 특징 : 사용자 인터페이스를 제공하며, 사용자의 입력을 서버에 요청으로 전달한다.

3) 예시 : 웹 브라우저, 모바일 앱, 데스크탑 애플리케이션 등

 - 요청과 응답 과정

1) 요청(Request): 클라이언트가 서버에 어떤 작업을 수행해달라고 요청한다. 이 요청은 데이터를 검색하거나, 데이터베이스에 정보를 저장하거나, 계산을 수행하는 등 다양한 형태를 가질 수 있다.

2) 처리: 서버는 클라이언트의 요청을 받아서 처리한다. 이 과정에서 데이터베이스 조회, 파일 시스템 접근, 계산 수행 등이 이루어질 수 있다.

3) 응답(Response): 서버는 처리된 결과를 클라이언트에게 응답으로 보낸다. 응답은 요청된 작업의 결과, 데이터, 상태 코드, 에러 메시지 등을 포함할 수 있다.

1-2. HTTP Server

Node.js에서 HTTP 서버를 구축하는 것은 간단하며, http 모듈을 사용하여 기본적인 웹 서버를 구성할 수 있다. 과정에서 req (Request) 객체와 res (Response) 객체를 사용하여 클라이언트의 요청을 처리하고 응답을 보내는 방식으로 작동한다.

 - HTTP 서버 구축 방법

1) 서버 생성: http.createServer() 메서드를 호출하여 서버 객체를 생성한다. 이 메서드는 클라이언트의 요청을 처리할 콜백 함수를 인자로 받는다.

2) 요청 처리 (req 객체): 클라이언트로부터의 요청은 req 객체를 통해 접근할 수 있다. 이 객체는 요청 URL, HTTP 메서드, 헤더 등의 정보를 담고 있다.

3) 응답 전송 (res 객체):

 (1) res.write(): 응답 본문에 데이터를 추가한다. 여러 번 호출하여 여러 조각의 데이터를 전송할 수 있다.

 (2) res.end(): 응답을 마무리한다. 이 메서드는 선택적으로 마지막 데이터 조각을 전송할 수 있으며, 호출되면 응답이 클라이언트에게 전송된다.

4) 서버 실행: listen(port) 메서드를 호출하여 서버가 특정 포트에서 클라이언트의 요청을 기다리도록 한다.

const http = require('http');

const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
  res.write('<h1>Hello Node!</h1>');
  res.end('<p>Hello Server!</p>');
});
server.listen(8080);

server.on('listening', () => {
  console.log('8080번 포트에서 서버 대기 중입니다!');
});
server.on('error', (error) => {
  console.error(error);
	process.exit()
});

 - Localhost와 포트

1) localhost: 컴퓨터의 내부 주소를 나타내며, 일반적으로 127.0.0.1로 표현된다. 이 주소는 해당 컴퓨터 내부에서만 접근할 수 있다.

2) 포트: 서버 내에서 실행되는 개별 프로세스를 구분하는 번호입니다. HTTP 서버는 기본적으로 80번 포트를 사용하고, HTTPS는 443번 포트를 사용한다.

 - 여러개의 서버 실행

1) 하나의 Node.js 애플리케이션에서 여러 개의 HTTP 서버를 실행할 수 있다. 이를 위해 createServer를 여러 번 호출하고, 각 서버를 다른 포트 번호로 실행해야 한다.

2) 만약 같은 포트 번호에서 여러 서버를 실행하려고 시도하면, EADDRINUSE (주소가 이미 사용 중임) 에러가 발생한다.

const http = require('http');

http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
  res.write('<h1>Hello Node!</h1>');
  res.end('<p>Hello Server!</p>');
})
  .listen(8080, () => { // 서버 연결
    console.log('8080번 포트에서 서버 대기 중입니다!');
  });

http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
  res.write('<h1>Hello Node!</h1>');
  res.end('<p>Hello Server!</p>');
})
  .listen(8081, () => { // 서버 연결
    console.log('8081번 포트에서 서버 대기 중입니다!');
  });

 - HTML 읽어서 전송

Node.js에서 HTML 파일을 읽어 클라이언트에게 전송하는 것은 fs 모듈을 사용하여 구현할 수 있다. 이 방법은 HTML 파일을 서버에 저장하고, 클라이언트의 요청에 따라 해당 파일을 읽어 응답으로 전송한다. 이는 write 메서드가 버퍼를 전송할 수 있다는 특성을 활용한다.

 - HTML 파일 전송 과정

1) HTML 파일 준비: 서버가 전송할 HTML 파일을 준비한다. 이 파일은 서버의 파일 시스템에 저장되어 있어야 한다.

2) fs 모듈 사용: fs 모듈을 사용하여 HTML 파일을 비동기적으로 읽는다.

3) 응답 객체에 파일 내용 전송: 읽은 HTML 파일의 내용을 응답 객체(res)를 통해 클라이언트에게 전송한다.

const http = require('http');
const fs = require('fs').promises;

http.createServer(async (req, res) => {
  try {
    const data = await fs.readFile('./server2.html');
    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
    res.end(data);
  } catch (err) {
    console.error(err);
    res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
    res.end(err.message);
  }
})
  .listen(8081, () => {
    console.log('8081번 포트에서 서버 대기 중입니다!');
  });
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Node.js 웹 서버</title>
</head>
<body>
    <h1>Node.js 웹 서버</h1>
    <p>만들 준비되셨나요?</p>
</body>
</html>

 - 주의 사항

1) 파일을 읽는 작업은 I/O 작업이므로 비동기적으로 처리하는 것이 좋다. 이를 위해 fs.readFile을 사용하거나, 스트림을 사용하여 파일을 읽을 수도 있다.

2) 에러 처리: 파일 읽기 과정에서 오류가 발생할 수 있으므로 적절한 에러 처리를 구현해야 한다.

3) Content-Type: 응답 헤더에 Content-Type을 text/html로 설정하여 클라이언트가 내용을 HTML로 해석하도록 한다.

2. REST API

2-1. REST API

REST API (Representational State Transfer API)는 네트워크 상에서 클라이언트와 서버 간의 통신을 위한 아키텍처 스타일이다. 이는 웹의 기본 프로토콜인 HTTP를 사용하여 서버의 자원에 접근하고 이를 활용하는 방법을 정의한다.

 - REST API의 주요 개념

1) 자원(Resource): REST API에서 자원은 서버에서 제공하는 데이터나 서비스를 의미한다. 각 자원은 고유한 URI(Uniform Resource Identifier)로 식별된다. 예를 들어, /users는 사용자 정보에 관한 자원을, /posts는 게시글에 관련된 자원을 나타낼 수 있다.

2) 메서드(Method): REST API는 HTTP의 메서드를 사용하여 자원에 대한 다양한 작업을 정의한다.

 * GET: 서버로부터 자원을 조회하기 위해 사용됩니다. 데이터를 받아오는 데에 사용된다.

 * POST: 새로운 자원을 생성할 때 사용된다. 예를 들어, 새로운 사용자 등록, 새 게시글 작성 등에 사용된다.

 * PUT: 자원의 전체를 교체하기 위해 사용된다. 주어진 자원의 전체 내용을 업데이트할 때 사용된다.

 * PATCH: 자원의 일부를 수정하기 위해 사용됩니다. 자원의 일부 속성만 업데이트할 때 유용하다.

 * DELETE: 자원을 삭제하기 위해 사용된다.

 - REST API 설계 원칙

1) 주소는 명사로 표현: 자원을 식별하는 주소는 동사보다는 명사를 사용한다.

2) 계층적 구조: 리소스 간의 관계를 계층적으로 표현한다. 예를 들어, /users/123/posts는 사용자 123의 게시글을 나타낼 수 있다.

3) 상태 코드 활용: 응답에는 HTTP 상태 코드를 활용하여 작업의 성공, 실패 등의 상태를 전달한다. 예를 들어, 200은 성공, 404는 자원을 찾을 수 없음, 500은 서버 에러 등을 나타낸다.

 - RESTful API

HTTP 프로토콜을 사용하는 RESTful 서비스는 다양한 클라이언트(웹, iOS, 안드로이드 등)와의 통신을 표준화된 방법으로 가능하게 한다. 이러한 접근 방식은 서버와 클라이언트의 분리를 용이하게 하며, 다양한 플랫폼에서 동일한 서비스를 제공할 수 있는 기반을 마련한다.

 - 서버와 클라이언트의 분리

1) 플랫폼 독립성: RESTful 아키텍처를 사용하면, 서버는 플랫폼에 구애받지 않고 동일한 인터페이스(API)를 제공한다. 따라서 iOS, 안드로이드, 웹 브라우저 등 어떤 클라이언트에서도 동일한 방식으로 서버와 소통할 수 있다.

2) 개발 및 유지보수의 용이성 : 서버와 클라이언트가 분리되어 있기 때문에, 각각 독립적으로 개발하고 유지보수할 수 있다. 서버의 변경이 클라이언트에 미치는 영향을 최소화할 수 있으며, 클라이언트는 서버의 변경사항에 신속하게 대응할 수 있다. 

 - HTTP 요청

HTTP 프로토콜은 웹에서 데이터를 주고받기 위한 기본적인 방법을 제공하며, 여기에는 여러가지 HTTP 메서드가 포함된다. 각 메서드는 웹서버에 대한 다른 유형의 요청을 나타내며, 서버는 이에 따라 다르게 반응한다. 주요 HTTP 메서드에는 GET, POST, DELETE, PUT, PATCH가 있다.

 - GET

1) 용도 : 서버로부터 정보를 조회하기 위해 사용된다.

2) 작동 방식 : 데이터를 서버에서 클라이언트로 전송한다. URL에 쿼리 문자열을 포함시켜 요청할 수 있다.

3) 특징 : 데이터를 변경하지 않는 안전한(safe) 요청으로 간주된다.

- POST

1) 용도 : 서버에 데이터를 제출하여 생성(또는 업데이트)하기 위해 사용된다.

2) 작동 방식 : 요청 본문에 데이터를 담아 서버에 전송한다.

3) 특징 : 데이터를 생성하거나 변경할 때 주로 사용된다. 멱등하지 않는다.(동일한 요청을 여러번 보내면 다른 결과가 발생할 수 있음)

- DELETE

1) 용도 : 서버의 특정 리소스를 삭제하고자 할 때 사용된다.

2) 작동 방식 : 삭제할 리소스의 URI를 지정한다.

3) 특징 : 서버의 데이터를 변경(삭제)한다. 멱등한 요청이다.(동일한 요청을 여러번 해도 동일한 결과가 발생한다.)

- PUT

1) 용도 : 서버의 특정 리소스를 지정한 데이터로 전체 교체하기 위해 사용된다.

2) 작동 방식 : 요청 본문에 전체 새 데이터를 담아 전송한다.

3) 특징 : 지정된 URI에 해당하는 데이터를 완전히 대체한다. 멱등한 요청이다.

- PATCH

1) 용도 : 서버의 특정 리소스의 일부를 수정하고자 할 때 사용된다. 

2) 작동 방식 : 요청 본문에 변경할 데이터의 일부만 담아 전송한다.

3) 특징 : PUT과 달리 리소스의 일부만 수정한다. 멱등성은 구현에 따라 다를 수 있다.

 - RESTful 서비스의 예

1) GET /user: 이 요청은 사용자 정보를 조회하는 용도로 사용된다. 클라이언트는 이 주소로 GET 요청을 보내 사용자 정보를 요청한다.

2) POST /user: 이 요청은 새로운 사용자를 등록하기 위한 용도로 사용된다. 클라이언트는 이 주소로 POST 요청과 함께 사용자 데이터를 서버에 전송한다.

 - RESTful 서비스 장점

1) 단순성과 일관성: RESTful 서비스는 HTTP 표준 메서드를 사용하므로, API의 단순성과 일관성을 유지할 수 있다.

2) 확장성: 다양한 클라이언트와의 호환성 덕분에 서비스의 확장성이 높아진다.

3) 캐싱 가능성: HTTP 프로토콜의 캐싱 기능을 활용하여 성능을 향상시킬 수 있다.

const http = require('http');
const fs = require('fs').promises;
const path = require('path');

const users = {}; // 데이터 저장용

http.createServer(async (req, res) => {
  try {
    if (req.method === 'GET') {
      if (req.url === '/') {
        const data = await fs.readFile(path.join(__dirname, 'restFront.html'));
        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
        return res.end(data);
      } else if (req.url === '/about') {
        const data = await fs.readFile(path.join(__dirname, 'about.html'));
        res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
        return res.end(data);
      } else if (req.url === '/users') {
        res.writeHead(200, { 'Content-Type': 'application/json; charset=utf-8' });
        return res.end(JSON.stringify(users));
      }
      // /도 /about도 /users도 아니면
      try {
        const data = await fs.readFile(path.join(__dirname, req.url));
        return res.end(data);
      } catch (err) {
        // 주소에 해당하는 라우트를 못 찾았다는 404 Not Found error 발생
      }
    } else if (req.method === 'POST') {
      if (req.url === '/user') {
        let body = '';
        // 요청의 body를 stream 형식으로 받음
        req.on('data', (data) => {
          body += data;
        });
        // 요청의 body를 다 받은 후 실행됨
        return req.on('end', () => {
          console.log('POST 본문(Body):', body);
          const { name } = JSON.parse(body);
          const id = Date.now();
          users[id] = name;
          res.writeHead(201, { 'Content-Type': 'text/plain; charset=utf-8' });
          res.end('등록 성공');
        });
      }
    } else if (req.method === 'PUT') {
      if (req.url.startsWith('/user/')) {
        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;
          res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
          return res.end(JSON.stringify(users));
        });
      }
    } else if (req.method === 'DELETE') {
      if (req.url.startsWith('/user/')) {
        const key = req.url.split('/')[2];
        delete users[key];
        res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
        return res.end(JSON.stringify(users));
      }
    }
    res.writeHead(404);
    return res.end('NOT FOUND');
  } catch (err) {
    console.error(err);
    res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
    res.end(err.message);
  }
})
  .listen(8082, () => {
    console.log('8082번 포트에서 서버 대기 중입니다');
  });

restServer.js

async function getUser() { // 로딩 시 사용자 가져오는 함수
  try {
    const res = await axios.get('/users');
    const users = res.data;
    const list = document.getElementById('list');
    list.innerHTML = '';
    // 사용자마다 반복적으로 화면 표시 및 이벤트 연결
    Object.keys(users).map(function (key) {
      const userDiv = document.createElement('div');
      const span = document.createElement('span');
      span.textContent = users[key];
      const edit = document.createElement('button');
      edit.textContent = '수정';
      edit.addEventListener('click', async () => { // 수정 버튼 클릭
        const name = prompt('바꿀 이름을 입력하세요');
        if (!name) {
          return alert('이름을 반드시 입력하셔야 합니다');
        }
        try {
          await axios.put('/user/' + key, { name });
          getUser();
        } catch (err) {
          console.error(err);
        }
      });
      const remove = document.createElement('button');
      remove.textContent = '삭제';
      remove.addEventListener('click', async () => { // 삭제 버튼 클릭
        try {
          await axios.delete('/user/' + key);
          getUser();
        } catch (err) {
          console.error(err);
        }
      });
      userDiv.appendChild(span);
      userDiv.appendChild(edit);
      userDiv.appendChild(remove);
      list.appendChild(userDiv);
      console.log(res.data);
    });
  } catch (err) {
    console.error(err);
  }
}

window.onload = getUser; // 화면 로딩 시 getUser 호출
// 폼 제출(submit) 시 실행
document.getElementById('form').addEventListener('submit', async (e) => {
  e.preventDefault();
  const name = e.target.username.value;
  if (!name) {
    return alert('이름을 입력하세요');
  }
  try {
    await axios.post('/user', { name });
    getUser();
  } catch (err) {
    console.error(err);
  }
  e.target.username.value = '';
});

restFront.js

<!DOCTYPE html>
<html lang="ko">
<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="https://unpkg.com/axios/dist/axios.min.js"></script>
<script src="./restFront.js"></script>
</body>
</html>

restFront.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>

about.html

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

restFront.css

728x90

'웹 서비스 개발(FB,BE,SERVER,DB) > Node.js' 카테고리의 다른 글

6. Express_Server  (1) 2024.03.15
5. Node.js WebServer 2  (0) 2024.03.13
3. Node.js_Basic2  (0) 2024.03.11
2. Node.js_Basic  (0) 2024.03.11
0. Node.js 설치하기  (0) 2024.03.11

댓글