티스토리 뷰

책/nodejs 교과서

3. 노드 기능 알아보기

안양사람 2021. 2. 19. 01:16
728x90
SMALL

1. REPL 사용하기

자바스크립트는 스크립트 언어이므로 미리 컴파일을 하지 않아도 즉석에서 코드를 실행할 수 있다. 브라우저 콘솔 탭에서도 가능하고 노드에서도 가능하다. 노드에서는 입력한 코드를 읽고(READ), 해석하고(EVAL), 결과물을 반환하고(PRINT), 종료할 때까지 반복한다(LOOP)고 해서 REPL(Read Eval Print Loop)이라고 부른다.

cmd에서 node를 입력하면 실행된다.

2. js 파일 실행하기

REPL에 긴 코드를 입력하는 것은 불편하기 때문에 파일로 실행한다. node ex.js(js는 생략가능)

3. 모듈로 만들기

모듈 : 특정한 기능을 하는 함수나 변수들의 집합

모듈 만들기

const odd = '홀수입니다';
const even = '짝수입니다';

module.exports = {
  odd,
  even,
};

 

 

모듈 사용

const { odd, even } = require('./var');
const checkNumber = require('./func');

function checkStringOddOrEven(str) {
  if (str.length % 2) { // 홀수면
    return odd;
  }
  return even;
}

console.log(checkNumber(10));
console.log(checkStringOddOrEven('hello'));

 

ES2015 모듈은 나중에~~

 

4. 노드 내장 객체 알아보기

1. global

브라우저의 window와 같은 전역 객체

window처럼 global도 생략 가능

내부를 보려면 REPL로 global입력

전역 객체라는 점을 이용하여 파일 간에 간단한 데이터를 공유할 때 사용하기도 한다. 하지만 남용하면 유지보수가 어려워지니 모듈을 사용해

 

2. console

console 객체는 보통 디버깅을 위해 사용한다.

console.time(레이블) : console.timeEnd(레이블)과 대응되어 같은 레이블을 가지 time과 timeEnd 사이의 시간을 측정

console.log(내용) : 평범한 로그를 콘솔에 표시

console..error(에러 내용) : 에러를 콘솔에 표시합니다.

console.table(배열) : 배열의 요소로 객체 리터럴을 넣으면, 객체의 속성들이 테이블 형식으로 표현

console.dir(객체, 옵션) : 객체를 콘솔에 표시할 때 사용. 첫 번째 인수로 표시할 객체를 넣고, 두 번째 인수로 옵션을 넣는다. 옵션의 colors를 true로 하면 콘솔에 색이 추가되어 보기 편해진다. depth는 객체 안의 객체를 ㄹ몇 단계까지 보여줄지를 결정한다. 기본값은 2

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: 'hero', birth: 1988}]);

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. 타이머

setTimeout(콜백 함수, 밀리초) : 주어진 밀리초 이후에 콜백 함수를 실행

setInterval(콜백 함수, 밀리초) : 주어진 밀리초마다 콜백 함수를 반복 실행

setImmediate(콜백 함수) : 콜백 함수를 즉시 실행

이 타이머 함수들은 모두 아이디를 반환한다. 아이디를 사용하여 타이머를 취소할 수 있다.

clearTimeout(아이디)

clearInterval(아이디)

clearImmediate(아이디)

const timeout = setTimeout(() => {
  console.log('1.5초 후 실행');
}, 1500);

const interval = setInterval(() => {
  console.log('1초마다 실행');
}, 1000);

const timeout2 = setTimeout(() => {
  console.log('실행되지 않습니다');
}, 3000);

setTimeout(() => {
  clearTimeout(timeout2);
  clearInterval(interval);
}, 2500);

const immediate = setImmediate(() => {
  console.log('즉시 실행');
});

const immediate2 = setImmediate(() => {
  console.log('실행되지 않습니다');
});

clearImmediate(immediate2);

 

4. __filename, __dirname

__filename : 실행 시 현재 파일명

__dirname : 실행 시 현재 파일 경로

5. module, exports, require

이전에는 모듈을 만들 때 module.exports를 사용했는데, exports 객체로도 모듈을 만들 수 있다.

console.log(module.exports===exports) true야

exports.odd = '홀수입니다';
exports.even = '짝수입니다';

 

모듈을 불러오는 require. require는 함수이고 함수는 객체이므로 require는 객체로서 몇 가지 속성을 갖고 있다.

require.cache

