* 위 내용을 정리하였음
3.5 노드 내장 모듈 사용하기
3.5.1 os
- 사용자 컴퓨터의 운영체제 정보를 가져온다.
const os = require('os');
console.log('운영체제 정보---------------------------------');
console.log('os.arch():', os.arch());
console.log('os.platform():', os.platform());
console.log('os.type():', os.type());
console.log('os.uptime():', os.uptime());
console.log('os.hostname():', os.hostname());
console.log('os.release():', os.release());
console.log('경로------------------------------------------');
console.log('os.homedir():', os.homedir());
console.log('os.tmpdir():', os.tmpdir());
console.log('cpu 정보--------------------------------------');
console.log('os.cpus():', os.cpus());
console.log('os.cpus().length:', os.cpus().length);
console.log('메모리 정보-----------------------------------');
console.log('os.freemem():', os.freemem());
console.log('os.totalmem():', os.totalmem());
3.5.2 path
- 폴더와 파일의 경로를 쉽게 조작하도록 도와주는 모듈
- 참고로 절대경로는 루트 폴더 혹은 노드 프로세스가 실행되는 위치를 기준으로의 경로이다.
- 상대경로는 현재 파일을 기준으로 다른 파일의 경로를 말한다. (.)은 현재 파일과 같은 위치를 나타내고 현재 파일 보다 상위 경로면 (..)를 사용한다.
const path = require('path');
const string = __filename;
// 경로의 구분자 /, ₩
console.log('path.sep:', path.sep);
// 환경변수 구분자 ;, :
console.log('path.delimiter:', path.delimiter);
console.log('------------------------------');
// 파일이 위치한 폴더 경로
console.log('path.dirname():', path.dirname(string));
// 파일의 확장자
console.log('path.extname():', path.extname(string));
// 확장자 포함 파일의 이름
console.log('path.basename():', path.basename(string));
// 확장자 뺀 파일의 이름
console.log('path.basename - extname:', path.basename(string, path.extname(string)));
console.log('------------------------------');
// 파일 경로를, root, dir, base, ext, name으로 분리
console.log('path.parse()', path.parse(string));
// path.parse한 객체를 하나의 파일 경로로 합친다.
console.log('path.format():', path.format({
dir: 'C:\\users\\zerocho',
name: 'path',
ext: '.js',
}));
// //를 잘못 여러번 쓰거나 하면 수정해준다.
console.log('path.normalize():', path.normalize('C://users\\\\zerocho\\\path.js'));
console.log('------------------------------');
// 절대경로인지 상대경로 인지 t/f
console.log('path.isAbsolute(C:\\):', path.isAbsolute('C:\\'));
console.log('path.isAbsolute(./home):', path.isAbsolute('./home'));
console.log('------------------------------');
// 첫번째 경로에서 두번째 경로로 가는 방법을 알려줌
console.log('path.relative():', path.relative('C:\\users\\zerocho\\path.js', 'C:\\'));
// 인수들을 합쳐서 하나의 경로를 반환 ('/a', '/b', 'c') -> /a/b/c
console.log('path.join():', path.join(__dirname, '..', '..', '/users', '.', '/zerocho'));
// 인수들을 합쳐서 하나의 경로를 반환 (/가 나오면 절대경로로 처리) ('/a', '/b', 'c') -> /b/c
console.log('path.resolve():', path.resolve(__dirname, '..', 'users', '.', '/zerocho'));
3.5.3 url
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));
- url.parse()는 주소를 분해해서 객체를 만든다.
- url.format()은 분해된 객체를 가지고 주소로 다시 조립한다.
- WHATWG 방식으로 아래 처럼 주소를 분해할 수 있다.
- 주소를 통해 데이터를 전달할 수 있다. https://blah-blah-blah?page=3&category=naver. 이렇게 ?로 시작해서 키, 값 형식으로 데이터를 전달할 수 있다. searchParams 객체를 통해 이 값들을 꺼낼 수 있다.
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.set('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();
3.5.4 querystring
- WHATWG 대신 search 부분을 사용하기 쉽게 객체로 만드는 모듈이다.
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));
- querystring.parse() : url의 query 부분을 자바스크립트 객체로 분해
- querystring.stringify() : 분해된 query 객체를 문자열로 다시 조립
3.5.5 crypto
- 암호화를 도와주는 모듈, 실제 서비스에서도 유용하다.
3.5.5.1 단방향 암호화
- 복호화 할 수 없는 암호화 방식, 비밀번호를 보통 단방향 암호화 한다.
- 다시 복호화 하지 않기 때문에 해시 함수라고 부르기도 한다.
- 해시 기법이란, 예로 abcedge, iosiegeosldd 문자열이 입력으로 들어오면 고정된 크기의 문자열로 바꾸는 것을 말한다. 예를들면 esge로 만들거나 odiv로 만드는 것이다.
- 고객의 비밀번호를 암호화해서 DB에 저장하고 고객이 비밀번호를 입력 했을때 암호화 한 결과가 동일한지 확인하면 된다.
- 즉 원본 비밀번호는 어디에도 저장되지 않는다.
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'));
- createHash에는 사용할 hash알고리즘을 넣는다.
- update() : 비밀번호라는 문자열을 암호화 할 것이다.
- digest : 인코딩할 알고리즘을 넣는다 base64가 결과 문자열이 제일 짧기 때문에 애용한다.
- noevidd와 saegea가 동일한 nweg로 변환되면 충돌이 일어났다고 본다. 해킹 컴퓨터는 이를 찾는다.
- 기존 해시 알고리즘들에서 취약점이 발견되면 더 강력한 알고리즘으로 갈아 타야 한다.
- 현재는 주로 pbkdf2, bcrypt, scrypt라는 알고리즘으로 비밀번호를 암호화 한다.
- pbkdf2는 기존 문자열에 salt라고 불리는 문자열을 붙인 후 해시 알고리즘을 반복해서 적용한다.
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'));
});
});
- randomBytes로 64바이트 길이의 문자열을 만든다.
- 이 문자열이 salt이고 이를 base64알고리즘으로 인코딩한다.
- randomBytes의 결과는 매번 달라지기 때문에 salt를 잘 보관하고 있어야 비밀번호를 찾을 수 있다.
- pbkdf2에 차례로 비밀번호, salt, 반복횟수, 출력바이트, 해시알고리즘을 넣어준다.
- 즉 sha512의 결과를 반복해서 다시 sha512에 넣는 것이다.
- crypto.randomBytes와 crypto.pbkdf2는 내부적으로 스레드풀을 사용해 멀티 스레딩으로 동작한다. 또한 작업시간이 좀 걸릴 수 있기 때문에 횟수를 잘 조절해야 한다.
- pbkdf2도 취약점이 발견됐기 때문에 bcrypt, scrypt를 권장한다.
3.5.5.2 양방향 암호화
- 암호화 할때 사용한 키를 가지고 암호 문자열에서 원본을 복호화할 수 있다.
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);
3.5.6 util
- 각종 편의 기능들을 모아둔 모듈
- deprecated되어 사라지는 경우도 있다.( 더 이상 사용되지 않고 앞으로 사라지게 될 기능 )
const util = require('util');
const crypto = require('crypto');
const dontUseMe = util.deprecate((x, y) => {
console.log(x + y);
}, 'dontUseMe 함수는 deprecated되었으니 더 이상 사용하지 마세요!');
dontUseMe(1, 2);
const randomBytesPromise = util.promisify(crypto.randomBytes);
randomBytesPromise(64)
.then((buf) => {
console.log(buf.toString('base64'));
})
.catch((error) => {
console.error(error);
});
- deprecate의 인자로 함수를 넣어서 dontUseMe라는 함수를 만들었다. 이 함수를 사용하면 사용시 deprecationWarning을 출력한다.
- promisify를 사용하면 콜백을 프로미스로 바꿔서 사용할 수 있다. 이렇게 바꿔두면 async, await 패턴도 사용할 수 있다.
3.5.7 worker_threads
- 노드에서 멀티 스레드 방식으로 작업하는 방법을 알아보자.
const {
Worker, isMainThread, parentPort,
} = require('worker_threads');
if (isMainThread) { // 부모일 때
const worker = new Worker(__filename);
worker.on('message', message => console.log('from worker', message));
worker.on('exit', () => console.log('worker exit'));
worker.postMessage('ping');
} else { // 워커일 때
parentPort.on('message', (value) => {
console.log('from parent', value);
parentPort.postMessage('pong');
parentPort.close();
});
}
- 만약 현재 코드가 메인스레드에서 실행되면 작업자를 만든다. 작업자로 부터 메세지가 들어오면, 작업자가 부모와 연결을 끊으면 작업을 on으로 정의했다.
- 부모 스레드는 postmessage를 통해 워커에게 ping이라는 메시지를 전달한다.
- 현재 코드가 워커에서 실행되면 부모 스레드로부터 메세지를 받았을때 수행할 작업을 on으로 정의한다.
- postmessage로 부모 스레드에게 pong을 보낸다.
- 워커에서 on을 사용할때는 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', message => console.log('from worker', message));
worker.on('exit', () => {
threads.delete(worker);
if (threads.size === 0) {
console.log('job done');
}
});
}
} else { // 워커일 때
const data = workerData;
parentPort.postMessage(data.start + 100);
}
- workerData를 가지고 부모 스레드로부터 자식이 데이터를 받을 수 있다.
- 멀티 스레딩으로 1부터 1000만 까지의 소수의 개수를 찾아보자.
const { Worker, isMainThread, parentPort, workerData } = require('worker_threads');
const min = 2;
let primes = [];
function findPrimes(start, range) {
let isPrime = true;
const end = start + range;
for (let i = start; i < end; i++) {
for (let j = min; j < Math.sqrt(end); j++) {
if (i !== j && i % j === 0) {
isPrime = false;
break;
}
}
if (isPrime) {
primes.push(i);
}
isPrime = true;
}
}
if (isMainThread) {
const max = 10000000;
const threadCount = 8;
const threads = new Set();
const range = Math.ceil((max - min) / threadCount);
let start = min;
console.time('prime');
for (let i = 0; i < threadCount - 1; i++) {
const wStart = start;
threads.add(new Worker(__filename, { workerData: { start: wStart, range } }));
start += range;
}
threads.add(new Worker(__filename, { workerData: { start, range: range + ((max - min + 1) % threadCount) } }));
for (let worker of threads) {
worker.on('error', (err) => {
throw err;
});
worker.on('exit', () => {
threads.delete(worker);
if (threads.size === 0) {
console.timeEnd('prime');
console.log(primes.length);
}
});
worker.on('message', (msg) => {
primes = primes.concat(msg);
});
}
} else {
findPrimes(workerData.start, workerData.range);
parentPort.postMessage(primes);
}
- 총 8개의 쓰레드에게 2부터 10000 사이의 범위를 각각 나누어 주었다. 각각 범위 안에서 소수의 수를 구하도록 한다.
- 쓰레드 사이의 통신과 스레드의 생성은 비용이 많이 들기 때문에 자칫 싱글 스레딩보다 더 느려질 수 있으니 주의 한다.
3.5.8 child_process
- 노드에서 다른 프로그램을 실행하고 싶거나 명령어를 수행하고 싶을 때 사용하는 모듈이다.
- 이 모듈을 통해 다른 언어의 코드를 실행하여 결과값을 받을수도 있다.
- 현재 노드의 프로세스 이외의 child process를 띄워서 명령을 수행하여 결과를 받는다.
const exec = require('child_process').exec;
var process = exec('dir');
process.stdout.on('data', function(data) {
console.log(data.toString());
}); // 실행 결과
process.stderr.on('data', function(data) {
console.error(data.toString());
}); // 실행 에러
- 명령 프롬프트 명령어 dir(혹은 ls)를 노드에서 실행해보자. exec를 사용한다.
- exec는 셸을 실행해서 명령어를 수행한다.
- 성공하면 현재 폴더의 파일 목록이 나올 것이다.
// test.py
print('hello python')
const spawn = require('child_process').spawn;
var process = spawn('python', ['test.py']);
process.stdout.on('data', function(data) {
console.log(data.toString());
}); // 실행 결과
process.stderr.on('data', function(data) {
console.error(data.toString());
}); // 실행 에러
- hello python을 출력하는 코드를 노드에서 실행해보자. spawn을 사용한다.
- spawn은 새로운 프로세스를 띄우면서 명령어를 실행한다.
- spawn에서도 세번째 인수로 {shell:true}를 제공하면 exec처럼 셸을 실행해서 명령어를 수행한다.
'ComputerScience > NodeJs' 카테고리의 다른 글
node - 3.7 이벤트 이해하기 ~ 3.8 예외처리 (0) | 2022.01.09 |
---|---|
node - 3.6 파일 시스템 접근하기 (0) | 2022.01.07 |
node - 3.1 노드 기능 알아보기~ 3.4 노드 내장 객체 알아보기 (0) | 2021.12.30 |
node - 2. 알아야 하는 javascript (0) | 2021.12.29 |
node - 1. 노드 시작하기 (0) | 2021.12.29 |