본문 바로가기

WINK-(Web & App)/Express.js (Node.js) 스터디

[2024 여름방학 Node.js 스터디] 김지나 #3주차

반응형

4장. http 모듈로 서버 만들기

4.1. 요청과 응답 이해하기

- 서버에는 요청을 받는 부분/응답을 보내는 부분 <- 두 개가 있어야 함

- 요청을 받았을 때 수행할 이벤트 리스너를 미리 등록해야 함

 

1) 노드 서버 만들어보기

- createServer 메서드: 인수로 있는 콜백 함수를 요청이 들어올 때마다 실행, 어떻게 응답할지 적으면 됨

- req: request, 요청에 관한 정보

- res: response, 응답에 관한 정보

 

2) 응답을 보내는 부분과 서버 연결

- res.writeHead: 응답에 대한 정보를 기록하는 메서드

- res.write: 클라이언트로 보낼 데이터를 작성하는 메서드 (여러 개 호출, 전송 가능)

- res.end: 응답을 종료하는 메서드(인수 데이터를 클라이언트에게 보내고 종료)

 

- localhost: 현재 컴퓨터의 내부 주소

- IP: 숫자 주소 

- 포트: 서버 내에서 프로세스를 구분하는 번호 

 

- res.write나 res.end에 일일이 html 코드를 작성하기보다는 html 파일을 하나 만들고 fs 모듈로 읽게 하는 게 더 효율적

<!-- server2.html -->
<html>
<head>
	<meta charset="utf-8">
    <title>Node.js 웹 서버</title>
</head>
<body>
	<h1>Node.js 웹 서버</h1>
    <p>만들 준비 갈 완료</p>
</body>
</html>
//server2.js
http.createServer(async (req,res) => {
	try {
    	const data = await fs.readFile('./server2.html');
		res.end(data);
}

 

※ 요청 처리 과정 중에 에러가 발생해도 응답은 보내야 함 

 

4.2. REST와 라우팅 사용하기

- REST: 서버의 자원을 정의하고 자원에 대한 주소를 지정하는 방법

(네트워크에서 통신을 만들 때 이런 규칙은 꼭 지켜라!! 하는 약속...인 것 같습니다)

- 주소는 명사로 구성됨(/user: 사용자 정보 관련 자원 요청, /post: 게시글 관련 자원 요청)

- HTTP 요청 메서드

GET 서버 자원을 가져오고자 할 때 사용
POST 서버에 자원을 새로 등록하고자 할 때 사용
PUT 서버의 자원을 요청에 들어 있는 자원으로 치환하고자 할 때 사용
PATCH 서버 자원의 일부만 수정하고자 할 때 사용
DELETE 서버의 자원을 삭제하고자 할 때 사용
OPTIONS 요청을 하기 전에 통신 옵션을 설명하기 위해 사용

 

- HTTP 통신을 사용하면 클라이언트의 형태에 상관없이 같은 방식으로 서버와 통신 가능

 

4.3. 쿠키와 세션 이해하기

 

- 웹 사이트에서 로그인을 하고 새로고침을 여러번 해도 로그아웃이 안 되는 이유 -> 클라이언트가 서버에게 사용자 정보를 지속적으로 알려주고 있기 때문

- 쿠키: 유효 기간이 있는 키-값의 쌍

ㄴ 서버로부터 쿠키가 오면 브라우저가 쿠키를 저장해뒀다가 다음에 요청을 보낼 때 쿠키를 동봉해서 보냄. 서버는 요청에 들어있는 쿠키를 읽어서 사용자 파악

- 브라우저에서는 쿠키를 자동으로 보내므로 우리가 작성할 필요 xx, 서버 -> 브라우저로 쿠키를 보낼 때만 작성해주면 됨

1) 클라이언트가 서버에 요청을 보냄

2) 서버: 사용자의 정보를 쿠키로 생성 -> 쿠키를 클라이언트에게 보냄

3) 클라이언트가 서버에게 쿠키를 담아서 요청을 보냄

4) 서버가 쿠키를 읽고 사용자를 파악해 응답을 보냄

ㄴ 쿠키는 클라이언트에서만 저장이 되고 서버에서는 따로 저장이 되지 않습니다!!

서버는 그냥 쿠키 생성 -> 전송만 하고, 다음에 클라이언트에서 쿠키가 들어올 때 쿠키를 읽어서 사용자를 파악합니다

 

- 쿠키를 만들어보자!

- Set-Cookie: 브라우저에게 쿠키를 저장하게 함 (브라우저: mycookie=test 쿠키 저장)

- 파비콘: 웹 사이트 탭에 보이는 이미지 (브라우저가 HTML에서 파비콘을 유추할 수 없으면 서버에 요청을 보냄)

 

- 사용자를 식별하는 방법을 알아보자!

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

➊
const parseCookies = (cookie = '') =>
  cookie
    .split(';')
    .map(v => v.split('='))
    .reduce((acc, [k, v]) => {
      acc[k.trim()] = decodeURIComponent(v);
      return acc;
    }, {});
