쿠키와 세션, cluster (5)

BirdsOnTree·2022년 12월 30일
0

Node.js

목록 보기
5/8
post-thumbnail

쿠키

쿠키의 필요성

요청에는 한 가지 단점이 있음
누가 요청을 보냈는지 모름 > 로그인을 구현하면 된다 > 쿠키와 세션이 필요하다

쿠키: 키 = 값의 쌍으로 매 요청마다 서버에 동봉해서 보낸다. 이로서 서버는 쿠키를 읽어서 누구인지 파악할 수 있다.

mycookie라는 이름의 쿠키를 준다.

const http = require('http')

http.createServer((req, res) => {
	console.log(req.url, req.headers.cookie);
  	res.writeHead(200, {'Set-Cookie': 'mycookie=test'});
  	res.end('hellow Cookie');
})
	.listen(8080, () => {
	console.log('8080번 포트가 서버 대기중')
})

Set-Cookie 시 다양한 옵션이 있다.

  • 쿠키명=쿠키값: 기본적인 쿠키의 값이다. mycookie=test와 같이 설정한다.
  • Expires=날짜: 만료 기한이다. 이 기한이 지나면 쿠키가 제거된다. 기본값은 클라이언트가 종료될때까지이다.
  • Max-age=초: Expires와 비슷하지만 날짜 대신 초를 입력할 수 있다. 해당 초가 지나면 쿠키가 제거된다. Expires보다 우선이다.
  • Domain=도메인명: 쿠키가 전송될 도메인을 특정할 수 있다. 기본값은 현재 도메인이다.
  • path=URL: 쿠키가 전송될 URL을 특정할 수 있다. 기본값은 '/'이고 이 경우 모든 URL에 쿠키를 전송할 수 있다.
  • Secure: HTTPS일 경우에만 쿠키를 전송한다.
  • HttpOnly: 설정시 자바스크립트에서 쿠키에 접할수 없다. 쿠키조작을 방지
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); // { mycookie: 'test' }
  
  // 주소가 /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 {
    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번 포트에서 서버 대기 중입니다!');
  });

세션

중요한 정보는 서버에서 관리하고 클라이언트에는 세션키만 제공, 서버에 세션 객체를 생성 후 uniqueInt(키)를 만들어 속성명으로 사용한다. 속성 값에 정보를 저장하고 uniqueInt를 클라이언트에 보낸다.

쿠키부분과 크게 다르지 않다.
로그인하는부분은 쿠키에서 아래부분이 추가되었다.

const uniqueInt = Date.now(); // 안겹치도록 해야한다
    session[uniqueInt] = {
      name,
      expires,
    };

전체 코드

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;
    }, {});

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

cluster

기본적으로 싱글 스레드인 노드가 cpu 코어를 모두 사용할 수 있게 해주는 모듈

  • 포트를 공유하는 노드 프로세스를 여러개 둘 수 있음
  • 요청이 많이 들어왔을 때 병렬로 실행된 서버의 개수만큼 요청이 분산됨
  • 서버에 무리가 덜 감
  • 코어가 8개인 서버가 있을 때: 보통은 코어 하나만 활용
  • cluster로 코어 하나당 노드 프로세스 하나를 배정 가능
  • 성능이 8배가 되는것은 아니지만 개선이 됨
  • 단점으로는 컴퓨터 자원(메모리, 세션 등) 공유를 못함
  • Redis 등 별도 서버로 해결
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length; // cpu의 개수

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}번 워커가 종료되었습니다.`);
    console.log('code', code, 'signal', signal);
    cluster.fork(); // 종료가 되면 다시 새로운 워커 생성
  });
} else {
  // 워커들이 포트에서 대기
  http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
    res.write('<h1>Hello Node!</h1>');
    res.end('<p>Hello Cluster!</p>');
    
    // 워커 존재를 확인하기 위해 1초마다 강제 종료
    setTimeout(() => {
      process.exit(1);
    }, 1000);
    
  }).listen(8086);
  console.log(`${process.pid}번 워커 실행`);
}

0개의 댓글