속성값으로는 각 파일의 모듈 객체가 들어 있다. 한 번 require한 파일은 require.cache에 저장되므로 다음 번에 require할 때는 새로 불러오지 않고 require.cache에 있는 것이 재사용된다.

속성을 자세히 살펴보면 module.exports했던 부분이나 로딩여부, 부모, 자식 모듈 관계를 찾을 수 있다.

require.main

현재 파일이 첫 모듈인지 알아보려면 require.main===module을 해보면 된다. 첫모듈이라면 true 아니면 falsea

 

모듈을 사용할 때 주의해야 할 점

만약 두 모듈이 있고 이 둘이 서로를 require한다면??

dep1의 module.exports가 함수가 아니라 빈 객체로 표시

이러한 현상을 순환 참조(circular dependency)라고 부른다. 이렇게 순환 참조가 있을 경우에는 순환 참조되는 대상을 빈 객체로 만든다.

6. process

process 객체는 현재 실행되고 있는 노드 프로세스에 대한 정보를 담고 있다.

> process.version
'v14.15.4' // 노드 버전
> process.arch
'x64' // 프로세스 아키텍처 정보
> process.platform
'win32' // 운영체제 플렛폼
> process.pid
혹시몰라 안씀 // 현재 프로세스의 아이디, 프로세스를 여러 개 가질 때 구분할 수 있다.
> process.uptime()
30.2452056 // 프로세스가 시작된 후 흐른 시간. 단위는 초
> process.execPath
'C:\\Program Files\\nodejs\\node.exe' // 노드의 경로
> process.cwd()
'C:\\Users\\User\\Desktop\\nodejs-book-master\\ch3\\3.4' // 현재 플세스가 실행되는 위치
> process.cpuUsage()
{ user: 859000, system: 656000 } // 현재 cpu 사용량

 

process.env : 시스템 환경 변수

시스템 환경 변수 외에도 임의로 환경 변수를 저장할 수 있다. process.env는 서비스의 중요한 키를 저장하는 공간으로도 사용된다. 서버나 데이터베이스의 비밀번호와 각종 API키를 코드에 직접 입력하는 것은 위험하다. 따라서 중요한 비밀번호는 cosnt secretId=process.env.SECRET_ID; 와 같이 대체

넣는 방법은 운영체제마다 차이. 6절에서 dotenv 사용하면 동일하게 넣는 방법 있어

 

process.nextTick(콜백)

이벤트 루프가 다른 콜백 함수들보다 nextTick의 콜백 함수를 우선으로 처리하도록 만든다.

맨 밑 promise를 넣은 것은 resolve된 Promise도 nextTick처럼 다른 콜백들보다 우선시되기 때문이다.

그래서 process.nextTick과 Promise를 마이크로태스크라고 따로 구분지어 부른다.

setImmediate(() => {
  console.log('immediate');
});
process.nextTick(() => {
  console.log('nextTick');
});
setTimeout(() => {
  console.log('timeout');
}, 0);
Promise.resolve().then(() => console.log('promise'));

nextTick
promise
timeout
immediate

 

process.exit(코드)

실행중인 노드 프로세스를 종료한다. 서버 환경에서는 잘 사용하지 않는다. 하지만 서버 외의 독립적인 프로그램에서는 수동으로 노드를 멈추기 위해 사용한다.

인수로 코드 번호를 줄 수 있는데 주지 않거나 0을 주면 정상 종료, 1을 주면 비정상 종료다.

 

5. 노드 내장 모듈 사용하기

os

웹 브라우저에서 사용되는 자바스크립트는 운영체제의 정보를 가져올 수 없지만, 노드는 os 모듈에 정보가 담겨 있어 정보를 가져올 수 있다.

process 객체와 겹치는 부분도 있다.

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());

운영체제 정보---------------------------------
os.arch(): x64
os.platform(): win32
os.type(): Windows_NT
os.uptime(): 365706
os.hostname(): DESKTOP-VOH9EIA
os.release(): 10.0.19041
경로------------------------------------------
os.homedir(): C:\Users\User
os.tmpdir(): C:\Users\User\AppData\Local\Temp
cpu 정보--------------------------------------
os.cpus(): [
  {
    model: 'Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz',
    speed: 2208,
    times: {
      user: 6816765,
      nice: 0,
      sys: 14484265,
      idle: 102312984,
      irq: 1458750
    }
  },~~~~
]
os.cpus().length: 12
메모리 정보-----------------------------------
os.freemem(): 9783590912
os.totalmem(): 17031770112

 

os.arch() : process.arch와 동일

os.platform() : process.platform과 동일

os.type() : 운영체제의 종류

