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 개수만큼 워커 프로세스 생성, 요청이 들어오면 워커 프로세스에 요청 분배
- 워커 프로세스: 실질적인 일을 하는 프로세스
'WINK-(Web & App) > Express.js (Node.js) 스터디' 카테고리의 다른 글
[2024 여름방학 Node.js 스터디] 이종윤 #3주차 (0) | 2024.08.02 |
---|---|
[2024 여름방학 Node.js 스터디] 백채린 #3주차 4장 (0) | 2024.08.01 |
[2024 여름방학 Node.js 스터디] 이종윤 #2주차 (1) | 2024.07.25 |
[2024 여름방학 Node.js 스터디] 백채린 #2주차 3장 (0) | 2024.07.25 |
[2024 여름방학 Node.js 스터디] 김지나 #2주차 (3) | 2024.07.25 |