http.createServer(async (req, res) => {
  const cookies = parseCookies(req.headers.cookie);
 

➋
  // 주소가 /login으로 시작하는 경우
  if (req.url.startsWith('/login')) {
    const url = new URL(req.url, 'http://localhost:8084');
    const name = url.searchParams.get('name');
    const expires = new Date();
    // 쿠키 유효 시간을 현재 시간 + 5분으로 설정
    expires.setMinutes(expires.getMinutes() + 5);
    res.writeHead(302, {
      Location: '/',
      'Set-Cookie': `name=${encodeURIComponent(name)}; Expires=${expires.toGMTString()}; HttpOnly; Path=/`,
    });
    res.end();
 

➌
  // 주소가 /이면서 name이라는 쿠키가 있는 경우
  } else if (cookies.name) {
    res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
    res.end(`${cookies.name}님 안녕하세요`);
  } else { // 주소가 /이면서 name이라는 쿠키가 없는 경우
    try {
      const data = await fs.readFile(path.join(__dirname, 'cookie2.html'));
      res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
      res.end(data);
    } catch (err) {
      res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
      res.end(err.message);
    }
  }

})
  .listen(8084, () => {
    console.log('8084번 포트에서 서버 대기 중입니다!');
});

 parseCookies: 쿠키 문자열을 쉽게 사용하기 위해 자바스크립트 객체 형식으로 바꾸는 함수

ㄴ mycookie=test -> { mycookie:'test' }

 로그인 요청 처리 부분

 그 외의 경우, 쿠키 유무 확인

쿠키명 = 쿠키값 기본적인 쿠키의 값
Expires = 날짜 만료 기한
Max-age = 초 해당 초가 지나면 쿠키 제거
Domain = 도메인명 쿠키가 전송될 도메인 특정
Path = URL 쿠키가 전송될 URL 특정
Secure HTTPS일 경우에만 쿠키 전송
HttpOnly 설정 시 자바스크립트에서 쿠키 접근 불가

 

로그인 전
로그인 후

 

- 애플리케이션 탭에서 쿠키가 노출되어 있는 상태 -> 쿠키 조작 위험 있음

// 서버가 사용자 정보를 관리하도록 cookie2.js 수정하기! (수정된 부분만...)

const session = {};

http.createServer(async (req, res) => {
  const cookies = parseCookies(req.headers.cookie);
  if (req.url.startsWith('/login')) {
    const url = new URL(req.url, 'http://localhost:8085');
    const name = url.searchParams.get('name');
    const expires = new Date();
    expires.setMinutes(expires.getMinutes() + 5);
    const uniqueInt = Date.now();
    session[uniqueInt] = {
      name,
      expires,
    };
    res.writeHead(302, {
      Location: '/',
      'Set-Cookie': `session=${uniqueInt}; Expires=${expires.toGMTString()}; HttpOnly; Path=/`,
    });
    res.end();
  // 세션 쿠키가 존재하고, 만료 기간이 지나지 않았다면
  } else if (cookies.session && session[cookies.session].expires > new Date()) {
    res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
    res.end(`${session[cookies.session].name}님 안녕하세요`);
  } else {
    try {
      const data = await fs.readFile(path.join(__dirname, 'cookie2.html'));
      res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
      res.end(data);
    } catch (err) {
      res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
      res.end(err.message);
    }
  }
})
  .listen(8085, () => {
    console.log('8085번 포트에서 서버 대기 중입니다!');
  });

- 쿠키에 uniqueInt라는 숫자 값을 보냄. 사용자의 이름과 만료 시간은 uniqueInt 아래에 session 객체에 저장

- 세션: 사용자의 정보를 저장하는 방법(인데 클라이언트와는 세션 아이디로만 소통함)

 

4.4. https와 http2

- https 모듈: 웹 서버에 SSL 암호화를 추가

- SSL: 서버나 브라우저 사이에서 전송되는 데이터를 암호화하여 보안을 강화하는 기술

// server1.js
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번 포트에서 서버 대기 중입니다!');
  });

- 이 서버에 암호화를 적용해보자!

// server1-3.js
const https = require('https');
const fs = require('fs');

https.createServer({
  cert: fs.readFileSync('도메인 인증서 경로'),
  key: fs.readFileSync('도메인 비밀 키 경로'),
  ca: [
    fs.readFileSync('상위 인증서 경로'),
    fs.readFileSync('상위 인증서 경로'),
  ],
}, (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(443, () => {
    console.log('443번 포트에서 서버 대기 중입니다!');
  });

- createServer의 첫 번째 인수: 인증서 관련 옵션 객체, 두 번째 인수: 서버 로직

 

- http2 모듈: SSL 암호화와 더불어 http/2를 사용할 수 있게 함

- http/2: 훨씬 효율적으로 요청 및 응답을 할 수 있는 HTTP 프로토콜

// server1-4.js (http2 적용)
const http2 = require('http2');
const fs = require('fs');

http2.createSecureServer({
  cert: fs.readFileSync('도메인 인증서 경로'),
  key: fs.readFileSync('도메인 비밀 키 경로'),
  ca: [
    fs.readFileSync('상위 인증서 경로'),
    fs.readFileSync('상위 인증서 경로'),
  ],
}, (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(443, () => {
    console.log('443번 포트에서 서버 대기 중입니다!');
  });

- https -> http2, createServer -> createSecure

 

4.5. cluster

- cluster 모듈: 노드가 CPU 코어를 모두 사용할 수 있게 해주는 모듈

- 장점: 코어 하나당 노드 프로세스 하나가 돌아가게 할 수 있음 -> 성능 개선

- 단점: 메모리 공유 불가

 

- 마스터 프로세스: CPU 개수만큼 워커 프로세스 생성, 요청이 들어오면 워커 프로세스에 요청 분배

- 워커 프로세스: 실질적인 일을 하는 프로세스

 

반응형