os.uptime() : 운영체제 부팅 이후 흐른 시간(초) (process.uptime()은 노드의 실행 시간)

os.hostname() : 컴퓨터의 이름

os.release() : 운영체제의 버전

os.homedir() : 홈 디렉터리 경로

os.tmpdir() : 임시 파일 저장 경로

os.cpus() : 컴퓨터의 코어 정보

os.freemem() : 사용 가능한 메모리(RMA)

os.totalmen() : 전체 메모리 용량

 

path

폴더와 파일의 경로를 쉽게 조작하도록 도와주는 모듈. 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('------------------------------');
console.log('path.parse()', path.parse(string));
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('------------------------------');
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:\\'));
console.log('path.join():', path.join(__dirname, '..', '..', '/users', '.', '/zerocho'));
console.log('path.resolve():', path.resolve(__dirname, '..', 'users', '.', '/zerocho'));


path.sep: \
path.delimiter: ;
------------------------------
path.dirname(): C:\Users\User\Desktop\nodejs-book-master\ch3\3.5
path.extname(): .js
path.basename(): path.js
path.basename - extname: path
------------------------------
path.parse() {
  root: 'C:\\',
  dir: 'C:\\Users\\User\\Desktop\\nodejs-book-master\\ch3\\3.5',
  base: 'path.js',
  ext: '.js',
  name: 'path'
}
path.format(): C:\users\zerocho\path.js
path.normalize(): C:\users\zerocho\path.js

 

path.sep : 경로의 구분자. 윈도는 /, POSIX는 \

path.delimiter : 환경 변수의 구분자. process.env.PATH를 입력하면 여러 개의 경로가 이 구분자로 구분되어 있다. 윈도는 세미콜론(;)이고 POSIX는 콜론(:)이다.

path.dirname(경로) : 파일이 위치한 폴더 경로를 보여준다.

path.extname(경로) : 파일의 확장자를 보여준다.

path.basename(경로, 확장자) : 파일의 이름(확장자 포함)을 표시한다. 파일의 이름만 표시하고 싶다면 basename의 두번째 인수로 파일의 확장자를 넣으면 된다.

path.parse(경로) : 파일 경로를 root, dir, bae, ext, name으로 분리한다.

path.format(객체) : path.parse()한 객체를 파일 경로로 합칩니다.

path.normalize(경로) : /나 \를 실수로 여러 번 사용했거나 혼용했을 때 정상적인 경로로 변환

path.isAbsolute(경로) : 파일의 경로가 절대경로인지 상대경로인지를 true나 false로 알린다.

path.relative(기준경로, 비교경로) : 경로를 두 개 넣으면 첫 번째 경로에서 두 번째 경로로 가는 방법을 알린다.

path.join(경로, ....) : 여러 인수를 넣으면 하나의 경로로 합친다. 상대경로인 ..(부모 디렉터리)과 .(현 위치)도 알아서 처리한다.

path.resolve(경로, ...) : path.join()과 비슷하지만 차이가 있다.

 

note

/를 만나면 path.resolve는 절대경로로 인식해서 앞의 경로를 무시하고, path.join은 상대경로로 처리한다.

path.join('/a', '/b', 'c);   /a/b/c/

path.resolve('/a', '/b', 'c');   /b/c

 

note

어떤 때 \\를 사용하고 어떤 때 \를 사용하나요?

기본적으로 경로는 \하나를 사용해서 표시한다. 하지만 자스 문자열에서는 \가 특수 문자이므로 두 개로 경로를 표시.

path 모듈은 위와 같은 경우에 발생하는 문제를 알아서 처리

 

note

상대경로와 절대경로

알잖아

 

가끔 윈도에서 POSIX 스타일 경로를 사용할 때가 있고 그 반대일 때도 있다. 이러한 경우 윈도에서는 path.posix.sep이나 path.posix.joini()과 같이 사용하면 되고, POSIX에서는 path.win32.sep이나 paath.win32.join()과 같이 사용하면 된다.

 

노드는 require.main 파일을 기준으로 상대 경로를 인식한다. 다른 디렉터리의 파일이 상대 경로를 갖고 있다면 예상과 다르게 동작할수도... path 모듈로 해결가능. fs모듈볼때 다시 짚고 넘어갈게

 

url

인터넷 주소를 쉽게 조작하도록 도와주는 모듈이다. url 처리에는 크게 두 가지 방식이 있다. 

1. WHATWC 방식의 url과 예전부터 노드에서 사용하던 방식의 url

가운데를 기준으로 위쪽은 기존 노드의 url 구분 방법, 아래쪽은 WHATWC의 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));


ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
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 모듈 안에 URL 생성자가 있다. 이 생성자에 주소를 넣어 객체로 만들면 주소가 부분별로 정리된다. 이 방식이 WHATWG의 url이다. WHATWG에만 있는 username, password, origin, searchParams 속성이 존재한다.

 

