REPL
⇒ 자바스크립트는 스크립트 언어라 즉석에서 코드 실행이 가능하다.
- REPL이란 입력된 명령어를 읽어(Read) 평가하고(Eval) 평가한 결과를 출력(Print) 후 다시 명령어 입력을
과정을 반복(Loop)하는 과정을 의미한다.
- 윈도우의 명령 프롬프트 / 맥 or 리눅스의 터미널에 node 입력 등
ex) node console에서 REPL의 과정의 예
> const str = 'Hello node';
undefined
> console.log(str);
'Hello node'
undefined
ex2) 윈도우 명령 프롬프트에서 js파일을 실행시키는 예시
Microsoft Windows [Version 10.0.19045.3693]
(c) Microsoft Corporation. All rights reserved.
C:\nodejs>node helloWorld
Hello world
hello node
C:\nodejs>
모듈
⇒ 노드는 자바스크립트 코드를 모듈로 만들 수 있다.
- 모듈은 특정한 기능을 하는 함수나 변수들의 집합이며 모듈로 구현 시 여러 프로그램에서 재 사용이 가능하다.
- 모듈은 파일 끝에 module.exports로 모듈로 만들 값을 지정한다.
- 지정한 모듈의 값을 다른 파일에서 require(파일경로)로 그 모듈의 내용을 가져와 사용할 수 있다.
ex)모듈의 파일 구성 예시
var.js
const odd = '홀수';
const even = '짝수';
// 객체를 module.exports에 대입
module.exports = { odd, even };
// module.exports와 exports는 같다.(빈객체)
// 한가지만 exports하고 싶을때는 module.exports를 사용한다.
// 두가지 이상을 exports하고 싶을 때는
// exports.add = add; exports.even = even;
// module.exports = { odd, even };
// 두 방식중 하나를 사용한다.
// module.exports === exports === {}
func.js
// require를 이용해 모듈을 참조한다.
const value = require('./var.js');
value.even;
value.odd;
// 구조분해 할당을 통해 모듈로 얻어온 객체의 값을 선언한다.
const {odd, even} = require('./var.js');
console.log(value, odd, even);
function checkOddOrEven(number) {
if(number % 2) {
return odd;
} else {
return even;
}
}
// module.export는 하나의 파일에서 한번만 써울 수 있다.
module.exports = checkOddOrEven;
index.js
const {odd, even} = require('./var');
const checkNumber = require('./func');
function checkStringOddOrEven(str) {
if(str.length % 2) {
return odd;
} else {
return even;
}
}
console.log(checkNumber(10));
console.log(checkStringOddOrEven('hello'));
cmd
C:\nodejs>node func
{ odd: '홀수', even: '짝수' } 홀수 짝수
C:\nodejs>node index
{ odd: '홀수', even: '짝수' } 홀수 짝수
짝수
홀수
C:\nodejs>
this
⇒ 노드에서 this 사용시 정말 주의해야 할 점이 존재한다.
- 최상위 스코프의 this는 module.exports를 가르킨다.
- 그 외에는 브라우저의 자바스크립트와 동일하다.
- 함수 선언문 내부의 this는 global(전역) 객체를 가르킨다.
console.log(this === global); // false
console.log(this === mobule.exports); // true
function a() {
console.log(this === global); // true
}
a();
require의 특성
⇒ 몇 가지 숙지해둬야 할 속성들이 존재한다.
- require가 소스 최상단에 올 필요는 없다.
- require.main과 module은 동일하다.
- require.cache에는 한번 require한 모듈에 대한 캐싱 정보가 들어있다.
그러므로 다음번 require 선언 시 다시 파일을 불러오는 것이 아니라 캐시(메모리)에서 불러온다.
- require.main은 노드 실행 시 첫 모듈을 가르킨다.
module.exports = '저를 찾아보세요';
// 선언을 안하면 다른 파일을 실행만 하고 변수에 담지 않는다.
// require는 소스 가장 위에 있지 않아도 된다.
require('./var');
console.log('require.cache입니다.');
console.log(require.cache);
console.log('require.main입니다.');
console.log(require.main === module); // true
console.log(require.main.filename) // c:\nodejs\require.js
순환참조
⇒ 두 개의 모듈이 서로를 require하는 상황을 조심해야 하며 위 상황을 순환참조라 부른다.
- dep1이 dep2를 require하고, dep2가 dep1을 requrie함
- dep1 module.exports가 지정한 값이 아닌 빈 객체로 dep2에 참조가 된다.(무한반복을 막기 위한 의도)
- 순환참조라는 상황은 무한루프를 야기하기 때문에 해당 상황이 존재하지 않도록 소스 구성을 하자.
dep1.js
require('./dep2');
module.exports = {
hello : 'zerocho'
}
dep2.js
// 순환참조를 막아주기 위해 빈객체를 반환한다.
// exrpots한 값을 얻어올 수 없다.
require('./dep1'); // {}
ECMAScript
⇒ ECMAScript 모듈(ES모듈)은 공식적인 자바스크립트 모듈 형식이다.
- 노드에서는 현재까진 CommonJS모듈을 많이 쓰지만, ES모듈이 정해지면서 점차 ES모듈 사용 비중이 높아지고 있다.
- 특히 브라우저에서도 ES모듈을 사용 가능하기에 브라우저와 노드 모두 같은 형식을 띌 수 있는 장점이 있다.
- commonJs에서는 requrie, module, exports는 객체이기 때문에 참조를 끊거나 객체를 엎어씌울수 있지만
ES모듈의 export, default는 예약어이기 때문에 사용자가 직접 제어할 수 없다.
// var.mjs
export const odd = 'mjs 홀수입니다.'; // commonJs = export.odd = 'mjs 홀수입니다.';
export const even = 'mjs 짝수입니다.';
// func.mjs
import { odd, even } from './var.mjs'; // commonJs = const {odd, even} require('./var.cjs');
function checkOddOrEven(num) {
if(num %2) {
return odd;
} else {
return even;
}
}
export default checkOddOrEven; // commonJs = module.exrpots = checkOddOrEven;
// index.mjs
import { odd, even } from './var.mjs';
import checkNumber from './func.mjs';
function checkStringOddOrEven(str) {
if(str.length %2) {
return odd;
} else {
return even;
}
}
console.log(checkNumber(10)); // mjs 짝수입니다.
console.log(checkStringOddOrEven('hello')); // mjs 홀수입니다.
다이나믹 임포트
⇒ commonJs에서는 require를 어디에서든 선언해 사용할 수 있다.
const a = false;
if (a) {
require('./func');
}
console.log('성공');
⇒ 하지만 ES 모듈에서는 첫번째 라인이 아닌 다른곳에서 import를 그냥 사용시에는 오류가 발생한다.
해당 오류를 해결하기 위해 import()함수를 호출해 어디서든 해당 소스를 실행할 수 있다.
const a = true;
if (a) {
// import './func.mjs'; // SyntaxError: Unexpected string
// import()함수는 Promise이다.
// 그렇기 때문에 await 넣어야한다.
// top level await가 있다.
const m1 = await import('./func.mjs'); // export의 default가 commonJS의 mobule.exports와는 다르다.
// requrie mobule.exports = [Function: checkOddOrEven]
// import = { default: [Function: checkOddOrEven] }
console.log(m1);
const m2 = await import('./var.mjs');
console.log(m2);
}
console.log('성공');
__filename, __dirname
⇒ commonJS 기준 노드 소스에 해당 키워드를 넣고 실행 시 현재 파일명/파일경로를 얻어올 수 있다.
하지만, ES모듈에서는 해당 키워드를 사용할 수 없다.
3.4 노드 내장 객체 알아보기
1. global
⇒ 노드의 전역 객체이다.
- 브라우저의 window와 같은 역할을 하며 모든 파일에서 접근이 가능하다.
- javascript의 window처럼 생략도 가능하다(console, require도 global 속성)
- global 속성에 값을 대입 시 다른 파일에서도 사용이 가능하다. 하지만, 해당 속성을 어디서 부여하는지
알 방도가 없어 관리가 어렵기에 선호하지 않는다.
// globalA.js 파일
module.exports = () => globalThis.message;
// globalB.js 파일
const A = require('./globalA');
globalThis.message = '안녕하세요';
console.log(A());
2. console 객체
- console.time, console.timeEnd(퍼포먼스 체크)
: 소스 위치의 time지점부터 timeEnd지점까지의 시간이 얼마나 걸리는지 체크할 수 있다.
- console.log(로그), console.error(에러로깅), console.dir(객체로깅(console.dir({hello:'hello'});))
- console.trace(호출스택 로깅)
: 자바 스크립트의 호출 스택 로깅을 확인할 수 있다.
const string = 'abc';
const number = 1;
const boolean = true;
const obj = {
outSide : {
inSide: {
key : 'value',
},
},
};
console.time('전체 시간');
console.log('평범한 로그 출력');
console.log(string, number, boolean);
console.error('에러 메시지는 console.error 사용');
console.table([{ name : '제로', birth : 1994}, {name : '재익', birth: 1986}]);
console.dir(obj, {colors : false, depth : 2});
console.dir(obj, {colors : true, depth : 1});
console.time('시간 측정');
for(let i = 0; i < 100000; i++) {}
console.timeEnd('시간 측정');
function b() {
console.trace('에러 위치 추적');
}
function a() {
b();
}
a();
console.timeEnd('전체 시간');
3. 타이머 메서드
⇒ 타이머 set 메서드에 clear 메서드가 매칭되며 set 메서드의 리턴 값(아이디)으로 clear 메서드에 넣어 취소할 수 있다.
- setTimeout(콜백함수, 밀리초) : 주어진 밀리초(1000분의 1초) 이후에 콜백함수를 실행한다.
- setIntarval(콜백함수, 밀리초) : 주어진 밀리초보다 콜백함수를 실행한다.
- setImmediate(콜백함수) : 콜백함수를 즉시 실행하며 즉시 실행 시 setTimeout(() => console.log('hello',0)과 동일하다.
: 굳이 타이머 메서드를 사용하지 않고 즉시 실행하는 것과 무엇이 다른지 의문점이 생길 수 있다.
타이머 메서드는 비동기 메서드이며 특정 함수들을 백 그라운드에 모아 한번에 실행이 필요한 경우가 있다.
해당 경우에 타이머 필요 없을 시 setImmediate 함수를 사용하여 백 그라운드로 특정 함수들을 한번에 보내 처리한다.
const timeout = setTimeout(() => {
console.log('1.5초후 실행');
},1500);
// 하단의 clearInterval로 인해 2번만 수행된다.
const interval = setInterval(()=> {
console.log('1초마다 실행');
},1000);
// 하단의 clearTimeout으로 인해 수행되지 않는다.
const timeout2 = setTimeout(() => {
console.log('setTimeclear로 제거예정');
}, 3000);
setTimeout(() => {
clearTimeout(timeout2);
clearInterval(interval);
}, 2500);
// setImmediate는 수행되지 않는다.
// Promise인 타이머함수의 실행 순서는 다음과 같다.
// 호출스택 -> 백그라운드 -> 태스트큐 -> 호출스택순이기 때문이다.
const immediate = setImmediate(() => {
console.log('즉시 실행');
});
const immediate2 = setImmediate(() => {
console.log('즉시 실행');
});
clearImmediate(immediate2);
// 즉시 실행
// 1초마다 실행
// 1.5초후 실행
// 1초마다 실행
12. process
⇒ 현재 실행중인 노드 프로세스에 대한 정보를 담고 있다.
- 컴퓨터마다 출력 값이 다를수도 있다. 왜냐하면 프로세스가 실제 실행되는 환경을 보여주기 때문.
C:\nodejs>node
Welcome to Node.js v21.4.0.
Type ".help" for more information.
> process.version // 설치된 노드의 버전
'v21.4.0'
> process.arch // 프로세서 아키텍처 정보. arm, ia32 등의 값일수도 있다.
'x64'
> process.platform // 운영체제 플랫폼 정보. linux나 darwin, freebsd 등의 값일수도 있다.
'win32'
> process.pid // 현재 프로세스의 아이디이다. 프로세스를 여러 개 가질 때 구분할 수 있다.
19756
> process.uptime() // 프로세스가 시작된 후 흐른 시간이다.
74.3572628
> process.execPath // 노드의 설치되어 있는 경로이다.
'C:\\Program Files\\nodejs\\node.exe'
> process.cwd() // 현재 프로세스가 실행되는 위치이다.
'C:\\nodejs'
> process.cpuUsage() // 현재 CPU 사용량
{ user: 765000, system: 625000 }
13. process.env
⇒ 시스템 환경 변수들이 들고있는 객체
- 비밀키(데이터베이스 비밀번호, 서드파트 앱 키 등)를 보관하는 용도로 쓰임
- 환경변수는 process.env로 접근이 가능하다.
const secretId = process.env.SECRET_ID;
const secretCode = process.env.SECRET_CODE;
- 일부 환경변수는 노드 실행 시 노드 환경에 영향을 줄 수 있다.
ex) NODE_OPTIONS(노드실행옵션), UV_THREADPOOL_SIZE(스레드풀 개수)
// --max-old-space-size는 노드가 사용할 수 있는 메모리를 지정한다.
NODE_OPTIONS=--max-old-space-size=8192
UV_THREADPOOL_SIZE=8
14. process.nextTick(콜백)
⇒ 이벤트 루프의 다른 콜백 함수들보다 nextTick의 콜백함수를 우선적으로 처리한다.
- promise와 nextTick은 마이크로 태스크에서 수행되므로 실행순서가 순차적으로 보장된다.
- 마이크로 태스크는 일반 태스크보다 우선순위가 높아 setTimeout, setImmediate가 후순위다.
setImmediate(() => {
console.log('immediate');
});
process.nextTick(() => {
console.log('nextTick');
});
setTimeout(() => {
console.log('timeout');
},0);
Promise.resolve().then(() => console.log('promise'));
// nextTick -> promise -> timeout -> immediate
// nextTick과 promise는 마이크로 태스크이기 때문에 순서가 정해져 있다.
// 하지만, timeout과 immediate는 순서가 환경에 따라 달라질 수 있다.
// 그러므로 setImmediate만 사용하는 편이 좋다.
15. process.exit(코드)
⇒ 현재의 프로세스를 멈추며 코드가 없거나 0이면 정상종료이며 그 외의 코드는 비정상종료를 의미한다.
3.5 노드 내장 모듈 알아보기
1. os
⇒ 운영체제의 정보를 담고 있는 내장 모듈이다.
- 모듈은 require로 가져올 수도 있다.(내장모듈이라 경로 대신 이름만 적어줘도 된다.)
2. os모듈의 메서드
const os = require('os');
console.log('운영체제 정보-----------------------------');
console.log('os.arch(): ', os.arch()); // x64(process.arch와 동일)
console.log('os.platform(): ', os.platform()); // win32(process.platform과 동일)
console.log('os.type(): ',os.type()); // Window_NT(운영체제의 종류)
console.log('os.uptime(): ',os.uptime()); // 38727.328(운영체제 부팅 이후 흐른 시간(초))
console.log('os.hostname(): ',os.hostname()); // DESKTOP-EOFDG1G(컴퓨터의이름)
console.log('os.release(): ',os.release()); // 10.0.19045(운영체제의 버전)
console.log('경로-------------------------------------');
console.log('os.homedir(): ',os.homedir()); // C:\Users\jikim(홈 디렉토리 경로)
console.log('os.tmpdir(): ',os.tmpdir()); // C:\Users\jikim\AppData\Local\Temp(임시파일저장경로)
console.log('cpu정보----------------------------------');
console.log('os.cpus(): ',os.cpus()); // cpu의 코어정보
console.log('os.cpus().length: ',os.cpus().length); // cpu 코어개수
console.log('메모리정보--------------------------------');
console.log('os.freemem(): ',os.freemem()); // 사용 가능한 메모리(RAM)을 보여준다.
console.log('os.totalmem(): ',os.totalmem()); // 전체 메모리 용량을 보여준다.
3. path
⇒ 폴더와 파일의 경로를 쉽게 조작하도록 도와주는 모듈이다.
- 운영체제별로 경로 구분자가 다르다.(Window : ' \ ' , POSIX : ' / ')
4. path 모듈 메서드
const path = require('path');
const fileName = __filename;
// 윈도우는 C:\users\zerocho
// 맥은 C:/user/zerocho
// window나 POSIX(max,linux)의 /,\를 os따라 맞는걸로 처리해준다.
// console.log(__dirname);
// console.log(path.join(__dirname, '..', '/var.js')); // ..입력 시 상위경로
// console.log(path.resolve(__dirname, '..', '/var.js')); // ..입력 시 상위경로
console.log(path.join(__dirname, '/var.js')); // join은 절대경로를 무시한다.
console.log(path.resolve(__dirname, '/var.js')); // resolve는 절대경로가 들어있으면 앞의경로를 무시한다.
console.log('path.sep: ', path.sep); // \(경로의 구분자이다.)
console.log('path.delimiter: ', path.delimiter); // ;(환경 변수의 구분자이다.)
console.log('path.dirname(): ',path.dirname(fileName)); // C:\nodejs(파일이 위치한 경로)
console.log('path.extname(): ',path.extname(fileName)); // .js(파일의 확장자)
console.log('path.basename(): ',path.basename(fileName)); // path.js(파일의이름(확장자포함))
console.log('path.basename - extname: ',path.basename(fileName, path.extname(fileName))); // path
// {root: 'C:\\',dir: 'C:\\nodejs',base: 'path.js',ext: '.js',name: 'path'}
console.log('path.parse()', path.parse(fileName)); // 파일경로를 root, dir, base, ext, name으로 분리
console.log('path.normalize()', path.normalize('c://nodejs\\\path.js')); // /나 \를 정상적인 경로로 변경처리
// isAbsolute는 현재 경로가 절대경로일 경우 true를 반환한다.
console.log('path.isAbsolute("c:\\")', path.isAbsolute('c:\\')); // true
console.log('path.isAbsolute("./nodejs)', path.isAbsolute('./nodejs')); // false
5. 알아둬야할 path 관련 정보
⇒ join과 resolve의 차이 : resolve는 ' / ' 를 절대경로로 처리한다.
join은 상대경로로 처리한다.
- 상대 경로 : 현재 파일 기준. 같은 경로면 점 하나( . ), 한 단계 상위 경로면 점 두개( .. )
- 절대 경로는 루트 폴더나 프로세스가 실행되는 위치가 기준이다.
path.join('/a','/b','c'); // /a/b/c
path.resolve('/a,'/b','c'); // b/c // a는 resolve에서는 절대경로이다.(/a)
⇒ \\ 와 \ 의 차이 : \ 는 윈도우의 경로 구분자, \\ 는 자바스크립트 문자열 안에서 사용된다.
⇒ 윈도우에서 POSIX path를 사용 : path.posix 객체를 사용하면 된다.
⇒ POSIX에서 윈도우 path를 사용 : path.win32 객체를 사용하면 된다.
6. url
⇒ 인터넷 주소를 쉽게 조작하도록 도와주는 모듈이다.
- url 처리방식은 노드 버전 7에서 추가된 WHATWG(웹 표준을 정하는 단체명) url과 과거 노드 사용 url 방식이 있다.
현재는 WHATWG url 방식만 사용한다.
- WHATWG방식은 브라우저에서도 동일하게 사용되므로 호환성이 좋다.
- url 모듈 안에 URL 생성자가 있어 이 생성자에 주소를 넣어 객체로 만들면 부분별로 정리가 된다.
- 해당 방식이 WHATWG의 url이며 username, password, origin, searchParams 속성이 존재한다.
// 내장객체이므로 require를 사용할 필요가 없다.
const url = require('url');
const {URL} = url;
const myURL = new URL('https://class.tsherpa.co.kr/board/add.html?category_id=613245&content_id=1931654&classNo=7174&fromBoardAll=Y')
console.log('new URL(): ', myURL);
// new URL(): URL {
// href: 'https://class.tsherpa.co.kr/board/add.html?category_id=613245&content_id=1931654&classNo=7174&fromBoardAll=Y',
// origin: 'https://class.tsherpa.co.kr',
// protocol: 'https:',
// username: '',
// password: '',
// host: 'class.tsherpa.co.kr',
// hostname: 'class.tsherpa.co.kr',
// port: '',
// pathname: '/board/add.html',
// search: '?category_id=613245&content_id=1931654&classNo=7174&fromBoardAll=Y',
// searchParams: URLSearchParams {
// 'category_id' => '613245',
// 'content_id' => '1931654',
// 'classNo' => '7174',
// 'fromBoardAll' => 'Y' },
// hash: ''
// }
console.log('url.format(): ', url.format(myURL));
// https://class.tsherpa.co.kr/board/all.html?classNo=7174
7. searchParams
>> queryString을 searchParams로 전환하는게 좋다.
const myURL = new URL('https://class.tsherpa.co.kr/board/add.html?category_id=613245&content_id=1931654&classNo=7174&fromBoardAll=Y&category_id=2029029');
console.log('searchParams:', myURL.searchParams); // {}
console.log('searchParams.get()',myURL.searchParams.get('category_id')); // '613245' key에 대한 첫번째 값만 얻어온다.
console.log('searchParams.getAll()',myURL.searchParams.getAll('content_id')); // ['193165']
console.log('searchParams.getAll()',myURL.searchParams.getAll('category_id')); // ['613245','2029029']
console.log('searchParams.has()',myURL.searchParams.has('fromBoardAll')); // true(속성존재여부)
console.log('searchParams.has()',myURL.searchParams.has('notAttribute')); // false
// keys or values 키 혹은 값에 대해서만 반복기(iterator(ES2015문법)) 객체로 가져온다.
// URLSearchParams Iterator {'category_id','content_id','classNo','fromBoardAll','category_id'}
console.log('searchParams.values()',myURL.searchParams.keys('category_id'));
// URLSearchParams Iterator { '613245', '1931654', '7174', 'Y', '2029029' }
console.log('searchParams.values()',myURL.searchParams.values('category_id'));
// append는 속성을 연속적으로 추가할 수 있다.
myURL.searchParams.append('filter', 'es3');
myURL.searchParams.append('filter','es5');
console.log(myURL.searchParams.getAll('filter')) // ['es3', 'es5']
// set은 key가 동일할 경우 기존 값은 초기화되고 새로운 값이 들어간다.
myURL.searchParams.set('filter','es6');
console.log(myURL.searchParams.getAll('filter')); // ['es6']
// delete는 해당 key를 삭제한다.
myURL.searchParams.delete('filter');
console.log(myURL.searchParams.getAll('filter')); // []
// category_id=613245&content_id=1931654&classNo=7174&fromBoardAll=Y&category_id=2029029
console.log('searchParams.toString() ',myURL.searchParams.toString());
myURL.search = myURL.searchParams.toString();
8. dns
>> DNS를 다룰 때 사용하는 모듈이다. 주로 도메인을 통해 IP, 기타 DNS 정보를 얻고자 할때 사용한다.
import dns from 'dns/promises';
const ip = await dns.lookup('class.tsherpa.co.kr');
console.log('IP',ip); // ip주소를 얻어온다
const a = await dns.resolve('class.tsherpa.co.kr', 'A');
console.log('A',a); // ipv4 주소
const mx = await dns.resolve('class.tsherpa.co.kr','MX');
console.log('MX',mx); // 메일서버
const cname = await dns.resolve('www.class.tsherpa.co.kr','CNAME');
console.log('CNAME',cname); // 별칭, (주로 www.가 붙는 주소는 별칭인 경우가 많음)
const nay = await dns.resolve('class.tsherpa.co.kr','ANY');
console.log('ANY',nay); // 그외의 정보들
13. 단방향 암호화(crypto)
>> 암호화는 가능하지만 복호화는 불가능하다.
- 암호화 : 평문을 암호로 만듦.
- 복호화 : 암호를 평문으로 해독
>> 단방향 암호화의 대표 주자는 해시 기법(ex)비밀번호)
- 문자열을 고정된 길이의 다른 문자열로 바꾸는 방식
ex) abcdefgh 문자열 -> qvew
- 비밀번호 ------> 해쉬 함수 ------> 다이제스트
<--x--- <------
14. Hash 사용하기(sha512 추천)
>> createHash(알고리즘): 사용할 해시 알고리즘을 넣는다.
- md5, sha1, sha256, sha512등이 가능하지만, md5, sha1은 보안 취약점이 발견 되었다.
- 현재는 sha512정도로 충분하지만, 나중에 sha512마저도 취약해지면 더 강화된 알고리즘으로 바꿔야한다.
>> update(문자열) : 변환할 문자열을 넣어준다.
>> digest(인코딩) : 인코딩할 알고리즘을 넣어준다.
- base64, hex, latin1이 주로 사용되는데, 그중 base64가 결과 문자열이 가장 짧아 애용된다.
결과물로 변환된 문자열을 반환한다.
const crypto = require('crypto');
console.log('base64', crypto.createHash('sha512').update('비밀번호').digest('base64'));
console.log('base64', crypto.createHash('sha512').update('비밀번호').digest('hex'));
console.log('base64', crypto.createHash('sha512').update('다른 비밀번호').digest('base64'));
// base64 dvfV6nyLRRt3NxKSlTHOkkEGgqW2HRtfu19Ou/psUXvwlebbXCboxIPmDYOFRIpqav2eUTBFuHaZri5x+usy1g==
// base64 76f7d5ea7c8b451b773712929531ce92410682a5b61d1b5fbb5f4ebbfa6c517bf095e6db5c26e8c483e60d8385448a6a6afd9e513045b87699ae2e71faeb32d6
// base64 cx49cjC8ctKtMzwJGBY853itZeb6qxzXGvuUJkbWTGn5VXAFbAwXGEOxU2Qksoj+aM2GWPhc1O7mmkyohXMsQw==
15. pbkdf2
>> 컴퓨터의 발달로 기존 암호화 알고리즘이 위협받고 있다.
- sha512가 취약해지면 sha3으로 넘어가야 한다.
- 현재는 pbkdf2나, bcrypt, scrypt 알고리즘으로 비밀번호를 암호화한다.
- Node는 pbkdf2와 scrypt를 지원한다.
const crypto = require('crypto');
crypto.randomBytes(64, (err, buf) => {
// crypto.randomBytes로 64바이트 문자열 생성 -> salt 역할
const salt = buf.toString('base64');
// pbkdf2 인수로 순서대로 비밀번호, salt, 반복 횟수, 출력 바이트, 알고리즘 입력
// 반복 횟수를 조정해 암호화하는데 1초 정도 걸리게 맞추는 것이 권장된다.
crypto.pbkdf2('비밀번호', salt, 100000, 64, 'sha512',(err,key) => {
console.log('password : ', key.toString('base64'));
});
});
16. 양방향(대칭형) 암호화
>> 키 관리를 할 경우 AWS Key Management Service이런것들도 사용한다.
crypto.js를 통해 암호화처리를 하는게 유용하다.
https://www.npmjs.com/package/crypto-js
- 대칭형 암호화는 AES를 추천한다.
- 비대칭 암호화는 RSA를 추천한다.
17. util
const util = require('util');
const crypto = require('crypto');
// util.deprecate의 첫 번째 인자로 넣은 함수를 사용했을 때 경고 메시지를 출력한다.
// util.deprecate의 두 번째 인자에 경고메시지를 넣는다.
const dontUserMe = util.deprecate((x,y) => {
console.log(x+y);
}, 'dontUserMe 함수는 deprecated되었으니 더 이상 사용하지 마세요!');
dontUserMe(1,2);
const randomBytesPromise = util.promisify(crypto.randomBytes);
randomBytesPromise(64)
.then((buf) => {
console.log(buf.toString('base64'));
})
.catch((error) => {
console.error(error);
})
async function randomStrFunction(num) {
const randomStr = await randomBytesPromise(num);
console.log(randomStr.toString('base64'));
}
console.log(randomStrFunction(64));
>> deprecated란 '중요도가 떨어져 더 이상 사용되지 않고 앞으로는 사라지게 될' 것이라는 뜻.
>> 각종 편의기능을 모아둔 모듈이며 deprecated와 promisify가 자주 쓰인다.
>> util.deprecate : 함수가 deprecated 처리되었음을 알려줄 수 있다.
함수가 조만간 사라지거나 변경될 것을 알려줄 수 있어 유용하다
>> util.promisify : 콜백 패턴을 프로미스 패턴으로 바꿔준다.
- 바꿀 함수를 인자로 제공하면 된다. 프로미스이므로 async/await 패턴까지 사용할 수 있다.
단, 콜백이(error,data) => {} 형식이여야 한다.
- randomBytes에는 프로미스를 콜백으로 바꾸는 util.callbackity도 있지만 잘 사용되질 않는다.
worker_thread
최종 예시는 다시 한번 복습이 필요하다.
const { Worker, isMainThread, parentPort } = require('worker_threads');
// 처음에는 메인스레드가 실행되서 내부에서 워커스레드를 생성해
// 워커스레드에 일을 분배한다. 일 처리가 끝나면 다시 메인스레드로 보내
// 메인스레드 내에서 워커스레드 내의 일을 합쳐 최종적인 결과물로 리턴한다.
if(isMainThread) { // 메인스레드
const worker = new Worker(__filename);
worker.on('message', (value) => console.log('워커로부터', value));
worker.on('exit', () => console.log('워커 끝'));
worker.postMessage('ping');
} else { // 워커스레드
parentPort.on('message', (value) => {
console.log('부모로부터', value);
parentPort.postMessage('pong');
parentPort.close();
});
}
// 처음에는 메인스레드가 실행되서 내부에서 워커스레드를 생성해
// 워커스레드에 일을 분배한다. 일 처리가 끝나면 다시 메인스레드로 보내
// 메인스레드 내에서 워커스레드 내의 일을 합쳐 최종적인 결과물로 리턴한다.
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
if(isMainThread) { // 메인스레드
const threads = new Set();
threads.add(new Worker(__filename, {
workerData : {start : 1},
}));
threads.add(new Worker(__filename, {
workerData : {start : 2},
}));
for (let worker of threads) {
worker.on('message', (value) => console.log('워커로부터', value));
worker.on('exit', () => {
threads.delete(worker);
if(threads.size === 0) {
console.log('워커 끝');
}
});
}
} else { // 워커스레드
const data = workerData;
parentPort.postMessage(data.start + 100);
}
child_process
// exec.js
const exec = require('child_process').exec;
var process = exec('dir');
process.stdout.on('data', (data) => {
console.log(data.toString('utf8'));
});
process.stderr.on('data', (data) => {
console.error(data.toString('utf8'));
});
// Node에서 파이썬을 스폰으로 호출할 수 있다.
// 파이썬을 직접 실행하는게 아니라 파이썬에게 실행해달라 요청하는 것이다.
const spawn = require('child_process').spawn;
const spawnProcess = spawn('python', ['test.py']);
spawnProcess.stdout.on('data', (data) => {
console.log(data.toString());
});
spawnProcess.stderr.on('data', (data) => {
console.error(data.toString());
});
3.6 파일 시스템 접근하기
1. fs
>> 파일 시스템에 접근하는 모듈
- 파일/폴더 생성, 삭제, 읽기 쓰기가 가능하다.
- 웹 브라우저에서는 제한적이었으나 노드는 권한을 가지고 있다.
// readFile.js
const fs = require('fs');
fs.readFile('./readme.txt', (err,data) => {
if(err) {
throw err;
}
console.log(data.toString());
});
// await도 사용이 가능하다.
const fsPromise = require('fs').promises;
fsPromise.readFile('./readme.txt')
.then((data) => {
console.log(data.toString());
})
.catch((err) => {
console.error(err);
});
async function readFunction() {
const readme = await fsPromise.readFile('./readme.txt');
return readme.toString();
}
// writeFile.js
const fsPromise = require('fs').promises;
fsPromise.writeFile('./writeFile.txt','글이 입력됩니다.')
.then(() => { // 쓰기
return fsPromise.readFile('./writeFile.txt')
})
.then((data) => { // 읽기
console.log(data.toString());
})
.catch((err) => {
console.error(err);
});
5. 동기 메서드와 비동기 메서드
>> 동기메서드
const fs = require('fs');
// 동기이기 때문에 순차적으로 실행이 된다.
// 단한번 실행하거나 서버 초기화할때 동기를 사용한다.
// 서버실행 이후에는 동기코드를 사용하면 다른 사람들이 기다려야 한다.
// 그러므로 동기작업은 사용 시 늘 주의해야 한다.
let data = fs.readFileSync('./readme.txt');
console.log('1번', data.toString());
data = fs.readFileSync('./readme.txt');
console.log('2번', data.toString());
data = fs.readFileSync('./readme.txt');
console.log('3번', data.toString());
data = fs.readFileSync('./readme.txt');
console.log('4번', data.toString());
>> 비동기메서드
// async.js
const fs = require('fs');
// 비동기 함수이기 때문에 콜백들을 백그라운드로 보내준다.
// 백그라운드로 넘어가면 동시에 수행되기 때문에 매번 순서가 바뀐다.
// 순서보장이 안된다.
fs.readFile('./readme.txt', (err, data) => {
if (err) {
throw err;
}
console.log('1번', data.toString());
});
fs.readFile('./readme.txt', (err, data) => {
if (err) {
throw err;
}
console.log('2번', data.toString());
});
fs.readFile('./readme.txt', (err, data) => {
if (err) {
throw err;
}
console.log('3번', data.toString());
});
fs.readFile('./readme.txt', (err, data) => {
if (err) {
throw err;
}
console.log('4번', data.toString());
});
// asyncOrder.js
const fs = require('fs');
// 비동기일때 콜백으로 다음순서를 넣어준다.
// 이렇게 되면 동기와 다를바가 없어 보일수도 있다.
// 하지만, asyncOrder.js파일을 실행할 때에는 각각의 파일이 비동기로 수행된다.
// 모든 파일들이 백 그라운드로 들어간다고 보면 된다.
// 방금 전 sync.js파일을 여러개 실행할 때에는 앞에 파일이 모든 실행이 끝나기 전에는 다음 파일을 실행할 수 없다.
// 콜백헬...
fs.readFile('./readme.txt', (err, data) => {
if (err) {
throw err;
}
console.log('1번', data.toString());
fs.readFile('./readme.txt', (err, data) => {
if (err) {
throw err;
}
console.log('2번', data.toString());
fs.readFile('./readme.txt', (err, data) => {
if (err) {
throw err;
}
console.log('3번', data.toString());
fs.readFile('./readme.txt', (err, data) => {
if (err) {
throw err;
}
console.log('4번', data.toString());
});
});
});
});
// asyncOrderPromise.js
const fs = require('fs').promises;
// fs.readFile('./readme.txt')
// .then((data) => {
// console.log('1번', data.toString());
// return fs.readFile('./readme.txt');
// })
// .then((data) => {
// console.log('2번', data.toString());
// return fs.readFile('./readme.txt');
// })
// .then((data) => {
// console.log('3번', data.toString());
// return fs.readFile('./readme.txt');
// })
// .then((data) => {
// console.log('4번', data.toString());
// return fs.readFile('./readme.txt');
// })
// .catch((error) => console.log(err));
async function main() {
let data = await fs.readFile('./readme.txt');
console.log('1번', data.toString());
data = await fs.readFile('./readme.txt');
console.log('2번', data.toString());
data = await fs.readFile('./readme.txt');
console.log('3번', data.toString());
data = await fs.readFile('./readme.txt');
console.log('4번', data.toString());
}
main();
9. 버퍼와 스트림 이해하기
>> 버퍼 : 일정한 크기로 모아두는 데이터
- 일정한 크기가 되면 한 번에 처리한다.
- 버퍼링 : 버퍼에 데이터가 찰 때까지 모으는 작업이다.
const buffer = Buffer.from('저를 버퍼로 바꿔주세요');
console.log(buffer);
console.log(buffer.length);
console.log(buffer.toString());
// 버퍼가 여러개 쌓이게 되면 하나의 버퍼로 합칠 수 있다.
const array = [Buffer.from('띄엄 '), Buffer.from('띄엄 '), Buffer.from('띄어쓰기 ')];
console.log(Buffer.concat(array).toString());
// Buffer에 아무것도 안들어있는데 5byte짜리 데이터를 널을 수도 있다.
console.log(Buffer.alloc(5));
>> 스트림 : 데이터의 흐름
- 일정한 크기로 나눠서 여러 번에 걸쳐서 처리한다.
- 버퍼(또는 청크)의 크기를 작게 만들어서 주기적으로 데이터를 전달한다.
- 스트리밍 : 일정한 크기의 데이터를 지속적으로 전달하는 작업이다.
// createReadStream.js
const fs = require('fs');
// createReadStream은 최초 64kbyte를 읽어온다.
// highWaterMark 속성을 통해 16byte씩 갖고오도록 변경 처리
// 스트림관리가 메모리 관리에서 가장 효율적이다.
const readStream = fs.createReadStream('./readme3.txt', {highWaterMark: 16});
const data = [];
readStream.on('data', (chunk) => {
data.push(chunk);
console.log('data: ',chunk, chunk.length);
});
// data: <Buffer eb 82 98 eb 8a 94 20 ea b8 b8 ea b2 8c 20 ec a0> 16
// data: <Buffer 81 ec 9c bc eb a0 a4 ea b3 a0 20 ed 95 9c eb 8b> 16
// data: <Buffer a4 2e 0d 0a ec a2 80 eb 8d 94 20 ea b8 b8 ea b2> 16
// data: <Buffer 8c 20 ec a0 81 ec 96 b4 eb b3 b4 ec 9e 90 2e 0d> 16
// data: <Buffer 0a ec a2 80 eb 8d 94 20 ea b8 b8 ea b2 8c 2e 2e> 16
// data: <Buffer ea b8 b8 ea b2 8c 2e 2e 2e 20 ed 95 98 2e 2e 2e> 16
// data: <Buffer 20 ed 9e 98 eb 93 a4 ec 96 b4> 10
readStream.on('end', () => {
console.log('end: ',Buffer.concat(data).toString());
// end: 나는 길게 적으려고 한다.
// 좀더 길게 적어보자.
// 좀더 길게..길게... 하... 힘들어
});
// 비동기처리는 항상 error처리를 해줘야 한다.
readStream.on('error', err => console.log(err));
>> 파일을 스트림으로 만드는 방법
// createWriteStream.js
const fs = require('fs');
const writeStream = fs.createWriteStream('./writeme2.txt');
writeStream.on('finish', () => {
console.log('작성완료');
});
writeStream.write('이글을쓴다.\n');
writeStream.write('이글을쓴다.... 끝...');
writeStream.end();
>> 커다란 파일을 만들어서 버퍼와 스트림을 사용하여 메모리 사용량을 비교한다.
- 하단의 결과를 확인해보면 스트림이 메모리 사용에 가장 효율적인 것을 확인할 수 있다.
// createBicFile.js
const fs = require('fs');
const file = fs.createWriteStream('./bic.txt');
for(let i = 0; i <= 10_000_000; i++) {
file.write('안녕하세요. 데이터 용량이 큰 파일을 만들면서 메모리 체크를 해볼 예정입니다.');
}
file.end();
// buffer-memory.js
const fs = require('fs');
console.log('before', process.memoryUsage().rss);
const data1 = fs.readFileSync('./bic.txt');
fs.writeFileSync('./big2.txt', data1);
console.log('buffer: ',process.memoryUsage().rss);
// 메모리 사용량
// before : 26185728
// buffer : 1097293824
// stream-memory.js
const fs = require('fs');
console.log('before', process.memoryUsage().rss);
const readStream = fs.createReadStream('./bic.txt');
const writeStream = fs.createWriteStream('./bic3.txt');
readStream.pipe(writeStream);
readStream.on('end', () => {
console.log('stream : ', process.memoryUsage().rss);
})
// 메모리 사용량
// before : 26181632
// stream : 46559232
>> 파이프 : 파이프를 지원하는(스트림을 지원하는) 애들끼리만 가능하다.
const fs = require('fs');
const zlib = require('zlib');
// readme3.txt를 16byte씩 읽어 writeme3.txt에 16byte씩 저장한다.
// 스트림을 하면 압축도 할 수 있고 다양한 스트림을 조합할 수 있다.
const readStream = fs.createReadStream('./readme3.txt',{highWaterMark:16});
const zlibStream = zlib.createGzip();
const writeStream = fs.createWriteStream('./writeme3.txt')
readStream.pipe(zlibStream).pipe(writeStream);
18. 기타 fs 메서드
>> 파일 및 폴더 생성
// fsCreate.js
const fs = require('fs').promises;
const constants = require('fs').constants;
// F_OK : 파일 존재여부, W_OK : 쓰기 권한여부, R_OK : 읽기 권한 여부
fs.access('./folder', constants.F_OK | constants.W_OK | constants.R_OK)
.then(() => {
return Promise.reject('이미 폴더가 존재한다.');
})
.catch((err) => {
if(err.code === 'ENOENT') {
console.log('폴더가 없다.');
return fs.mkdir('./folder');
}
return Promise.reject(err);
})
.then(() => {
console.log('폴더 만들기 성공');
return fs.open('./folder/file.js', 'w');
})
.then(() => {
console.log('빈 파일 만들기 성공', fs);
return fs.rename('./folder/file.js', '/folder/newfile.js');
})
.then(() => {
console.log('이름 바꾸기 성공');
});
20. 폴더 내용 확인 및 삭제
// fsDelete.js
const fs = require('fs').promises;
fs.readdir('./folder')
.then((dir) => {
console.log('폴더 내용 확인', dir);
// unlink는 파일을 삭제한다.
return fs.unlink('./folder/newFile.js');
})
.then(() => {
console.log('파일 삭제 성공');
// rmdir은 폴더를 삭제한다.
return fs.rmdir('./folder');
})
.then(() => {
console.log('폴더 삭제 성공');
})
.catch((err) => console.log(err));
21. 파일을 복사하는 방법
- createReadStream과 createWriteStream끼리 연결하여 복사하는 방법 대신 사용할 수 있다.
// copyFile.js
const fs = requrie('fs').promises;
fs.copyFile('readme4.txt', 'writeme4.txt')
.then(() => {
console.log('복사 완료');
})
.catch((err) => console.log(err));
22. 파일을 감시하는 방법
- 파일의 변경사항 발생 시 이벤트가 호출된다. (change : 내용물의 수정, rename : 파일몇 변경 또는 파일 삭제 후)
// watch.js
// 파일을 감시하는 방법(변경사항 발생 시 이벤트가 호출된다.)
const fs = require('fs');
fs.watch('./target.txt',(eventType, fileName) => {
console.log(eventType, fileName);
});
// 내용물 수정시 change target.txt
// 파일명 변경 또는 파일 삭제 후 rename target.txt
23. 스레드풀 알아보기
>> fs, crypto, zlib 모듈의 메서드를 실행할 때는 백그라운드에서 동시에 실행된다.
- 스레드풀이 동시에 처리해 준다.
// threadpool.js
const crypto = require('crypto');
const pass = 'pass';
const salt = 'salt';
const start = Date.now();
// 로그를 확인해보면 기본적으로 동시에 4개씩 실행이 된다.
// 자신의 사양에 맞춰 쓰레드 숫자를 변경할 수도 있다.
// cmd 명령어 : SET(window만) UV_THREADPOOL_SIZE=8
crypto.pbkdf2(pass, salt,1_000_000,128,'sha512',() => {
console.log('1', Date.now() - start);
});
crypto.pbkdf2(pass, salt,1_000_000,128,'sha512',() => {
console.log('2', Date.now() - start);
});
crypto.pbkdf2(pass, salt,1_000_000,128,'sha512',() => {
console.log('3', Date.now() - start);
});
crypto.pbkdf2(pass, salt,1_000_000,128,'sha512',() => {
console.log('4', Date.now() - start);
});
crypto.pbkdf2(pass, salt,1_000_000,128,'sha512',() => {
console.log('5', Date.now() - start);
});
crypto.pbkdf2(pass, salt,1_000_000,128,'sha512',() => {
console.log('6', Date.now() - start);
});
crypto.pbkdf2(pass, salt,1_000_000,128,'sha512',() => {
console.log('7', Date.now() - start);
});
crypto.pbkdf2(pass, salt,1_000_000,128,'sha512',() => {
console.log('8', Date.now() - start);
});
crypto.pbkdf2(pass, salt,1_000_000,128,'sha512',() => {
console.log('9', Date.now() - start);
});
3.7 이벤트 이해하기
2. 커스텀 이벤트 예제
// event.js
const EventEmitter = require('events');
const myEvent = new EventEmitter();
myEvent.addListener('event1', () => {
console.log('이벤트1');
});
myEvent.on('event2', () => {
console.log('이벤트2');
});
myEvent.on('event2', () => {
console.log('이벤트2 추가');
});
myEvent.once('event3',() => {
console.log('이벤트3 : 나는 한번만 실행된다!');
});
myEvent.emit('event1'); // 이벤트1
myEvent.emit('event2'); // 이벤트2
// 이벤트2 추가
myEvent.emit('event3'); // '나는 한번만 실행된다'
myEvent.emit('event3'); //
myEvent.on('event4', () => {
console.log('이벤트 4');
});
myEvent.removeAllListeners('event4'); // 이벤트 삭제(하위 이벤트들도 삭제됨)
myEvent.emit('event4'); //
const listener = () => {
console.log('이벤트 5');
};
myEvent.on('event5', listener);
myEvent.removeListener('event5', listener);
myEvent.emit('event5');
3.8 예외 처리하기
1. 예외 처리
>> 예외(Exception) : 처리하지 못한 에러
- 노드 스레드를 멈춤
- 노드는 기본적으로 싱글 스레드라 스레드가 멈추면 프로세스가 멈춘다.
- 그러므로 에러 처리는 필수이다.
// error1.js
setInterval(() => {
console.log('시작');
try {
throw new Error('서버를 고장내주마!');
} catch (err) {
console.error(err);
}
}, 1000);
- 해당 에러 발생 시 try catch를 처리해주지 않았으면 쓰레드가 멈춰 프로세스가 멈춘다.
- 또한 try ~ catch로 감싸지 않아도 되는 부분들이 존재하는데 promise나 callback에 error값을 갖고 있을 때이다.
- 노드가 기본적으로 제공하는 비동기 메서드들의 callback error는 노드 프로세스를 멈추지 않는다.
// error2.js
// 에러가 발생해도 프로세스가 멈추지 않는 예
const fs = require('fs');
setInterval(() => {
fs.unlink('./abcdefg.js', (err) => {
if(err) {
console.log(err);
}
});
},1000);
>> 프로미스의 에러는 따로 처리하지 않아도 된다.
- 버전이 올라가면 바뀔수 있으니 주의(v21.4.0에서는 프로세스가 죽는 것 같다??)
학습하면서 좀더 확인해보자..
const fs = require('fs').promises;
setInterval(() => {
fs.unlink('./abcdefg.js');
}, 1000);
// node:internal/fs/promises:1059
// return await PromisePrototypeThen(
// ^
// Error: ENOENT: no such file or directory, unlink 'C:\nodejs\abcdefg.js'
// at async Object.unlink (node:internal/fs/promises:1059:10) {
// errno: -4058,
// code: 'ENOENT',
// syscall: 'unlink',
// path: 'C:\\nodejs\\abcdefg.js'
// }
// Node.js v21.4.0
>> 최후의 수단으로 사용
- 콜백 함수의 동작이 보장되지 않는다. 즉 복구 작업용으로 사용하는건 부적합 하다는 의미이다.
- 따라서 에러 내용 기록 용으로만 쓰는 게 좋다.
process.on('uncaughtException', (err) => console.error('예기치 못한 에러',err));
setInterval(() => {
throw new Error('서버를 고장내주마!!');
},1000);
setTimeout(() => {
console.log('실행됩니다.');
},2000);
// 예기치 못한 에러 Error: 서버를 고장내주마!!
// at Timeout._onTimeout (C:\nodejs\error4.js:4:11)
// at listOnTimeout (node:internal/timers:573:17)
// at process.processTimers (node:internal/timers:514:7)
// 실행됩니다.
// 예기치 못한 에러 Error: 서버를 고장내주마!!
// at Timeout._onTimeout (C:\nodejs\error4.js:4:11)
// at listOnTimeout (node:internal/timers:573:17)
// at process.processTimers (node:internal/timers:514:7)
// 반복...
6. 프로세스 종료하기
>> 윈도우
$ netstat -ano | findstr 포트
$ taskkill /pid 프로세스아이디 /f
>> 맥/리눅스
$ lsof -i tcp:포트
$ kill -9 프로세스아이디
'BackEnd > Node' 카테고리의 다른 글
[노드교과서] 섹션5. 익스프레스 웹 서버 만들기 (0) | 2023.12.31 |
---|---|
[노드교과서] 섹션 3. http 모듈로 서버 만들기. 섹션 4. 패키지 매니저 (0) | 2023.12.19 |
[노드교과서] 프런트엔드 자바 스크립트 (0) | 2023.12.13 |
[노드교과서] 섹션 1. 알아두어야 할 자바스크립트 (0) | 2023.12.12 |
[노드교과서] 챕터0. Node.js 설치하기 (0) | 2023.12.01 |