4장 http 모듈로 서버 만들기
4-1. 요청과 응답 이해하기
내용이 어렵지는 않았어서 본문을 읽어본 뒤에 바로 코드를 작성해보았다.
localhost:8080 | localhost:8081 |
res가 json인 경우가 많아서 json파일을 임의로 만들어 사용해봤는데 잘 작동했다.
코드 살펴보기
- require('http'): node의 http 모듈을 불러온다. 당연하지만 node 필요
- http.createServer(): http 모듈의 메서드로 서버를 만든다. listen 메서드에서 포트를 결정한다.
- req, res: 각각 request(요청), response(응답)
- res.writeHead(): 첫 번째 인수에 HTTP 상태 코드(ex. 200: 성공), 두 번째 인수에 응답의 헤더를 보낸다.
- res.write(): 코드에는 없지만 응답의 바디를 보낸다. 여러 번 호출 가능하고 버퍼나 문자열 등을 보낸다.
- res.end(): 응답을 종료한다. 만약 인수가 있다면 해당 인수까지 보낸 후 종료한다.
- listen(): 인수로는 연결할 포트를 작성하고, 콜백 함수에 연결에 성공했을 때 실행할 코드를 작성한다.
- promise/async,await: 2.1.7, 2.1.8에 있는 내용. 비동기 작업을 위함.
- promise: 비동기 작업의 완료나 실패를 처리할 수 있게 해주는 객체. require('fs').promises 는 node.js에서 fs의 promise 기반 API를 사용할 수 있게 해준다.
- async: 함수 앞에 async를 붙이면 그 함수는 비동기 함수가 된다.
- await: 비동기 작업을 처리할 때, 작업이 완료될 때까지 기다렸다가 그 다음 코드를 실행하게 한다.
HTTP 상태 코드
상태 코드 | 의미 | |
200 | OK | 요청 메시지에 대한 성공적인 응답을 나타내는 메시지. |
201 | Created | 요청이 성공했고 자원을 서버 내에 만들었다. PUT 메소드에 대한 전형적인 응답. |
301 | Moved Permanently | 요청한 자원이 새로운 URL로 영구히 이동했다. 보통은 404 에러를 발생시킨다 |
302 | Found | 요청한 자원이 일시적으로 다른 URL을 사용하고 있다. |
403 | Forbidden | 서버가 요청을 거부했다. |
404 | Not Found | 요청한 자원을 찾을 수 없다. |
500 | Internal Server Error | 서버 문제로 인해 요청을 수행할 수 없다. |
4-2. REST와 라우팅 사용하기
REST(REpresentational State Transfer): 서버의 자원을 정의하고 자원에 대한 주소를 지정하는 방법. 이 방법을 따르는 서버를 RESTful하다고 표현.
- GET: 서버 자원을 가져오고자 할 때 사용한다. 요청의 본문(body)에 데이터를 넣지 않는다. 데이터를 서버로 보내야 한다면 쿼리스트링을 사용한다.
- POST: 서버에 자원을 새로 등록하고자 할 때 사용한다. 요청의 본문에 새로 등록할 데이터를 넣어 보낸다.
- PUT: 서버의 자원을 요청에 들어 있는 자원으로 치환하고자 할 때 사용한다. 요청의 본문에 치환할 데이터를 넣어 보낸다.
- PATCH: 서버 자원의 일부만 수정하고자 할 때 사용한다. 요청의 본문에 일부 수정할 데이터를 넣어 보낸다.
- DELETE: 서버의 자원을 삭제하고자 할 때 사용한다. 요청의 본문에 데이터를 넣지 않는다.
- OPTIONS: 요청을 하기 전에 통신 옵션을 설명하기 위해 사용한다.
책에 나온 코드를 그대로 옮겨두었다.
HTTP 메서드 | 주소 | 역할 |
GET | / | restFront.html 파일 제공 |
GET | /about | about.html 파일 제공 |
GET | /users | 사용자 목록 제공 |
GET | 기타 | 기타 정적 파일 제공 |
POST | /user | 사용자 등록 |
PUT | /user/사용자id | 해당 id의 사용자 수정 |
DELETE | /user/사용자id | 해당 id의 사용자 제 |
API 명세서와 프론트의 각 구간이 어떤 역할인지 표시한 사진이다.
users를 반환하는 GET 말고도 Home 페이지와 About 페이지도 GET으로 불러왔다.
그냥 코드를 긁어서 복사해둔 후 처음 실행했을 때는 당연히 restFront.html이 별개인줄 알아서 html파일과 node.js 파일을 따로 실행했는데, html을 별개로 실행할 필요 없이 서버에서 반환해주는 구조였다.
restServer.js 코드 자체는 길이는 길었지만 로직 자체는 어렵지는 않서 크게 눈여겨 볼 곳은 없었는데, 왜 switch 문이 아닌 else if 문을 사용했는지 모르겠다.
그래서 switch 문으로 바꿔서 작성해봤는데 정상적으로 작동하기도 하고 내 기준으로는 switch 문의 가독성이 훨씬 좋기 때문에 이 방식을 선호할 것 같다.
이렇게 개발자 도구의 Network 탭에서 어떤 통신이 있었는지 확인할 수 있다.
API 연동할 때 오류가 나면 저 미리보기 부분을 백엔드 분들에게 보내주었던 기억이 있다.
그리고 내가 못 찾은 건지 책에는 라우팅에 대한 개념이 없었는데 라우팅이란 HTTP 요청에 대해 해당 요청을 적절한 처리 함수로 연결해주는 과정이다
4-3. 쿠키와 세션 이해하기
쿠키: 웹 브라우저와 서버 간의 통신에서 사용자의 정보를 저장하고 관리하기 위한 작은 데이터 파일
웹 페이지에서 종종 나오는 쿠키를 허용하시겠습니까?의 그 쿠키이다. 왜 이런 이름이 붙었을까 궁금해 찾아보니 1970년대 멀티시스템 환경에서 데이터를 주고받는 데이터 조각을 뜻하는 "Magic Cookie"에서 유래됐다고 한다. 또한 쿠키라는 단어 자체도 서로 주고받을 수 있는 작은 선물이라는 뜻으로 봐도 된다.
참고로 뉴진스의 Cookie를 들으며 작성 중이다.
우선 쿠키를 지우고
확인을 해봤는데 책에서와 다른 결과가 나왔다. 아마 webstorm때문인 듯 한데 쿠기가 없는 상태를 확인하는 게 중요할 것 같지는 않아서 넘어가기로 했다.
쿠키를 설정하는 파일을 만들어준 뒤 로그인을 하면 새로고침을 하거나 서버를 재시작해도 쿠키가 남아있어 로그인이 유지되는 모습을 볼 수 있다.
쿠키는 헤더 부분에 들어있어 req.headers.cookie로 읽거나 res.writeHead()로 보낸다.
쿠키 옵션
그런데 이 방식은 원하는 대로 동작하기는 하지만 쿠키가 노출되어 있 위험하다.
이를 보완하기 위해 사용하는 게 세션이다.
서버에 사용자 정보를 저장하고 클라이언트와는 세션 ID로만 소통한다.
하지만 이 방식도 세션 ID가 노출되어 있기 때문에 안전하지는 않다. 이는 테스트를 위한 코드일 뿐이니 안전하게 사용하려면 다른 모듈 등을 사용해야 한다.
4-4. https와 http2
https 모듈은 웹 서버에 암호화를 추가한다. GET이나 POST 요청을 할 때 오가는 데이터를 암호화해서 타인이 요청을 가로채도 내용을 확인할 수 없게끔 한다.
https가 적용되면 위와 같이 자물쇠 모양을 확인할 수 있다.
https를 사용하려면 인증서가 필요한데 이 과정은 복잡하므로 다루지 않는다. 만약 인증서를 발급받았다면 다음과 같이 하면 된다.
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번 포트에서 서버 대기 중입니다!');
});
인증서가 있으면 pem, crt, key 등의 확장자를 가진 파일을 제공하는데 이를 읽어서 cert, key, ca 옵션에 알맞게 넣어주면 된다. 또한 80이 아닌 443 포트를 사용한다.
http2는 암호화도 제공하고 기존 http/1.1 보다 개선된 성능을 보여준다.
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를 createSecureServer로 바꾸기만 하면 된다.
4-5. Cluster
Cluster 모듈은 기본적으로 싱글 프로세스로 동작하는 node가 CPU의 코어를 모두 사용할 수 있게 해준다. 성능이 개선되지만 메모리를 공유하지 못하는 문제가 있는데 이는 레디스 등의 서버를 도입해 해결할 수 있다.
클러스터에는 마스터 프로세스와 워커 프로세스가 있다다. 마스터 프로세스는 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; 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(8080);
console.log(`${process.pid}번 워커 실행`);
}
cluster로 워커를 CPU의 코어 갯수만큼 생성하고 요청이 들어오면 프로세스를 종료한 뒤 다시 생성한다. 이러면 예기치 못한 오류로 서버가 종료되는 것을 막을 수 있다.
'WINK-(Web & App) > Express.js (Node.js) 스터디' 카테고리의 다른 글
[2024-2 Node.js 스터디] 류상우 #3주차 (0) | 2024.11.04 |
---|---|
[2024-2 Node.js 스터디] 김민재 #3주차 - 익스프레스 웹 서버 만들기 (1) | 2024.11.04 |
[2024-2 Node.js 스터디] 김민재 #2주차 - HTTP 모듈로 서버 만들기 (4) | 2024.10.12 |
[2024-2 Node.js 스터디] 류상우 #1주차 (0) | 2024.10.07 |
[2024-2 Node.js 스터디] 김민재 #1주차 (0) | 2024.10.06 |