기존 노드 방식에서는 두 메서드를 주로 사용한다.

url.parse(주소) : 주소를 분해한다. 

url.format(객체) : WHATWG 방식 url과 기존 노드의 url을 모두 사용할 수 있다. 분해되었던 url 객체를 다시 원래 상태로 조립한다.

 

url 사용은 취향이지만, 노드의 url 형식을 꼭 사용해야 하는 경우가 있다. host 부분 없이 pathname 부분만 오는 주소인 경우에는 WHATWG 방식이 처리할 수 없다.

WHATWG 방식은 search 부분을 searchParams라는 특수한 객체로 반환하므로 유용하다.

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();

ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
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' ]
[ 'es6' ]
[]
searchParams.toString(): page=3&limit=10&category=nodejs&category=javascript

searchParams에는 다양한 메서드가 있다. (formData 객체 메서드와 비슷)

getAll(키) : 키에 해당하는 모든 값들을 가져온다.

get(키) : 키에 해당하는 첫번째 값만 가져온다.

has(키) : 해당 키가 있는지 없는지를 검사한다.

keys() : searchParams의 모든 키를 반복기 객체로 가져옵니다.

values() : searchParams의 모든 값을 반복기 객체로 가져옵니다.

append(키, 값) : 해당 키를 추가한다.

set(키, 값) : append와 비슷하지만, 같은 키의 값들을 모두 지우고 새로 추가한다.

delete(키) : 해당 키를 제거한다.

toString() : 조작한 searchParams 객체를 다시 문자열로 만든다. 이 문자열을 search에 대입하면 주소 객체에 반영된다.

 

queryStrirng

query같은 문자열보다 searchParams가 유용한 이유는 query의 경우 다음에 배우는 queryString 모듈을 한번 더 사용해야 하기 때문

WHATWG 방식의 url 대신 기존 노드의 url을 사용할 때, 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(): [Object: null prototype] {
  page: '3',
  limit: '10',
  category: [ 'nodejs', 'javascript' ]
}
querystring.stringify(): page=3&limit=10&category=nodejs&category=javascript

crypto

다양한 방식의 암호화를 도와주는 모듈이다. 고객의 비밀번호는 반드시!!! 암호화

1. 단방향 암호화

비밀번호는 보통 단방향 암호화 알고리즘을 사용해서 암호화. 단방향 암호화란 복호화할 수 없는 암호화 방식을 뜻함

즉, 단방향 암호화는 한 번 암호화하면 원래 문자열을 찾을 수 없다. 해시 함수라고도 불린다.

암호화 알고리즘은 주로 해시 기법을 사용. 해시 기법이란 어떠한 문자열을 고정된 길이의 다른 문자열로 바꿔버리는 방식이다. 

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'));

base64: dvfV6nyLRRt3NxKSlTHOkkEGgqW2HRtfu19Ou/psUXvwlebbXCboxIPmDYOFRIpqav2eUTBFuHaZri5x+usy1g==
hex: 76f7d5ea7c8b451b773712929531ce92410682a5b61d1b5fbb5f4ebbfa6c517bf095e6db5c26e8c483e60d8385448a6a6afd9e513045b87699ae2e71faeb32d6
base64: cx49cjC8ctKtMzwJGBY853itZeb6qxzXGvuUJkbWTGn5VXAFbAwXGEOxU2Qksoj+aM2GWPhc1O7mmkyohXMsQw==

createHash(알고리즘) : 사용할 해시 알고리즘을 넣는다. md5, sha1, sha256, sha512 등이 가능. 현재는 sha512면 충분

update(문자열) : 변환할 문자열을 넣는다.

digest(인코딩) : 인코딩할 알고리즘을 넣는다. base64,hex,latin1이 주로 사용. base64가 결과 문자열이 짧아 애용.

 

해킹 발달. 하지만 해시 알고리즘도 발달.

현재는 주로 pbkdf2나 bcrypt, scrypt라는 알고리즘으로 비밀번호를 암호화

 

아래는 pbkdf2(노드에서 지원) (bcrypt, scrypt보다 취약)

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'));
  });
});

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);

util

각종 편의 기능을 모아둔 모듈. 계속해서 API 추가되고 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);
  });

 

