티스토리 뷰
요청(Request)과 응답(Response)
클라이언트에서 서버로 요청(request)를 보내고, 서버에서는 요청의 내용을 읽고 처리한 뒤 클라이언트에 응답(response)를 보낸다.
그렇기 때문에 서버에는 요청을 받는 부분과 응답을 보내는 부분이 있어야한다.
요청과 응답은 이벤트 방식으로 동작한다.
const http = require("http");
const server = http
.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
res.write("<h1>hello Node!</h1>");
res.write("<p>Heelo server</p>");
res.end("<p>Hello hyunS00</p>");
})
.listen(8080);
server.on("listening", () => {
console.log("8080번 포트에서 서버 대기 입니다.");
});
server.on("error", (error) => {
console.error(error);
});
REST와 라우팅
라우팅 은 앱이 특정 API 엔드 포인트에 대한 클라이언트 요청에 응답하는 방법을 정의하는 프로세스다.
REST(REpresentational State Transfer)은 서버의 자원을 정의하고 자원에 대한 주소를 지정하는 일종의 약속이다. 여기서 자원이란 꼭 파일일 필요는 없고 서버가 행할 수 있는 것들을 통틀어서 의미한다.
REST API의 대표적인 규칙중에는 주소의 의미를 명확히 전달하기 위해 명사로 구분되어야한다.
하지만 단순히 명사로만 되어있으면 무슨 동작을 행하는것인지 알기 어렵기 때문에 REST API는 HTTP 요청 메서드를 함께 사용한다.
HTTP 요청 메서드
- GET: 서버의 자원을 가져올 때 사용한다. 데이터를 서버로 보내야한다면 요청의 본문(Body)에 넣지말고 쿼리스트링을 사용해야한다.
- POST: 서버에 자원을 등록할 때 사용한다. 요청의 본문에 새로 등록할 데이터를 함께 보낸다.
- PUT: 서버의 자원을 요청에 들어있는 자원으로 치환할 때 사용한다. 요청의 본문에 치환할 데이터를 넣어 보낸다.
- PATCH: 서버의 자원의 일부만 수정할 때 사용한다. 요청의 본문에 수정할 데이터를 넣어 보낸다.
- DELETE: 서버의 자원을 삭제하고자 할 때 사용한다. 요청의 본문에 데이터를 넣지 않는다.
- OPTIONS: 요청을 하기 전에 통신 옵션을 설명하기 위해 사용된다.
주소 하나가 요청 메서드를 여러 개 가질 수 있다.
GET 메서드의 경우 브라우저에 캐싱을 할 수 있다.
REST를 따르는 서버를 RESTful하다고 표현한다.
쿠기와 세션
클라이언트에서 보내는 요청은 누가 요청을 보내는지 모른다는 단점있다. 이것을 식별하기 위해 우리는 로그인과 같은 과정을 거친다.
로그인 요청을 하게되면 서버는 누구인지 기억하기 위해 응답을 할 때 쿠키라는 것을 같이 보낸다.
- 쿠키는 유효 기간이 있다
- 키-값의 쌍으로 되어있다.
- 쿠키는 요청의 헤더(Cookie)에 담겨 전송된다.
- 브라우저는 응답의 헤더(Set-Cookie)에 따라 쿠키를 저장한다.
서버로부터 쿠키가 오면, 웹 브라우저는 쿠키를 저장해뒀다가 다음에 요청할 떄마다 쿠키를 동봉해서 보낸다.
서버는 요청에 들어 있는 쿠키를 읽어서 사용자가 누구인지 파악한다.
const http = require("http");
http
.createServer((req, res) => {
console.log(req.url, req.headers.cookie);
res.writeHead(200, { "Set-Cookie": "mycookie=test" });
res.end("Hello Cookie");
})
.listen(8083, () => {
console.log("8083번 포트에서 서버 대기 중입니다.");
});
쿠키는 문자열 형식으로 존재한다. 쿠키 간에는 세미콜론(;)을 넣어 각각을 구분한다.
쿠키를 설정할 때 각종 옵션을 넣을 수 있고 세미콜론을 사용해 구분한다.
- 쿠키명=쿠키값: 기본적인 쿠키의 값이다.
- Expires=날짜: 쿠키 만료 기한이다. 이 기한이 지나면 쿠키가 제거된다. 기본값은 클라이언트가 종료될 때까지다.
- Max-age=초: Expires와 비슷하지만 날짜대신 초를 사용한다. Expiress보다 우선한다.
- Domain=도메인명: 쿠키가 전송될 도메인을 특정한다. 기본값은 현재 도메인이다.
- Path=URL: 쿠키가 전송 URL을 특정한다. 기본값은 '/'이다.
- Secure: HTTPS인 경우에만 쿠키가 전송된다.
- HttpOnly: 설정 시 자바스크립트에서 쿠키에 접근할 수 없다.
const http = require("http");
const fs = require("fs").promises;
const url = require("url");
const qs = require("querystring");
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 { query } = url.parse(req.url);
const { name } = qs.parse(query);
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("./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번 포트에서 서버 대기 중입니다!");
});
https와 http2
https
https 모듈은 웹 서버에 SSL 암호화를 추가한다.
GET이나 POST 요청을 할 때 오가는 데이터를 암호화해서 다른 사람이 요청을 가로채더라도 내용을 확인할 수 없게 한다.
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번 포트에서 서버 대기 중입니다!');
});
http2
http2 모듈은 SSL 암호화와 더불어 최신 HTTP 프로토콜인 http/2를 사용할 수 있다.
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번 포트에서 서버 대기 중입니다!');
});
cluster
cluster 모듈은 기본적으로 싱글스레드로 동작하는 노드가 CPU코어를 사용할 수 있게 해주는 모듈이다.
노드 프로세스를 여러 개 둘 수 있어 요청이 많이 들어왔을 때 병렬로 실행된 서버의 개수만큼 요청이 분산되게 할 수 있다.
메모리를 공유하지 못하는 단점이 있다.
const cluster = require("cluster");
const http = require("http");
const numCPUs = require("os").cpus().length;
if (cluster.isMaster) {
console.log(`마스터 프로세스 아이디: ${process.pid}`);
// CPU 개수만큼 워커를 생산
for (let i = 0; i < numCPUs + 5; 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>");
setTimeout(() => {
// 워커 존재를 확인하기 위해 1초마다 강제 종료
process.exit(1);
}, 1000);
})
.listen(8086);
console.log(`${process.pid}번 워커 실행`);
}
클러스터를 통해 생성되는 것은 스레드가 아니라 프로세스다.
클러스터에는 마스터 프로세스와 워커 프로세스가 존재한다.
참고
'Node.js' 카테고리의 다른 글
[Node.js 교과서] 섹션 6 - 익스프레스 웹 서버 만들기 (0) | 2024.11.03 |
---|---|
[Node.js 교과서] 섹션 5 - 패키지 매니저 (1) | 2024.10.25 |
[Node.js 교과서] 섹션 3 - 노드의 기본 기능 익히기 (1) | 2024.10.19 |
[Node.js 교과서] 섹션 2 - 알아두어야 할 자바스크립트 (0) | 2024.10.12 |
[Node.js 교과서] 섹션 1 - 노드의 정의와 특성 (3) | 2024.10.11 |