바르고 뜨겁게
[NodeJS] 노드JS로 서버 만들기 본문
http 모듈, localhost, 포트
http 모듈
은 선언된 js를 서버프로그램으로 만든다.
http 모듈
이 서버 역활을 하고 node는 이걸 실행한다.
const http = require('http');
// http.createServer(요청/응답 콜백);
http.createServer((req, res)=>{
console.log('server start');
res.write('<h1>server1.js PAGE</h1>');
res.write('<h3>write used multiple times</h3>');
res.write('<h3>write used multiple times</h3>');
res.end('<p>end</p>');
}).listen(8080, ()=>{
console.log('8080 포트에서 서버 실행');
});
PS D:\포트폴리오\_Study\Inflearn_nodeJS-Textbook> node server1 8080 포트에서 서버 실행 server start
응답을 이용해 클라이언트로 HTML 파일 보내기
server2.html
<html>
<head>
<meta charset="utf-8" />
<title>Node.js 서버</title>
</head>
<body>
<h1>노드JS 웹서버</h1>
<p>HTML 삽입</p>
</body>
</html>
server1.js
const http = require('http');
const fs = require('fs');
// http.createServer(요청/응답 콜백);
const server = http.createServer((req, res)=>{
console.log('server start');
fs.readFile('./server2.html',(err,data)=>{
if(err){
throw err;
}
res.end(data); //data에 담긴 버퍼를 브라우저(클라이언트)로 요청을 보냄
});
}).listen(8080);
// http 모듈에 이벤트 추가 가능
server.on('listening', ()=>{
console.log('8080 포트에서 서버 실행');
});
server.on('error',()=>{
console.error(error);
});
클라이언트-서버 데이터 주고받기 (쿠키 사용)
// 쿠키 응답 (크롬 f12 - Application - Cookies 확인가능)
const http = require('http');
const fs = require('fs');
const parseCookies = (cookie = '') =>
cookie
.split(';')
.map(v => v.split('='))
.map(([k, vs]) => [k, vs.join('=')])
.reduce((acc, [k, v]) => {
acc[k.trim()] = decodeURIComponent(v);
return acc;
}, {});
// http.createServer(요청/응답 콜백);
const server = http.createServer((req, res) => {
console.log('요청한곳 : ',req.url);
// 쿠키 정보 받기
console.log(req.headers.cookie);
// 문자열을 객체로 파싱
console.log(parseCookies(req.headers.cookie));
// res.writeHead(요청코드, 객체)
// 객체 내용 : 'Set-Cookie' 쿠키사용설정 , '키=값' 으로 이루어져있음
res.writeHead(200, { 'Set-Cookie': 'mycookie=test' });
res.end('cooke end');
}).listen(8080);
// http 모듈에 이벤트 추가 가능
server.on('listening', () => {
console.log('8080 포트에서 서버 실행');
});
server.on('error', () => {
console.error(error);
});
// 로컬호스트 요청이라서 / 로 뜸
요청한곳 : /
_ga=GA1.1.517848530.1529337539; mycookie=test
{ _ga: 'GA1.1.517848530.1529337539', mycookie: 'test' }
// 브라우저가 파비콘 받아오기 위해한 요청
요청한곳 : /favicon.ico
_ga=GA1.1.517848530.1529337539; mycookie=test
{ _ga: 'GA1.1.517848530.1529337539', mycookie: 'test' }
Set-Cookie
를 보내면 브라우저는 쿠키를 저장한다.
라우터 분기 처리와 쿠키
server1.js
const http = require('http');
const fs = require('fs');
const url = require('url');
const qs = require('querystring');
const parseCookies = (cookie = '') =>
cookie
.split(';')
.map(v => v.split('='))
.map(([k, vs]) => [k, vs.join('=')])
.reduce((acc, [k, v]) => {
acc[k.trim()] = decodeURIComponent(v);
return acc;
}, {});
// http.createServer(요청/응답 콜백);
const server = http.createServer((req, res) => {
console.log('요청한곳 : ', req.url);
console.log('쿠키정보 : ', 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() + 5); // Expires 쿠키유효시간
res.writeHead(302, {
Location: '/',
'Set-Cookie': `name=${encodeURIComponent(name)}; Expires=${expires.toGMTString()}; HttpOnly; path=/`
});
console.log(name);
res.end(name);
} else if(cookies.name){
res.writeHead(200,{ 'Content-Type' : 'text/html; charset-utf-8'});
res.end(`${cookies.name}님 안녕하세요`)
}else {
fs.readFile('./server4.html', (err, data) => {
res.end(data);
});
}
}).listen(8080);
// http 모듈에 이벤트 추가 가능
server.on('listening', () => {
console.log('8080 포트에서 서버 실행');
});
server.on('error', () => {
console.error(error);
});
server4.html
<html>
<head>
<meta charset="utf-8" />
<title>쿠키 세션</title>
</head>
<body>
<form action="/login">
<input id="name" name="name" placeholder="이름을 입력하세요"/>
<button id="login">로그인</button>
</form>
</body>
</html>
요청한곳 : /login?name=RightHot
쿠키정보 : { _ga: 'GA1.1.517848530.1529337539', mycookie: 'test' }
RightHot
요청한곳 : /favicon.ico
쿠키정보 : { _ga: 'GA1.1.517848530.1529337539', mycookie: 'test' }
다른 페이지로 이동
res.writeHead(302, {
Location: '/',
'Set-Cookie': `name=${encodeURIComponent(name)}; Expires=${expires.toGMTString()}; HttpOnly; path=/`
});
메모리 세션
쿠키는 쿠키안에 데이터를 그대로 사용하지만
아래 예제는 세션의 아이디
만 쿠키에 저장하고 데이터는 브라우저의 정보를 사용한다.
const http = require('http');
const fs = require('fs');
const url = require('url');
const qs = require('querystring');
const parseCookies = (cookie = '') =>
cookie
.split(';')
.map(v => v.split('='))
.map(([k, vs]) => [k, vs.join('=')])
.reduce((acc, [k, v]) => {
acc[k.trim()] = decodeURIComponent(v);
return acc;
}, {});
const session = {
};
// http.createServer(요청/응답 콜백);
const server = http.createServer((req, res) => {
const cookies = parseCookies(req.headers.cookie);
console.log('요청한곳 : ', req.url);
console.log('쿠키정보 : ', cookies);
// 요청 주소에 따라 분기
if (req.url.startsWith('/login')) {
const { query } = url.parse(req.url);
const { name } = qs.parse(query);
const expires = new Date();
expires.setMinutes(expires.getMinutes() + 5); // Expires 쿠키유효시간
const randomInt = +new Date(); // 세션 객체에 랜덤값을 넣는다
session[randomInt] ={
name,
expires
}
res.writeHead(302, {
Location: '/',
'Set-Cookie': `session=${randomInt}; Expires=${expires.toGMTString()}; HttpOnly; path=/`
});
console.log(name);
res.end(name);
} else if(cookies.session && session[cookies.session] && session[cookies.session].expires > new Date()){ // 쿠키확인, 쿠키의 유효기간 확인
res.writeHead(200,{ 'Content-Type' : 'text/html; charset=utf-8'});
res.end(`${session[cookies.session].name}님 안녕하세요`)
}else {
fs.readFile('./server4.html', (err, data) => {
res.end(data);
});
}
}).listen(8080);
// http 모듈에 이벤트 추가 가능
server.on('listening', () => {
console.log('8080 포트에서 서버 실행');
});
server.on('error', () => {
console.error(error);
});
서버의 자원을 주소를 통해서 가져오는 구조적인 방법
GET
가져오기 POST
등록하기 PUT
전체 변경 PATCH
일부 변경 DELETE
삭제
위와 같은 메서드는 req.method
에 담겨있다.
server1.js
const http = require('http');
const fs = require('fs');
// DB가 없어서 임시로 메모리에 저장
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 === '/users'){
return res.end(JSON.stringify(users)); // 객체라서 객체형식을 못보내기때문에 JSON 형태로 변경
}
// 요청한 url을 보내준다 (css, js 등등... -정적파일 처리)
return fs.readFile(`.${req.url}`,(err,data)=>{
return res.end(data);
});
}else if(req.method === 'POST'){
if(req.url === '/'){
}else if(req.url === '/users'){
let body ='';
req.on('data', (chunk)=>{ // 데이터가 버퍼로 들어옴
body += chunk
});
return req.on('end', ()=>{
console.log('POST 본문(body)', body);
//const name = JSON.parse(body).name; 아래는 비구조화 할당
const { name } = JSON.parse(body);
const id = +new Date(); // 랜덤 키 생성
users[id] = name;
res.writeHead(201, { 'Content-Type': 'text/html; charset=utf-8'});
res.end('사용자 등록 완료')
});
}
}else if(req.method === 'PATCH'){
if(req.url === '/'){
}else if(req.url === '/users'){
}
}else if(req.method === 'PUT'){
if(req.url === '/'){
}else if(req.url.startsWith('/users/')){ // (/users/67) URL안에 값이 있어서 뒤에 나오는 값은 바뀔수도있으니 이렇게 모두 받는다
const id = req.url.split('/')[2];
let body ='';
req.on('data', (chunk)=>{ // 데이터가 버퍼로 들어옴
body += chunk
});
return req.on('end', ()=>{
console.log('put ', body);
users[id] = JSON.parse(body).name;
return res.end(JSON.stringify(users)); // 클라이언트로 응답
});
}
}else if(req.method === 'DELETE'){
if(req.url === '/'){
}else if(req.url.startsWith('/users/')){ // (/users/67) URL안에 값이 있어서 뒤에 나오는 값은 바뀔수도있으니 이렇게 모두 받는다
const id = req.url.split('/')[2];
let body ='';
req.on('data', (chunk)=>{ // 데이터가 버퍼로 들어옴
body += chunk
});
return req.on('end', ()=>{
console.log('delete', body);
delete users[id];
return res.end(JSON.stringify(users)); // 클라이언트로 응답
});
}
}
}).listen(8080, ()=>{
console.log('8080 포트에서 서버 실행중')
});
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('이름을 입력하세요');
}
// AJAX 요청
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 = '';
});
restFront.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>
위에 코드 리팩토링 (라우터)
보호연산자 :
( 첫번째값 && 두번째값 )
router && router[req.method] && router[req.method][req.url] ... undefind가 아니면 다음걸 계속 실행 아이면 이전값 실행기본값연산자 :
( 첫번째값 || 두번째값 )
첫번째값이 undefind면 두번째값이 실행
const http = require('http');
const fs = require('fs');
// DB가 없어서 임시로 메모리에 저장
const users = {
};
// 프레임워크를 사용하지 않을때 리팩토링 방법
const router = {
GET: {
'/': (req, res) => {
fs.readFile('./restFront.html', (err, data) => {
if (err) {
throw err;
}
res.end(data);
});
},
'/users': (req, res) => {
res.end(JSON.stringify(users));
},
'*': (req, res) => {
return fs.readFile(`.${req.url}`, (err, data) => {
return res.end(data);
});
}
},
POST: {
'/users': (req, res) => {
let body = '';
req.on('data', (chunk) => { // 데이터가 버퍼로 들어옴
body += chunk
});
return req.on('end', () => {
console.log('POST 본문(body)', body);
//const name = JSON.parse(body).name; 아래는 비구조화 할당
const { name } = JSON.parse(body);
const id = +new Date(); // 랜덤 키 생성
users[id] = name;
res.writeHead(201, { 'Content-Type': 'text/html; charset=utf-8' });
res.end('사용자 등록 완료')
});
}
},
PATCH: {
},
PUT: {
'/users': (req, res) => {
const id = req.url.split('/')[2];
let body = '';
req.on('data', (chunk) => { // 데이터가 버퍼로 들어옴
body += chunk
});
return req.on('end', () => {
console.log('put ', body);
users[id] = JSON.parse(body).name;
return res.end(JSON.stringify(users)); // 클라이언트로 응답
});
}
},
DELETE: {
'users': (req, res) => {
const id = req.url.split('/')[2];
let body = '';
req.on('data', (chunk) => { // 데이터가 버퍼로 들어옴
body += chunk
});
return req.on('end', () => {
console.log('delete', body);
delete users[id];
return res.end(JSON.stringify(users)); // 클라이언트로 응답
});
},
}
}
http.createServer((req, res) => {
const matchedUrl = router[req.method][req.url];
// matchedUrl에 값이 있으면 그대로 사용, req.url에 값이 없어서 undefind면 router[req.method]['*'] 사용
(matchedUrl || router[req.method]['*'])(req, res);
}).listen(8080, () => {
console.log('8080 포트에서 서버 실행중')
});
https
https
https는 암호화 통신이 때문에 인증서
가 필요하다.
letsencrypt (무료)등에서 발급
const http = require('http');
const https = require('https');
https.createServer({
cert: fs.readFileSync('도메인 인증서 경로'),
key: fs.readFileSync('도메인 비밀키 경로'),
ca: [
fs.readFileSync('상위 인증서 경로'),
fs.readFileSync('상위 인증서 경로')
],
},(req,res)=>{
res.end('https server');
}).listen(443);
cluster 멀티 프로세싱
노드는 싱글 스레드이기 때문에 CPU 코어를 모두 활용하지 못함.
그래서 서버를 여러개 만드는 개념의 CLUSTER를 사용
cluster
에는 master
와 worker
프로세스가 존재.
master
는 cpu개수만큼 워커를 만든다.
worker
는 서버를 돌린다.
const http = require('http');
const os = require('os');
const cluster = require('cluster');
const numCPUs = os.cpus().length;
if(cluster.isMaster){
console.log('마스터 프로세스 아이디: ', process.pid);
for(let i=0; i < numCPUs; i += 1){
cluster.fork(); // 워커 만들기
}
// 워커가 종료되면 발생되는 이벤트
cluster.on('exit',(worker, code, siqnal)=>{
console.log(worker.process.pid, ' 워커 종료 - 새로 fork로 워커를 만듭니다.');
cluster.fork();
})
}else{
console.log('워커 프로세스 아이디: ', process.pid);
http.createServer((req,res)=>{
res.end('http server');
}).listen(8080);
}
마스터 프로세스 아이디: 6004 워커 프로세스 아이디: 22372 워커 프로세스 아이디: 9676 워커 프로세스 아이디: 31268 워커 프로세스 아이디: 34712 워커 프로세스 아이디: 26344 워커 프로세스 아이디: 9864 워커 프로세스 아이디: 8992 워커 프로세스 아이디: 3740 워커 프로세스 아이디: 2984 워커 프로세스 아이디: 15848 워커 프로세스 아이디: 13304 워커 프로세스 아이디: 37796 워커 프로세스 아이디: 17340 워커 프로세스 아이디: 16108 워커 프로세스 아이디: 32124 워커 프로세스 아이디: 29916
'자바스크립트 > Node Js' 카테고리의 다른 글
[NodeJS] express 프레임워크 (0) | 2018.12.31 |
---|---|
[NodeJS] npm 명령어, 생성, 설치, 배포 (0) | 2018.12.29 |
[NodeJS] 예외처리 (0) | 2018.12.27 |
[NodeJS] events 모듈 (0) | 2018.12.27 |
[NodeJS] FS(파일시스템) 모듈 - 동기와 비동기 (0) | 2018.12.27 |
Comments