util.deprecate : 함수가 deprecated 처리되었음을 알림. 첫 번째 인수로 넣은 함수를 사용했을 때 경고 메시지 출력, 두 번째 인수로 경고 메시지 내용을 넣으면 된다.

util.promisify : 콜백 패턴을 프로미스 패턴으로 바꾼다.

worker_threads

노드에서 멀티 스레드 방식으로 작업하는 방법.

간단 ex)

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();
  });
}

-------------------------------------------
from parent ping
from worker pong
worker exit

 

여러개의 워커 스레드에 데이터를 넘겨보자 ex)

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);
}

-------------------------------------
from worker 101
from worker 102
job done

 

소수찾기 어려움.. ex)

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);
}

child_process

노드에서 다른 프로그램을 실행하고 싶거나 명령어를 수행하고 싶을 때 사용하는 모듈. 이 모듈을 통해 다른 언어의 코드 실행하고 결과값을 받을 수 있다.

ex

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());
}); // 실행 에러

ex 파이썬 실행

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());
}); // 실행 에러

 

exec는 셀을 실행해서 명령어를 수핼ㅇ

spawn은 새로운 프로세스를 띄우면서 명령어를 실행

 

기타 모듈들

assert, dns, net, string_decoder, tls, tty, dgram, v8, vm

6. 파일 시스템 접근하기

fs 모듈은 파일 시스템에 접근하는 모듈이다.

const fs = require('fs');

fs.readFile('./readme.txt', (err, data) => {
  if (err) {
    throw err;
  }
  console.log(data);
  console.log(data.toString());
});


<Buffer ec a0 80 eb a5 bc 20 ec 9d bd ec 96 b4 ec a3 bc ec 84 b8 ec 9a 94 2e>
저를 읽어주세요.

 

fs는 콜백형식이므로 프로미스로 바꿔

const fs = require('fs');

fs.writeFile('./writeme.txt', '글이 입력됩니다', (err) => {
  if (err) {
    throw err;
  }
  fs.readFile('./writeme.txt', (err, data) => {
    if (err) {
      throw err;
    }
    console.log(data.toString());
  });
});

 

동기 메서드와 비동기 메서드

note 동기와 비동기, 블로킹과 논 블로킹

동기와 비동기 : 백그라운드 작업 완료 확인 여부

블로킹과 논 블로킹 : 함수가 바로 return 되는지 여부

노드에서는 대부분 동기-블로킹 방식과 비동기-논 블로킹 방식이다.

 

순서대로 출력하고 싶다면 fs.readFileSync(~~) 처럼 뒤에 Sync를 붙이면 된다.

당연히 특별한 경우를 제외하고는 사용하면 안돼. 느리니까...

순서 유지하고 싶으면 콜백 사용. 콜백 지옥은 Promise나 async/await으로 어느정도 해결

const fs = require('fs').promises;

console.log('시작');
fs.readFile('./readme2.txt')
  .then((data) => {
    console.log('1번', data.toString());
    return fs.readFile('./readme2.txt');
  })
  .then((data) => {
    console.log('2번', data.toString());
    return fs.readFile('./readme2.txt');
  })
  .then((data) => {
    console.log('3번', data.toString());
    console.log('끝');
  })
  .catch((err) => {
    console.error(err);
  });

 

버퍼와 스트림 이해하기

파일을 읽거나 쓰는 방식에는 크게 두가지 방식이 있다. 버퍼, 스트림

  버퍼링 : 영상을 재생할 수 있을 때까지 데이터를 모으는 동작

  스트리밍 : 방송인의 컴퓨터에서 시청자의 컴퓨터로 영상 데이터를 조금씩 전송하는 동작

노드의 버퍼와 스트림도 비슷한 개념

앞에서 읽었던 파일이 버퍼 형식으로 출력되었다. 노드는 파일을 읽을 때 메모리에 파일 크기만큼 공간을 마련해두며 파일 데이터를 메모리에 저장한 뒤 사용자가 조작할 수 있도록 한다. 이때 메모리에 저장된 데이터가 버퍼

 

버퍼를 직접 다뤄보자. Buffer 클래스

const buffer = Buffer.from('저를 버퍼로 바꿔보세요');
console.log('from():', buffer);
console.log('length:', buffer.length);
console.log('toString():', buffer.toString());

const array = [Buffer.from('띄엄 '), Buffer.from('띄엄 '), Buffer.from('띄어쓰기')];
const buffer2 = Buffer.concat(array);
console.log('concat():', buffer2.toString());

