역시 애들 다 재우고 맥주마시면서 하는 개발공부가 개꿀잼
요청, 응답, 포트번호에 대한 기본적인 내용은 생략
기본적 메서드는 res.writehead
, res.write
, res.end
가 있음.
// run multiple servers
const server = http.createServer((req, res) => {/*callback content*/}).listen(8080, callback);
const server2 = http.createServer((req, res) => {/*callback content*/}).listen(8081, callback);
// eventlistener for servers
server.on('listening', callback);
server.on('error', callback);
restServer.js 파일을 포스팅하는 것으로 대체한다. 내용을 읽어 볼 것.
const http = require('http');
const fs = require('fs').promises;
const users = {}; // 데이터 저장용
http.createServer(async (req, res) => {
try {
if (req.method === 'GET') {
if (req.url === '/') {
const data = await fs.readFile('./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('./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(`.${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('ok');
});
}
} 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('ok');
});
}
} 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('ok');
}
}
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번 포트에서 서버 대기 중입니다');
});
쿠키와 세션 모두 요청자를 파악하기 위한 기능이다.
쿠키
const expires = new Date();
expires.setMinutes(expires.getMinutes() + 5);
res.writeHead(302, { // 302 redirection
Location: '/',
'Set-Cookie': `name=${encodeURIComponent(name)};
Expires=${expires.toGMTString()};
HttpOnly; Path=/`
});
세션
const session = {};
http.createServer(async (req, res) => {
const cookies = parseCookies(req.headers.cookie);
if (req.url.startsWith('/login')) {
const { query } = url.parse(req.url);
const { name } = qs.parse(query);
const expires = new Date();
expires.setMinutes(expires.getMinutes() + 1);
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('./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);
}
}
})
아우 제발 그냥 Express 쓸게요
https 모듈은 서버와 통신하는 데이터에 SSL 암호화를 추가한다.
실무에서는 필수
Let's Encrypt와 같은 기관에서 무료로 발급해준다.
http2 는 http/1.1 을 개선해 효율적이고 성능개선된 프로토콜, 가능하면 http2를 사용한다.
아래는 http2와 https 모두 적용된 코드 예제
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 SECURE world!</h1>');
res.end('<p>bye secured server</p>');
})
.listen('443', () => {
console.log('https 프로토콜은 443번 포트를 사용합니다.');
});
cluster는 동일 포트를 공유하는 여러 프로세스를 생성해서 노드가 멀티코어 CPU를 활용할 수 있게 해주는 모듈이다. 서버간 세션을 공유하지 못하는 등의 단점이 있지만, Redis 등을 활용하면 해결 가능하다.
const cluster = require('cluster');
const http = require('http');
const numCPUs = require('os').cpus().length; // returns CPU core nums
if (cluster.isMaster) {
console.log(`마스터 프로세스 아이디 : ${process.pid}`);
for (let i = 0; i < numCPUs; i++) {
cluster.fork(); // 새로운 워커를 생산
}
cluster.on('exit', (worker, code, signal) => {
console.log(`${worker.process.pid}번 워커가 종료되었습니다.`);
console.log('code', code, 'signal', signal);
cluster.fork(); // fork another process when a worker is terminated
});
} 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>');
setTimeout(() => { // let server exit whenever /GET localhost:8086
console.log('pid in setTimeout', process.pid);
process.exit(1);
}, 1000);
}).listen(8086);
console.log(`${process.pid}번 워커 실행`);
}
위의 예제와 같이, 워커 하나가 종료될 때마다 새로운 워커를 생성시켜 오류로 인해 전체 서버가 다운되는 것을 막을 수 있다.
실무에서는 cluster모듈을 직접 사용하기보다 pm2 등으로 클러스터링을 구현한다.