본문 바로가기

ComputerScience/NodeJs

node - 3.5 노드 내장 모듈 사용하기

728x90
 

더북(TheBook): Node.js 교과서 개정 2판

 

thebook.io

* 위 내용을 정리하였음

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처럼 셸을 실행해서 명령어를 수행한다.

728x90
반응형