const buffer3 = Buffer.alloc(5); // 빈 버퍼를 생성, 바이트를 인수로 넣으면 해당 크기의 버퍼가 생성
console.log('alloc():', buffer3);

--------------------------------------
from(): <Buffer ec a0 80 eb a5 bc 20 eb b2 84 ed 8d bc eb a1 9c 20 eb b0 94 ea bf 94 eb b3 b4 ec 84 b8 ec 9a 94>
length: 32
toString(): 저를 버퍼로 바꿔보세요
concat(): 띄엄 띄엄 띄어쓰기
alloc(): <Buffer 00 00 00 00 00>

 

용량이 커지면 메모리 문제가 발생해 => 버퍼의 크기를 작게 만든 후 여러 번으로 나눠 보내는 방식

=> 이를 편하게 만든 것이 스트림

ex) read

const fs = require('fs');

const readStream = fs.createReadStream('./readme3.txt', { highWaterMark: 16 });
const data = [];

readStream.on('data', (chunk) => {
  data.push(chunk);
  console.log('data :', chunk, chunk.length);
});

readStream.on('end', () => {
  console.log('end :', Buffer.concat(data).toString());
});

readStream.on('error', (err) => {
  console.log('error :', err);
});

--------------------------------------------------------------------
data : <Buffer ec a0 80 eb 8a 94 20 ec a1 b0 ea b8 88 ec 94 a9> 16
data : <Buffer 20 ec a1 b0 ea b8 88 ec 94 a9 20 eb 82 98 eb 88> 16
data : <Buffer a0 ec 84 9c 20 ec a0 84 eb 8b ac eb 90 a9 eb 8b> 16
data : <Buffer 88 eb 8b a4 2e 20 eb 82 98 eb 88 a0 ec a7 84 20> 16
data : <Buffer ec a1 b0 ea b0 81 ec 9d 84 20 63 68 75 6e 6b eb> 16
data : <Buffer 9d bc ea b3 a0 20 eb b6 80 eb a6 85 eb 8b 88 eb> 16
data : <Buffer 8b a4 2e> 3
end : 저는 조금씩 조금씩 나눠서 전달됩니다. 나눠진 조각을 chunk라고 부릅니다.

 

ex) write

const fs = require('fs');

const writeStream = fs.createWriteStream('./writeme2.txt');
writeStream.on('finish', () => {
  console.log('파일 쓰기 완료');
});

writeStream.write('이 글을 씁니다.\n');
writeStream.write('한 번 더 씁니다.');
writeStream.end();

ex) createReadStream으로 파일을 읽고 그 스트림을 전달받아 createWriteStream으로 파일을 쓸 수도 있다. pipe

const fs = require('fs');

const readStream = fs.createReadStream('readme4.txt');
const writeStream = fs.createWriteStream('writeme3.txt');
readStream.pipe(writeStream);

 

ex) 파일 압축

const zlib = require('zlib');
const fs = require('fs');

const readStream = fs.createReadStream('./readme4.txt');
const zlibStream = zlib.createGzip();
const writeStream = fs.createWriteStream('./readme4.txt.gz');
readStream.pipe(zlibStream).pipe(writeStream);

 

기타 fs 메서드 알아보기

ex) 파일 생성, 삭제, 폴더 생성, 삭제

const fs = require('fs');

fs.access('./folder', fs.constants.F_OK | fs.constants.R_OK | fs.constants.W_OK, (err) => {
  if (err) {
    if (err.code === 'ENOENT') {
      console.log('폴더 없음');
      fs.mkdir('./folder', (err) => {
        if (err) {
          throw err;
        }
        console.log('폴더 만들기 성공');
        fs.open('./folder/file.js', 'w', (err, fd) => {
          if (err) {
            throw err;
          }
          console.log('빈 파일 만들기 성공', fd);
          fs.rename('./folder/file.js', './folder/newfile.js', (err) => {
            if (err) {
              throw err;
            }
            console.log('이름 바꾸기 성공');
          });
        });
      });
    } else {
      throw err;
    }
  } else {
    console.log('이미 폴더 있음');
  }
});

fs.access(경로, 옵션, 콜백) : 폴더나 파일에 접근할 수 있는지를 체크

두 번째 인수로 상수들을 넣었다. F_OK는 파일 존재 여부, R_OK는 읽기 권한 여부, W_OK는 쓰기 권한 여부

파일/폴더가 없을 때의 에러코드는 ENOENT 

fs.mkdir(경로, 콜백) : 폴더를 만드는 메서드

