2022.09.13 경일 메타버스 24주차 1일 수업내용. Node.js - 노드 기능, http 모듈
p. 108 ~ 112
process 객체는 현재 실행되고 있는 노드 프로세스에 대한 정보를 담고 있다.
속성
version
: 노드의 버전
arch
: 프로세서 아키텍처 정보
platform
: 운영체제 플랫폼 정보
pid
: 현재 프로세스의 아이디
uptime()
: 프로세스가 시작된 후 흐른 시간, 단위는 초
execpath
: 노드의 경로
cwd()
: 현재 프로세스가 실행되는 위치
cpuUsage()
: 현재 cpu 사용량
시스템의 환경 변수
환경 변수는 노드에 직접 영향을 미치기도 한다.
UV_THREADPOOL_SIZE
NODE_OPTIONS
서비스의 중요한 키를 저장하는 공간
중요한 비밀번호 (키)는 process.env
의 속성으로 대체할 수 있다.
const secretId = process.env.SECRET_ID;
const secretCode = process.env.SECRET_CODE;
이벤트 루프가 다른 콜백 함수보다 nextTick의 콜백 함수를 우선으로 처리하도록 만든다.
process.nextTick
은 setImmediate
나 setTimeout
보다 먼저 실행된다.
resolve된 Promise도 다른 콜백들보다 우선시된다.
우선되는 이 둘을 마이크로태스크(Microtask)로 구분짓는다.
예시 코드
setImmediate(() => {
console.log('immediate');
});
process.nextTick(() => {
console.log('nextTick');
});
setTimeout(() => {
console.log('timeout');
}, 0);
Promise.resolve().then(() => console.log('promise'));
/*
console
> node 3_4_6_2
nextTick
promise
timeout
immediate
*/
실행 중인 노드 프로세스를 종료한다.
인수로 종료 코드를 줄 수 있다.
없거나 0 ⇒ 정상 종료
1 ⇒ 비정상 종료
서버 환경에서 사용하면 서버가 멈추므로 잘 사용하지 않는다.
p. 113 ~ 115
자바스크립트는 운영체제의 정보를 가져올 수 없지만, 노드는 os 모듈에 정보가 담겨 있어 정보를 가져올 수 있다.
p. 115 ~ 119
폴더와 파일의 경로 조작을 돕는 모듈
운영체제별로 경로 구분자가 다르기 때문에 필요
윈도 타입 :
\
로 구분
POSIX 타입 :
유닉스 기반 운영체제. 맥, 리눅스 등. /
로 구분
속성
path.sep
경로의 구분자
윈도는 \
, POSIX는 /
path.delimiter
환경 변수의 구분자
process.env.PATH를 입력하면 경로가 이 구분자로 구분되어 있다.
윈도는 세미콜론(;
), POSIX는 콜론(:
)
path.join(경로, …)
여러 인수를 넣으면 하나의 경로로 합친다.
상대 경로인 ..
(부모 디렉터리)와 .
(현 위치)도 알아서 처리한다.
path.resolve(경로, …)
path.join()
과 비슷하지만 동작 방식에 차이가 있다.
/
를 만나면 path.resolve
는 절대경로로 인식해 앞의 경로를 무시한다.
같은 상황에서 path.join
은 상대경로로 처리한다.
예시 코드
path.join('/a', '/b', 'c'); /* 결과: /a/b/c */
path.resolve('/a', '/b', 'c'); /* 결과: /b/c */
상대경로와 절대경로
절대경로
C:\
, POSIX의 /
)나 노드 프로세스가 실행되는 위치가 기준상대경로
현재 파일이 기준
현재 파일과 같은 경로 → 점 하나(.
)
현재 파일보다 한 단계 상위 경로 → 점 두 개(..
)
예시
C:\users\zerocho\path.js
에서 C:\
로 갈 때
절대경로 → C:\
입력
상대경로 → ..\..
입력
자바스크립트 문자열에서 \
가 특수 문자이므로 \\
처럼 두 개 붙여 경로를 표시해야 할 때도 있다.
\n
→ C:\\node
p. 119 ~ 123
인터넷 주소 조작을 돕는 모듈
url 처리 두 가지 방식
WHATWG (웹 표준을 정하는 단체) 방식
URL 생성자에 주소를 넣어 객체로 만들면 주소가 부문별로 정리된다.
WHATWG 방식에만 있는 username
, password
, origin
, searchParams
속성이 존재한다.
기존 노드에서 사용하던 방식
두 메소드를 주로 사용한다.
usl.parse(주소)
주소를 분해한다.
username
과 password
대신 auth
속성
searchParams
대신 query
속성
usl.format(객체)
두 가지 방식 url을 모두 사용 가능하다.
분해되었던 url 객체를 원래 상태로 조립한다.
url 구분 방법
예시 코드 1
// 3_5_3_url.js
const url = require('url');
const { URL } = url;
const myURL = new URL('http://www.gilbut.co.kr/book/bookList.aspx?sercate1=001001000#anchor');
console.log('new URL():', myURL);
console.log('url.format():', url.format(myURL));
console.log('--------------------------------');
const parsedURL = url.parse('http://www.gilbut.co.kr/book/bookList.aspx?sercate1=001001000#anchor');
console.log('url.parse():', parsedURL);
console.log('url.format():', url.format(parsedURL));
// console
> node 3_5_3_url
new URL(): URL {
href: 'http://www.gilbut.co.kr/book/bookList.aspx?sercate1=001001000#anchor',
origin: 'http://www.gilbut.co.kr',
protocol: 'http:',
username: '',
password: '',
host: 'www.gilbut.co.kr',
hostname: 'www.gilbut.co.kr',
port: '',
pathname: '/book/bookList.aspx',
search: '?sercate1=001001000',
searchParams: URLSearchParams { 'sercate1' => '001001000' },
hash: '#anchor'
}
url.format(): http://www.gilbut.co.kr/book/bookList.aspx?sercate1=001001000#anchor
--------------------------------
url.parse(): Url {
protocol: 'http:',
slashes: true,
auth: null,
host: 'www.gilbut.co.kr',
port: null,
hostname: 'www.gilbut.co.kr',
hash: '#anchor',
search: '?sercate1=001001000',
query: 'sercate1=001001000',
pathname: '/book/bookList.aspx',
path: '/book/bookList.aspx?sercate1=001001000',
href: 'http://www.gilbut.co.kr/book/bookList.aspx?sercate1=001001000#anchor'
}
url.format(): http://www.gilbut.co.kr/book/bookList.aspx?sercate1=001001000#anchor
기존 노드의 url 형식을 반드시 사용해야 하는 경우
host
부분 없이 pathname
부분만 오는 주소
WHATWG 방식은 search
부분을 searchParams
라는 특수한 객체로 반환하므로 유용
search
부분은 주소를 통해 데이터를 전달할 때 사용
?
로 시작
키=값 형식으로 데이터 전달
여러 키가 있을 경우 &
로 구분
searchParams
의 search
를 조작하는 메소드
getAll(키)
: 키에 해당하는 모든 값 반환
get(키)
: 키에 해당하는 첫 번째 값 반환
has(키)
: 해당 키가 있는지 없는지 검사
keys()
: searchParams의 모든 키를 반복기(Iterator) 객체로 반환
values()
: searchParams의 모든 값을 반복기(Iterator) 객체로 반환
append(키, 값)
: 해당 키를 추가. 같은 키의 값이 있다면 유지하고 추가.
set(키, 값)
: 해당 키를 추가. 같은 키의 값이 있다면 모두 삭제하고 추가.
delete(키)
: 해당 키를 제거
toString()
: 조작한 searchParams
객체를 다시 문자열로 조립. 이 문자열을 search에 대입하면 주소 객체에 반영
예시 코드 2
// 3_5_3_searchParams.js
const { URL } = require('url');
const myURL = new URL('http://www.gilbut.co.kr/?page=3&limit=10&category=nodejs&category=javascript');
console.log('searchParams:', myURL.searchParams);
console.log('searchParams.getAll():', myURL.searchParams.getAll('category'));
console.log('searchParams.get():', myURL.searchParams.get('limit'));
console.log('searchParams.has():', myURL.searchParams.has('page'));
console.log('searchParams.keys():', myURL.searchParams.keys());
console.log('searchParams.values():', myURL.searchParams.values());
myURL.searchParams.append('filter', 'es3');
myURL.searchParams.append('filter', 'es5');
console.log(myURL.searchParams.getAll('filter'));
myURL.searchParams.append('filter', 'es6');
console.log(myURL.searchParams.getAll('filter'));
myURL.searchParams.delete('filter');
console.log(myURL.searchParams.getAll('filter'));
console.log('searchParams.toString():', myURL.searchParams.toString());
myURL.search = myURL.searchParams.toString();
// console
> node 3_5_3_searchParams.js
searchParams: URLSearchParams {
'page' => '3',
'limit' => '10',
'category' => 'nodejs',
'category' => 'javascript' }
searchParams.getAll(): [ 'nodejs', 'javascript' ]
searchParams.get(): 10
searchParams.has(): true
searchParams.keys(): URLSearchParams Iterator { 'page', 'limit', 'category', 'category' }
searchParams.values(): URLSearchParams Iterator { '3', '10', 'nodejs', 'javascript' }
[ 'es3', 'es5' ]
[ 'es3', 'es5', 'es6' ]
[]
searchParams.toString(): page=3&limit=10&category=nodejs&category=javascript
기존 노드의 url을 사용할 때, search
부분을 사용하기 쉽게 객체로 만드는 모듈
메소드
querystring.parse(쿼리) : url의 query 부분을 자바스크립트 객체로 분해
querystring.stringify(객체) : 분해된 query 객체를 문자열로 조립
예시 코드
const url = require('url');
const querystring = require('querystring');
const parsedURL = url.parse('http://www.gilbut.co.kr/?page=3&limit=10&category=nodejs&category=javascript');
const query = querystring.parse(parsedURL.query);
console.log('querystring.parse():', query);
console.log('querystring.stringify():', querystring.stringify(query));
// console
> node 3_5_4.js
querystring.parse(): [Object: null prototype] {
page: '3',
limit: '10',
category: [ 'nodejs', 'javascript' ]
}
querystring.stringify(): page=3&limit=10&category=nodejs&category=javascript
복호화할 수 없는 암호화 방식
비밀번호를 암호화할 때 주로 사용
해시 함수라고도 한다.
해시 기법을 주로 사용한다.
메소드
createHash(알고리즘)
사용할 해시 알고리즘 입력
md5, sha1, sha256, sha512 등
md5, sha1은 취약점이 발견됨
update(문자열)
digest(인코딩)
인코딩할 알고리즘 입력. 결과물로 변환된 문자열을 반환한다.
base64, hex, latin1 등
예시 코드 1
// 3_5_5_1_hash.js
const crypto = require('crypto');
console.log('base64:', crypto.createHash('sha512').update('비밀번호').digest('base64'));
console.log('hex:', crypto.createHash('sha512').update('비밀번호').digest('hex'));
console.log('base64:', crypto.createHash('sha512').update('다른 비밀번호').digest('base64'));
// console
> node 3_5_5_1_hash.js
base64: dvfV6nyLRRt3NxKSlTHOkkEGgqW2HRtfu19Ou/psUXvwlebbXCboxIPmDYOFRIpqav2eUTBFuHaZri5x+usy1g==
hex: 76f7d5ea7c8b451b773712929531ce92410682a5b61d1b5fbb5f4ebbfa6c517bf095e6db5c26e8c483e60d8385448a6a6afd9e513045b87699ae2e71faeb32d6
base64: cx49cjC8ctKtMzwJGBY853itZeb6qxzXGvuUJkbWTGn5VXAFbAwXGEOxU2Qksoj+aM2GWPhc1O7mmkyohXMsQw==
알고리즘
pbkdf2
, bcrypt
, scrypt
pbkdf2
설명
기존 문자열에 salt라고 불리는 문자열을 붙인 후 해시 알고리즘을 반복해서 적용하는 알고리즘이다.
먼저 randomBytes()
메소드로 64바이트 길이 문자열을 만든다. ⇒ salt
pbkdf2()
메소드에 순서대로 비밀번호 / salt / 반복 횟수 / 출력 바이트 / 해시 알고리즘을 인수로 입력한다.
예시 코드 2
// 3_5_5_1_pbkdf2.js
const crypto = require('crypto');
crypto.randomBytes(64, (err, buf) => {
const salt = buf.toString('base64');
console.log('salt:', salt);
crypto.pbkdf2('비밀번호', salt, 100000, 64, 'sha512', (err, key) => {
console.log('password:', key.toString('base64'));
});
});
// console
> node 3_5_5_1_pbkdf2.js
salt: pt0VZ18HqnwDIw9iZA5VflI+/pCgi23s6StPq7qC+BxvFrwDlFguqV3humZoJxGnyCFdW+K0GC4juTaypfC46A==
password: IxPa382uro2oBuA6MsfNasGg0W1vCd4bLGVR8l24bKX4hWvnJSocDbn9woQUtdn5OYptwL/HqQfQ2Zg0dY1Y0g==
암호화된 문자열을 복호화할 수 있으며, 키(열쇠)라는 것이 사용된다.
메소드
crypto.createCipheriv(알고리즘, 키, iv)
암호화 알고리즘과 키, iv를 넣는다.
iv : 암호화할 때 사용하는 초기화 벡터
AES 암호화에 대한 공부가 필요하다.
cipher.update(문자열, 인코딩, 출력 인코딩)
cipher.final(출력 인코딩)
crypto.createDecipheriv(알고리즘, 키, iv)
복호화할 때 사용한다.
암호화할 때 사용한 정보를 그대로 넣어야한다.
decipher.update(문자열, 인코딩, 출력 인코딩)
decipher.final(출력 인코딩)
예시 코드
// 3_5_5_2_cipher.js
const crypto = require('crypto');
const algorithm = 'aes-256-cbc';
const key = 'abcdefghijklmnopqrstuvwxyz123456';
const iv = '1234567890123456';
const cipher = crypto.createCipheriv(algorithm, key, iv);
let result = cipher.update('암호화할 문장', 'utf8', 'base64');
result += cipher.final('base64');
console.log('암호화:', result);
const decipher = crypto.createDecipheriv(algorithm, key, iv);
let result2 = decipher.update(result, 'base64', 'utf8');
result2 += decipher.final('utf8');
console.log('복호화:', result2);
// console
> node 3_5_5_2_cipher.js
암호화: iiopeG2GsYlk6ccoBoFvEH2EBDMWv1kK9bNuDjYxiN0=
복호화: 암호화할 문장
각종 편의 기능을 모아둔 모듈
메소드
util.deprecate
util.promisify
deprecated
중요도가 떨어져 더 이상 사용되지 않고 앞으로는 사라지게 될 것이라는 의미
새로운 기능이 기존 기능보다 더 좋을 때, 레거시 기능인 기존 기능을 보통 이 처리를 한다.
자료 : 교과서 “Node.js 교과서” ch. 4 http 모듈로 서버 만들기 p. 169
실제 서버 동작에 필요한 쿠키와 세션 처리, 요청 주소별 라우팅 방법
p. 170 ~ 178
클라이언트에서 서버로 요청(request)
서버는 요청의 내용을 읽고 처리, 클라이언트에 응답(response)
서버에는 요청을 받는 부분과 응답을 보내는 부분이 필요
요청과 응답 ⇒ 이벤트 방식
이벤트 리스너 등록
http 서버가 있어야 웹 브라우저의 요청을 처리 가능
⇒ http 모듈 사용
http 모듈 createServer
메소드
인수로 콜백 함수 → 요청이 들어올 때마다 콜백 함수 실행 → 콜백 함수에 응답을 작성
콜백 부분의 req
, res
매개변수
req
요청, request의 준말 - 물론 변수이므로 이름 변경 가능
요청에 관한 정보
res
응답, response의 준말 - 물론 변수이므로 이름 변경 가능
응답에 관한 정보
메소드
res.writeHead
응답에 대한 정보를 기록
인수
HTTP 상태
200
(성공적인 요청)콘텐츠의 형식
이 정보가 기록되는 부분 : 헤더(Header)
res.write
데이터 전송
인수
클라이언트로 보낼 데이터
여러 번 호출해 데이터를 여러 개 전송하는 것도 가능
데이터가 기록되는 부분 : 본문(Body)
res.end
응답을 종료
인수가 있다면 클라이언트로 보내고 종료
예시 코드 1
// 4_1_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번 포트에서 서버 대기 중입니다!')
})
⇒ http://localhost:8080
혹은 http://127.0.0.1:8080
에 접속
localhost, IP 주소, 포트
localhost는 현재 컴퓨터의 내부 주소. 127.0.0.1도 동일.
위와 같은 숫자 주소를 IP(Internet Protocol)이라 한다.포트 : 서버 내의 프로세스 구분 번호. IP 주소 뒤에 콜론(
:
)과 함께 붙여 사용
- 유명한 포트 번호
- 21 : FTP
- 80 : HTTP
- 443 : HTTPS
- 3306 : MYSQL
- 보통 사이트들은 포트 번호를 따로 표시하지 않는다. http, https의 포트 80, 443을 생략
- 한 포트에 한 서비스 - 두 서비스가 같은 포트를 사용하면 에러 발생
- 리눅스와 맥은 1024번 이하 포트에 연결할 때 관리자 권한 필요 → 명령어 앞에
sudo
를 붙인다.
node server1 → sudo node server1
예시 코드 2
listen
메소드에 콜백 함수를 넣는 대신 서버에 listening
이벤트 리스너를 붙이고 error
이벤트 리스너 추가// 4_1_server1-1.js
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.end('<p>Hello Server!</p>');
})
server.listen(8080); // 서버 연결
server.on('listening', () => {
console.log('8080번 포트에서 서버 대기 중입니다!');
});
server.on('error', (error) => {
console.error(error);
});
예시 코드 3
한 번에 여러 서버도 실행 가능
서로 다른 포트를 사용해 포트 충돌 회피
// 4_1_server1-2.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 Server1!</p>');
})
.listen(8080, () => { // 서버 연결
console.log('8080번 포트에서 서버 대기 중입니다!')
})
http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.write('<h1>Hello Node!</h1>');
res.end('<p>Hello Server2!</p>');
})
.listen(8081, () => { // 서버 연결
console.log('8081번 포트에서 서버 대기 중입니다!')
})
예시 코드 4
HTML 파일을 fs 모듈로 읽어 전송
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Node.js 웹 서버</title>
</head>
<body>
<h1>Node.js 웹 서버</h1>
<p>만들 준비되셨나요?</p>
</body>
</html>
const http = require('http');
const fs = require('fs').promises
http.createServer(async (req, res) => {
try {
const data = await fs.readFile('./4_1_server2.html');
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(data);
} catch (err) {
console.error(err);
res.writeHead(500, {'Content-Type': 'text/plain; charset=utf-8' });
res.end(err.message);
}
})
.listen(8081, () => { // 서버 연결
console.log('8081번 포트에서 서버 대기 중입니다!')
})
HTTP 상태 코드
- 브라우저는 서버에서 보내주는 상태 코드로 요청이 성공 여부를 판단
- 2XX : 성공
- 200 성공
- 201 작성됨
- 3XX : 리다이렉션(다른 페이지로 이동). 어떤 주소를 입력했는데 다른 주소의 페이지로 넘어갈 때
- 301 (영구 이동)
- 302 (임시 이동)
- 304 (수정되지 않음)
- 4XX : 요청 오류
- 400 (잘못된 요청)
- 401 (권한 없음)
- 403 (금지됨)
- 404 (찾을 수 없음)
- 5XX : 서버 오류
- 500 (내부 서버 오류)
- 502 (불량 게이트웨이)
- 503 (서비스를 사용할 수 없음)
응답은 반드시 이루어져야 한다
응답은 요청이 마무리 되었음을 알린다
응답이 없으면 클라이언트는 서버로부터의 응답만 기다리다가 Timeout(시간 초과) 처리한다.