fs.open(경로, 옵션, 콜백) : 파일의 아이디(fd변수)를 가져오는 메서드

두번째 인수는 어떤 동작을 할 것인지.

w 쓰기, r 읽기, a 기존 파일에 추가

fs.rename(기존 경로, 새 경로, 콜백) : 파일의 이름을 바꾸는 메서드

 

ex) 폴더 내용 확인 및 삭제

const fs = require('fs');

fs.readdir('./folder', (err, dir) => {
  if (err) {
    throw err;
  }
  console.log('폴더 내용 확인', dir);
  fs.unlink('./folder/newFile.js', (err) => {
    if (err) {
      throw err;
    }
    console.log('파일 삭제 성공');
    fs.rmdir('./folder', (err) => {
      if (err) {
        throw err;
      }
      console.log('폴더 삭제 성공');
    });
  });
});

 

fs.readdir(경로, 콜백) : 폴더 안의 내용물 확인

fs.unlink(경로, 콜백) : 파일을 지운다.(없으면 에러)

fs.rmdir(경로, 콜백) : 폴더를 지운다.(내부 파일 미리 지워야돼)

 

노드 8.5버전 이후에는 파일 복사하는 새로운 방법

const fs = require('fs');

fs.copyFile('readme4.txt', 'writeme4.txt', (error) => {
  if (error) {
    return console.error(error);
  }
  console.log('복사 완료');
});

 

감시해버버려

const fs = require('fs');

fs.watch('./target.txt', (eventType, filename) => {
  console.log(eventType, filename);
});

수정하면 change 이벤트, 파일명을 변경하거나 삭제하면 rename

rename 이후에는 watch 수행 안돼. change 이벤트가 두번씩 발생하기도 하니 주의

 

스레드풀 알아보기

위에서 fs 메서드를 여러 번 실행해도 백그라운드에서 동시에 처리되는데, 이는 스레드풀이 있기 때문

ex)

아래 코드를 실행하면 1~4가 비교적 먼저실행되고 5~8이 비교적 늦게 실행된다.(1~4, 5~8의 실행순서는 랜덤)

왜냐면 기본 스레드가 4개라서 ㅎㅎ

우리는 스레드풀을 직접 컨트롤할 수는 없지만 개수를 조절할 수는 있다.

윈도 SET UV_THREADPOOL_SIZE=1, 맥 리눅스 UV_THREADPOOL_SIZE=1

const crypto = require('crypto');

const pass = 'pass';
const salt = 'salt';
const start = Date.now();

crypto.pbkdf2(pass, salt, 1000000, 128, 'sha512', () => {
  console.log('1:', Date.now() - start);
});

crypto.pbkdf2(pass, salt, 1000000, 128, 'sha512', () => {
  console.log('2:', Date.now() - start);
});

crypto.pbkdf2(pass, salt, 1000000, 128, 'sha512', () => {
  console.log('3:', Date.now() - start);
});

crypto.pbkdf2(pass, salt, 1000000, 128, 'sha512', () => {
  console.log('4:', Date.now() - start);
});

crypto.pbkdf2(pass, salt, 1000000, 128, 'sha512', () => {
  console.log('5:', Date.now() - start);
});

crypto.pbkdf2(pass, salt, 1000000, 128, 'sha512', () => {
  console.log('6:', Date.now() - start);
});

crypto.pbkdf2(pass, salt, 1000000, 128, 'sha512', () => {
  console.log('7:', Date.now() - start);
});

crypto.pbkdf2(pass, salt, 1000000, 128, 'sha512', () => {
  console.log('8:', Date.now() - start);
});

7. 이벤트 이해하기

스트림에서 on('data',콜백) 사용 했지. 이건 data라는 이벤트가 발생할 때 콜백 함수를 호출하도록 이벤트를 등록한 거야

우리가 이벤트를 만들어보자

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'); // 이벤트 호출
myEvent.emit('event2'); // 이벤트 호출

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'); // 실행 안 됨

console.log(myEvent.listenerCount('event2'));

--------------------------------------------------------------------------
이벤트 1
이벤트 2     
이벤트 2 추가
이벤트 3     
2

 

on(이벤트명, 콜백)

addListener(이벤트명, 콜백) : on과 같아

emit(이벤트명) : 이벤트 호출

once(이벤트명, 콜백) : 한번만 실행되는 이벤트

removeAllListeners(이벤트명) : 이벤트에 연결된 모든 이벤트 리스너를 제거

removeListner(이벤트명, 리스너) : 이벤트에 연결된 리스너를 하나씩 제거

off(이벤트명, 콜백) : removeListener와 같아

listenerCount(이벤트명) : 현재 리스너가 몇 개 연결되어 있는지 확인

8. 예외 처리하기

노드에서는 예외 처리가 정말!!!!!!!!!!중요.

멀티 스레드 프로그램에서는 스레드가 멈추면 다른 스레드가 대신하면 되지만 노드의 메인 스레드는 하나야. 소중해

try/catch 사용하자

 

const fs = require('fs');

setInterval(() => {
  fs.unlink('./abcdefg.js', (err) => {
    if (err) {
      console.error(err);
    }
  });
}, 1000);

 

에러가 발생하지만 노드 내장 모듈의 에러는 실행 중인 프로세스를 멈추지 않는다. 에러 로그를 보고 나중에 수정해

 

const fs = require('fs').promises;

setInterval(() => {
  fs.unlink('./abcdefg.js')
}, 1000);

 

프로미스의 에러는 catch하지 않아도 알아서 처리. 근데 노드 버전이 올라가면서 바뀔 수있어. 따라서 프로미스를 사용할 때는 항상 catch 붙이자

 

process.on('uncaughtException', (err) => {
  console.error('예기치 못한 에러', err);
});

setInterval(() => {
  throw new Error('서버를 고장내주마!');
}, 1000);

setTimeout(() => {
  console.log('실행됩니다');
}, 2000);

 

process 객체에 uncaughtException 이벤트 리스너를 달았다. 처리하지 못한 에러가 발생했을 때 이벤트 리스너가 실행되고 프로세스가 유지. 이부분 없다면 setTimeout 실행 안돼

uncaughtException은 단순히 에러 내용을 기록하는 정도로 사용하고, 에러를 기록한 후 process.exit()으로 프로세스를 종료하는 것이 좋다. 에러가 발생하는 코드를 수정하지 않는 이상, 에러는 계속 발생

 

자주 발생하는 에러들

node: command not found: 노드를 설치했지만 이 에러가 발생한다면 환경변수 문제야

ReferenceError: 모듈 is not defined: 모듈을 required했는지 확인

Error: Cannot find module 모듈명: 해당 모듈을 require했지만 설치하지 않았다.

Error: Can't set headers after they are sent: 요청에 대한 응답을 보낼 때 응답을 두번 이상 보냈다.(한번만!!)

FATAL ERROR: CALL_AND_RETRY_LAST Alloation failed - JavaScript heap out of memory: 코드를 실행할 때 메모리가 부족하여 스크립트가 정상 작동하지 않는 경우. 코드가 잘못되었을 확률이 높아. 코드확인

만약 코드가 정상이라면 노드의 메모리를 늘릴수있어

node --max-old-space-size=4096 파일명 과 같은 명령어(4096은 4GB)

UnhandledPromiseRejectionWarning: Unhandled promise rejection: 프로미스 사용 시 catch 메서드를 붙이지 않으면 발생한다.

EADDRINUSE 포트 번호: 해당 포트에 이미 다른 프로세스가 연결되어 있다.

 

note 프로세스 종료하기

윈도

netstat =ano | findstr 포트

taskkill /pid 프로세스아이디 /f

lsof -i tcp:포트

kill -9 프로세스아이디

 

EACCES 또는 EPERM: 노드가 작업을 수행하는데 권한이 충분하지 않다. 권환 확인해봐. 맥이나 리눅스면 sudo

EJSONPARSE: package.json 등의 JSON 파일에 문법 오류가 있을 때 발생. 쉼표 같은거 확인

ECONNREFUSED: 요청을 보냈으나 연결이 성립하지 않을 때 발생. 요청받는 서버 주소나 꺼져있는 확인 

ETARGET: package.json에 기록한 패키지 버전이 존재하지 않을 때

ETIMEOUT: 요청을 보냈으나 응답이 일정 시간 내에 오지 않을 때 발생. 서버 확인

ENOENT: no such file or directory: 지정한 폴더나 파일이 존재하지 않는 경우. 맥 리눅스는 대소문자확ㅇ니

728x90
LIST

' > nodejs 교과서' 카테고리의 다른 글

6. 익스프레스 웹 서버 만들기  (0) 2021.02.21
5. 패키지 매니저  (0) 2021.02.20
4. http 모듈로 서버 만들기  (0) 2021.02.20
2. 알아두어야 할 자바스크립트  (0) 2021.02.18
1. 노드 시작하기  (0) 2021.02.18
댓글
공지사항