03. NodeJS 사용하기

Kim Sang Yeob·2023년 1월 12일
1

NodeJS Book 3th

목록 보기
3/9
post-thumbnail

3.1 REPL 사용하기

1) REPL

자바스크립트는 스크립트 언어라서 즉석에서 코드 실행 가능

  • REPL이라는 콜솔 제공
  • R(Read), E(Evaluate), P(Print), L(Loop)
  • 터미널에 node 입력해서 사용 가능
  • 간단한 코드를 테스트하는 용도로 적합

3.2 JS 파일 실행하기

  • node {filename}.js 으로 실행

3.3 모듈

1) 모듈

노드는 Javascript 코드를 모듈화 가능

  • 모듈 : 특정한 기능을 하는 함수나 변수들의 집합
  • 모듈로 만들면 여러 프로그램에서 재사용 가능

2) 모듈 만들어보기

// var.js

const odd = '홀수';
const even = '짝수';
exports.odd = odd;
exports.even = even;

// func.js

const { odd, even} = require('./var');
function checkOddOrEven(num){
    if(num % 2){
        return odd;
    }
    return even;
}
module.exports = checkOddOrEven;

// index.js

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'));
  • 한 가지만 module로 만들고 싶을 때는 보통module.exports를 사용
  • 두 가지 이상 module로 만들고 싶을 때는 보통 exports.{변수명}을 사용

3) this

console.log(this); // {}
console.log(this === module.exports) // true
function a(){
	console.log(this === global); 
}
a(); // true
  • function마다 this가 새로 생성
  • arrow function인 경우 부모의 this 물려 받음
  • 전역 스코프의 this일 경우만 this는 module.exports

4) require, 순환참조

끝까지 본 후 복습할 때 보고 다시 쓰기

3.4 노드 내장 객체

1) global

노드의 전역 객체

  • 브라우저의 window같은 역할
  • 모든 파일에서 접근 가능
  • window처럼 생략도 가능(console, require도 global의 속성)

2) global 속성 공유

  • global 속성에 값을 대입하면 다른 파일에서도 사용 가능
  • 하지만 좋은 방법은 아님..
  • 모듈로 만드는게 훨씬 좋은 방법

3) console 객체

  • console.time, console.timeEnd : 시간 로깅
  • console.error : 에러 로깅
  • console.log : 평범한 로그
  • console.dir : 객체 로깅
  • console.trace : 호출스택 로깅
  • console.table : 테이블 로깅

4) 타이머 메서드 (비동기)

  • setTimeout(콜백 함수, 밀리초) : 주어진 밀리초 이후에 콜백 함수를 실행
  • setInterval(콜백 함수, 밀리초) : 주어진 밀리초마다 콜백 함수를 반복 실행
  • setImmediate(콜백 함수) : 콜백 함수를 즉시 실행 (setTimeout(,0)과 비슷함)
  • clearTimeout(변수) : setTimeout 취소
  • clear Interval(변수) : setInterval 취소
  • clearImmediate(변수) : setImmediate 취소 (백그라운드에 있을 때 취소 가능)

5) process

  • 현재 실행중인 노드 프로세스에 대한 정보를 담고 있다
  • 컴퓨터마다 출력값이 아래와 다름

6) process.env

시스템 환경 변수들이 들어있는 객체

  • 비밀키(데이터베이스 비밀번호, 서드파티 앱 키 등)를 보관하는 용도로도 쓰임
  • 환경 변수는 process.env로 접근 가능
const secretId = process.env.SECRET_ID;
const secretCode = process.env.SECRET_CODE;
  • 일부 환경 변수는 노드 실행 시 영향을 미침
  • 예시) NODE_OPTIONS(노드 실행 옵션), UV_THREADPOOL_SIZE(스레드풀 개수)
    • max-old-space-size는 노드가 사용할 수 있는 메모리를 지정하는 옵션
NODE_OPTIONS=--max-old-space-size=8192
UV_THREADPOOL_SIZE=8

7) process.nextTick (콜백)

  • 이벤트 루프가 다른 콜백 함수들보다 nextTick의 콜백 함수를 우선적으로 처리함
  • 비슷한 경우로 promise가 있음 (nextTick처럼 우선순위가 높음)
  • 아래 예제에서 setImmediate, setTimeout보다 promise와 nextTick이 먼저 실행됨
  • setImmediate와 setTimeout은 환경에 따라 먼저 실행되는 것이 정해짐. 따라서 setTimeout( ,0)은 사용하지 말자.

8) process.exit (코드)

  • 현재의 프로세스를 멈춤
  • exit() or exit(0) : 정상 종료
  • exit({아무거나 들어있음}) : 비정상 종료

3.5 노드 내장 모듈

1) os

  • 운영체제의 정보를 담고 있다
  • 모듈은 require로 가져옴

2) os 모듈 메서드

  • 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() : 사용 가능한 메모리(RAM)를 보여줌
  • os.totalmem() : 전체 메모리 용량을 보여줌

3) path

  • 폴더와 파일의 경로를 쉽게 조작하도록 도와주는 모듈
  • 운영체제별로 경로 구분자가 다름(Windows:'\', POSIX:'/')

4) path 모듈 메서드

  • path.sep : 경로의 구분자. Window는 \, POSIX는 /.
  • path.delimiter : 환경 변수의 구분자. process.env.PATH를 입력하면 여러 개의 경로가 이 구분자로 구분되어 있음. Windows는 세미콜론(;)이고 POSIX는 콜론(:).
  • path.dirname(경로) : 파일이 위치한 폴더 경로를 보여줌
  • path.extname(경로) : 파일의 확장자를 보여줌
  • path.basename(경로, 확장자) : 파일의 이름(확장자 포함)을 보여줌. 파일의 이름만 표시하고 싶다면 basename의 두 번째 인자로 파일의 확장자를 넣어주면 됨.
  • path.parse(경로) : 파일 경로를 root, dir, base, ext, name으로 분리함
  • path.format(객체) : path.parse()한 객체를 파일 경로로 합침
  • path.normalize(경로) : 파일의 경로가 절대경로인지 상대경로인지 true나 false로 알려줌
  • path.isAbsolute(경로) : 파일의 경로가 절대경로인지 상대경로인지 true나 false로 알려줌
  • path.relative(기준경로, 비교경로) : 경로를 두 개 넣으면 첫 번째 경로에서 두 번째 경로로 가는 방법을 알려줌
  • path.join(경로, .. .) : 여러 인자를 넣으면 하나의 경로로 합쳐줌. 상대경로인 ..(부모 디렉터리)과 .(현 위치)도 알아서 처리해줌.
  • path.resolve(경로, .. .) : path.join()과 비슷하지만 차이가 있음. 차이점은 아래에 설명

5) 알아둬야 할 path 정보

  • join과 resolve의 차이 : resolve는 /를 절대경로로 처리, join은 상대경로로 처리
    • 상대 경로 : 현재 파일 기준. 같은 경로면 점 하나(.), 한 단계 상위 경로면 점 두 개(..)
    • 절대 경로는 루트 폴더나 노드 프로세스가 실행되는 위치가 기준
path.join('/a', '/b', 'c'); // 결과 : /a/b/c
path.resolve('/a', '/b', 'c'); // 결과 /b/c
  • \\와 \차이 : \는 윈도 경로 구분자, \\는 자바스크립트 문자열 안에서 사용(\가 특수문자여서 \\로 이스케이프 해준 것)
  • 윈도우에서 POSIX path를 쓰고 싶다면 : path.posix객체 사용
    • POSIX에서 윈도 path를 쓰고 싶다면 : path.win32 객체 사용

6) url

  • WHATWG와 노드의 주소 체계
  • url 모듈 사용하기
// url.js
const url = require('url');
const { URL } = url;
const myURL = new URL('https://github.com/yeobi01/Algorithm/tree/master/solved.ac?name=yeobi_01');
console.log('new URL(): ', myURL);
console.log('url.format(): ', url.format(myURL));

// console 출력
new URL():  URL {
  href: 'https://github.com/yeobi01/Algorithm/tree/master/solved.ac?name=yeobi_01',  
  origin: 'https://github.com',
  protocol: 'https:',
  username: '',
  password: '',
  host: 'github.com',
  hostname: 'github.com',
  port: '',
  pathname: '/yeobi01/Algorithm/tree/master/solved.ac',
  search: '?name=yeobi_01',
  searchParams: URLSearchParams { 'name' => 'yeobi_01' },
  hash: ''
}
url.format():  https://github.com/yeobi01/Algorithm/tree/master/solved.ac?name=yeobi_01
  • url.searchParams 사용하기
const myURL = new URL('http://www.gilbut.co.kr/?page=3&limit=10&categor=modejs&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 출력
$ node searchParams

searchParams: URLSearchParams {
  'page' => '3',
  'limit' => '10',
  'categor' => 'modejs',
  'category' => 'javascript' }
searchParams.getAll(): [ 'javascript' ]
searchParams.get(): 10
searchParams.has(): true
searchParams.keys(): URLSearchParams Iterator { 'page', 'limit', 'categor', 'category' }
searchParams.values(): URLSearchParams Iterator { '3', '10', 'modejs', 'javascript' }[ 'es3', 'es5' ]
[ 'es6' ]
[]
searchParams.toString(): page=3&limit=10&categor=modejs&category=javascript

7) dns

  • DNS를 다룰 때 사용하는 모듈
  • 주로 도메인을 통해 IP나 기타 DNS 정보를 얻고자 할 때 사용
// dns.mjs
import dns from 'dns/promises';

const ip = await dns.lookup('gilbut.co.kr');
console.log('IP', ip);

const a = await dns.resolve('gilbut.co.kr', 'A');
console.log('A', a);

const mx = await dns.resolve('gilbut.co.kr','MX');
console.log('MX', mx);

const cname = await dns.resolve('www.gilbut.co.kr', 'CNAME');
console.log('CNAME', cname);

const any = await dns.resolve('gilbut.co.kr', 'ANY');
console.log('ANY', any);
$ node dns.mjs

IP { address: '49.236.151.220', family: 4 }
A [ '49.236.151.220' ]
MX [
  { exchange: 'aspmx2.googlemail.com', priority: 10 },
  { exchange: 'aspmx3.googlemail.com', priority: 10 },
  { exchange: 'alt2.aspmx.l.google.com', priority: 5 },
  { exchange: 'alt1.aspmx.l.google.com', priority: 5 },
  { exchange: 'aspmx.l.google.com', priority: 1 }
]
CNAME [ 'slb-1088813.ncloudslb.com' ]
ANY [
  { value: 'ns1-2.ns-ncloud.com', type: 'NS' },
  { value: 'ns1-1.ns-ncloud.com', type: 'NS' },
  {
    nsname: 'ns1-1.ns-ncloud.com',
    hostmaster: 'ns1-2.ns-ncloud.com',
    serial: 47,
    refresh: 21600,
    retry: 1800,
    expire: 1209600,
    minttl: 300,
    type: 'SOA'
  }
]
  • ip 주소는 간단하게 dns.lookup이나 dns.resolve(도메인)으로 얻을 수 있다.
  • A(ipv4 주소), AAAA(ipv6주소), NS(네임서버), SOA(도메인 정보), CNAME(별칭, 주로 www가 붙은 주소는 별칭인 경우가 많다).
  • MX(메일 서버) 등은 레코드라고 부르는데, 해당 레코드에 대한 정보는 dns.resolve(도메인, 레코드 이름)으로 조회

8) 단방향 암호화(crypto)

  • 암호화는 가능하지만 복호화는 불가능
    • 암호화 : 평문을 암호로 만듦
    • 복호화 : 암호를 평문으로 해독
  • 단방향 암호화의 대표 주자는 해시 기법
    • 문자열을 고정된 길이의 다른 문자열로 바꾸는 방식
    • abcdefgh 문자열 -> qvew

9) Hash 사용하기(sha512)

  • createHash(알고리즘) : 사용할 해시 알고리즘을 대입
    • md5, sha1, sha256, sha512 등이 가능하지만, md5와 sha1은 이미 취약점이 발견
    • 현재는 sha512 정도로 충분하지만, 나중에 sha512마저도 취약해지면 더 강화된 알고리즘으로 바꿔야함.
  • update(문자열) : 변환할 문자열을 대입
  • digest(인코딩) : 인코딩할 알고리즘을 대입
// 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'));
$ node hash
base64: dvfV6nyLRRt3NxKSlTHOkkEGgqW2HRtfu19Ou/psUXvwlebbXCboxIPmDYOFRIpqav2eUTBFuHaZri5x+usy1g==
hex: 76f7d5ea7c8b451b773712929531ce92410682a5b61d1b5fbb5f4ebbfa6c517bf095e6db5c26e8c483e60d8385448a6a6afd9e513045b87699ae2e71faeb32d6
base64: cx49cjC8ctKtMzwJGBY853itZeb6qxzXGvuUJkbWTGn5VXAFbAwXGEOxU2Qksoj+aM2GWPhc1O7mmkyohXMsQw==

10) pbkdf2

  • 컴퓨터의 발달로 기존 암호화 알고리즘이 위협받고 있음
    • sha512가 취약해지면 sha3으로 넘어가야함
    • 현재는 pbkdf2나, bcrypt, scrypt 알고리즘으로 비밀번호 암호화
    • Node는 pbkdf2와 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'));
    });
});
$ node pbkdf2

salt: 8pmiSd8eB02Y7KawklY4vphs3qZVRWgfIRJMGnorH5u9Cb0pXy2i7g0rUwNW0rchZTqLva4e5o0RW+gMxlRECQ==
password: OKqb3FD5yaFN+YJhoSNqdsgq+ZwCl4r7sn+zVKTJ3jRYuobNdBsh9R7eWVP+kPNddqpmzKo5tU5HW8YK0hnu+g==

11) 양방향 암호화 & 메서드

  • 대칭형 암호화(암호문 복호화 가능)
    • Key가 사용됨
    • 암호화할 때와 복호화 할 때 같은 Key를 사용해야 함

암호학을 배운다면 다시 참고해서 공부해보자..

12) util

  • deprecated와 promisify가 자주 쓰임
// util.js

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);
    });
$ node util

(node:27980) DeprecationWarning: dontUseMe 함수는 deprecated되었으니 더 이상 사용하지
 마세요!
(Use `node --trace-deprecation ...` to show where the warning was created)
YMNmZVFzN0zy3O0kHADaj3BhgyyA8PzKFb1TpMqaj2kP2oRORiPbVjwatz4KiV0KhmnXflAcsxy/1J2SgN2+ag==

12) util의 메서드

  • util.deprecate : 함수가 deprecated 처리되었음을 알려준다.
    • 첫 번째 인자로 넣은 함수를 사용했을 때 경고 메시지가 출력된다.
    • 두 번째 인자로 경고 메시지 내용을 넣으면 된다. 함수가 조만간 사라지거나 변경될 때 알려줄 수 있어 용이하다.
  • util.promisify : 콜백 패턴을 프로미스 패턴으로 바꿔준다.
    • 바꿀 함수를 인자로 제공하면 된다. 이렇게 바꿔두면 async/await 패턴까지 사용할 수 있어서 좋다. 단, 콜백이 (error, data) => {}형식이어야한다.

13) worker_threads

노드에서 멀티 스레드 방식으로 작업 가능

  • isMainThread : 현재 코드가 메인 스레드에서 실행되는지, 워커 스레드에서 실행되는지 구분
  • 메인 스레드는 new Worker를 통해 현재 파일(__filename)을 워커 스레드에서 실행시킴
  • worker.postMessage로 부모에서 워커로 데이터를 보냄
  • parentPort.on('message')로 부모로부터 데이터를 받고, postMessage로 데이터를 보냄

멀티스레드 예제 소스코드

  • 싱글스레드로 소수의 갯수 찾아주는 프로그램은 약 10초 걸렸으나, 8개의 멀티스레드로 소수의 갯수 찾아주는 프로그램을 만드니 2초 걸림.
  • NodeJS에서는 멀티스레드를 위한 모듈이 만들어져 있긴하지만, 사용하기 복잡하고 굉장히 수동적이다.

14) child_process

  • 노드에서 다른 언어의 파일을 실행해달라고 요청할 수 있음
  • 멀티스레드는 노드보다 다른언어로 작성하는게 더 편리하기 때문에, 이러한 경우에 사용
// spawn.js

const spawn = require('child_process').spawn;
const 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());
});

15) 기타 내장 모듈들

  • assert : 값을 비교하여 프로그램이 제대로 동작하는지 테스트하는 데 사용
  • net : HTTP보다 로우 레벨인 TCP나 IPC 통신을 할 때 사용
  • string_decoder : 버퍼 데이터를 문자열로 바꾸는 데 사용
  • tls : TLS와 SSL에 관련된 작업을 할 때 사용
  • tty : 터미널과 관련된 작업을 할 때 사용
  • dgram : UDP와 관련된 작업을 할 때 사용
  • v8 : V8 엔진에 직접 접근할 때 사용
  • vm : 가상 머신에 직접 접근할 때 사용

3.6 파일 시스템 접근하기

1) fs

  • 파일 시스템에 접근하는 모듈
    • 파일/폴더 생성, 삭제, 읽기, 쓰기 기능
    • 웹 브라우저에서는 제한적이였으나 노드는 권한을 가지고 있음
// 파일 읽기
const fs = require('fs').promises;

fs.readFile('./readme.txt')
    .then((data) => {
        console.log(data);
        console.log(data.toString());
    })
    .catch((err) => {
        throw err;
    });
// 파일 쓰기
const fs = require('fs').promises;

fs.writeFile('./writeme.txt','글이 입력됩니다.')
    .then((data) => {
        return fs.readFile('./writeme.txt');
    })
    .then((data) => {
        console.log(data.toString());
    })
    .catch((err) => {
        throw err;
    });
// 비동기(async) 확인하기
// 출력은 1234 랜덤적으로 나옴. ex) 3241
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());
});

2) 동기 메서드와 비동기 메서드

  • 동기와 비동기 : 백그라운드 작업 완료 확인 여부
  • 블로킹과 논 블로킹 : 함수가 바로 return 되는지 여부
  • 노드에서는 대부분 동기-블로킹 방식과 비동기-논블로킹 방식

3) fs 동기로 실행하기

// 출력 : 1 -> 2 -> 3 -> 4
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());
  • 보통 서버 실행전에 사용

4) fs 비동기 순서 지키기

  • promise로 구현
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((err) => {
        throw err;
    })
  • async await으로 구현
const fs = require('fs').promises;

async function main() {
    try{
        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());
    } catch(err) {
        console.error(err);
    }
}
main();

5) 버퍼와 스트림 이해하기

  • 버퍼 : 일정한 크기로 모아두는 데이터
    • 일정한 크기가 되면 한 번에 처리
    • 버퍼링 : 버퍼에 데이터가 찰 때까지 모으는 작업
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());

console.log(Buffer.alloc(5));

//출력
<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>
32
저를 버퍼로 바꿔보세요
띄엄 띄엄 띄어쓰기
<Buffer 00 00 00 00 00>
  • 스트림 : 데이터의 흐름
    • 일정한 크기로 나눠서 여러 번 걸쳐서 처리
    • 버퍼(또는 청크)의 크기를 작게 만들어서 주기적으로 데이터를 전달
    • 스트리밍 : 일정한 크기의 데이터를 지속적으로 전달하는 작업
    • 서버의 메모리를 아낄 수 있다. 메모리 관리에서 효율적!
// 예제 확인을 위해 highWaterMark를 16으로 해서 16byte씩 끊어서 출력
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 0d 0a ec 95 88 eb 85 95 ed 95 98 ec 84> 16
data: <Buffer b8 ec 9a 94 2e 20 ed 97 ac eb a1 9c 20 eb 85 b8> 16
data: <Buffer eb 93 9c 20 ed 97 ac eb a1 9c 20 ec 8a a4 ed 8a> 16
data: <Buffer b8 eb a6 bc 20 ed 97 ac eb a1 9c 20 eb b2 84 ed> 16
data: <Buffer 8d bc> 2
end: 저는 조금씩 조금씩 나눠서 전달됩니다. 나눠진 조각을 chunk라고 부릅니다.
안녕하세요. 헬로 노드 헬로 스트림 헬로 버퍼
// 스트림으로 파일 쓰기 예제
const fs = require('fs');

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

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

// writeme2.txt
이 글을 씁니다
한 번 더 씁니다.

// console
파일 쓰기 완료

6) pipe

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

// pipe로 readStream과 writeStream을 합쳐서 파일 복사하기
const readStream = fs.createReadStream('./readme3.txt', {highWaterMark: 16});
const writeStream = fs.createWriteStream('./writeme3.txt');
readStream.pipe(writeStream); // <- 이 코드는 주석처리 해야함

// zlibStream으로 압축해서 파일 복사하기
const zlibStream = fs.createWriteStream('./writeme4.txt.gz');
readStream.pipe(zlibStream).pipe(writeStream);

7) 버퍼와 스트림 메모리 효율 확인

1GB 파일을 버퍼 방식과 스트림 방식으로 생성했을 때 나타나는 메모리 효율 차이 비교하기

8) 기타 fs 메서드

  • 파일 및 폴더 생성
// folder라는 폴더가 없으면 만들고, 그 안에 file.js를 만든 후, newfile.js로 이름 바꾸기
const fs = require('fs').promises;
const constants = require('fs').constants;

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((fd) => {
        console.log('빈 파일 만들기 성공', fd);
        fs.rename('./folder/file.js','./folder/newfile.js');
    })
    .then(() => {
        console.log('이름 바꾸기 성공');
    })
    .catch((err) => {
        console.error(err);
    })
  • 파일 및 폴더 생성, 삭제 메소드

    • fs.access(경로, 옵션, 콜백) : 폴더나 파일에 접근할 수 있는지 체크. 두 번째 인자는 상수들을 넣는다. F_OK는 파일 존재 여부, R_OK는 읽기 권한 여부, W_OK는 쓰기 권한 여부를 체크. 파일/폴더나 권한이 없다면 에러가 발생하는데, 파일/폴더가 없을 때의 에러 코드는 ENOENT이다.
    • fs.mkdir(경로, 콜백) : 폴더를 만드는 메서드. 이미 폴더가 있다면 에러가 발생하므로 먼저 access() 메서드를 호출하서 확인하는 것이 중요
    • fs.open(경로, 옵션, 콜백) : 파일의 아이디(fd 변수)를 가져오는 메서드. 파일이 없다면 파일을 생성한 뒤 아이디를 가져옴. 가져온 아이디를 사용해 fs.read()나 fs.write()로 읽거나 쓸 수 있음. 두 번째 인자로 어떤 동작을 할 것인지 설정할 수 있음. 쓰려면 w, 읽으려면 r, 기존 파일에 추가하려면 a이다. 예제로는 w로 설정했으므로 파일이 없을 때 새로 만들 수 있었다. r이였다면 에러 발생.
    • fs.rename(기존 경로, 새 경로, 콜백) : 파일의 이름을 바꾸는 메서드. 기존 파일 위치와 새로운 파일 위치를 적어주면 된다. 반드시 같은 폴더를 지정할 필요는 없으므로 잘라내기 같은 기능을 할 수도 있다.
  • 폴더 내용 확인 및 삭제

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

fs.readdir('./folder')
    .then((dir) => {
        console.log('폴더 내용 확인', dir);
        return fs.unlink('./folder/newfile.js');
    })
    .then(() => {
        console.log('파일 삭제 성공');
        return fs.rmdir('./folder');
    })
    .then(() => {
        console.log('폴더 삭제 성공');
    })
    .catch((err) => {
        console.error(err);
    });
  • 폴더 내용 확인 및 삭제 메소드
    • fs.readdir(경로, 콜백) : 폴더 안의 내용물을 확인할 수 있다. 배열 안에 내부 파일과 폴더명이 나옴.
    • fs.unlink(경로, 콜백) : 파일을 지울 수 있다. 파일이 없다면 에러가 발생하므로 먼저 파일이 있는지를 꼭 확인해야 한다.
    • fs.rmdir(경로, 콜백) : 폴더를 지울 수 있다. 폴더 안에 파일이 있다면 에러를 발생하므로 먼저 내부 파일을 모두 지우고 호출해야한다.
  • 파일 복사하는 방법 (스트림을 사용하는게 더 다양한 작업 가능)
const fs = require('fs').promises;

fs.copyFile('./readme4.txt', './writeme4.txt')
    .then(() => {
        console.log('복사 완료');
    })
    .catch((err) => {
        console.error(err);
    })
  • 파일을 감시하는 방법 (변경사항 발생 시 이벤트 호출)
const fs = require('fs').promises;

fs.watch('./target.txt')
    .then((eventType, filename) => {
        console.log(eventType, filename);
    });
  • fs.existSync, fs.stats 등 공식문서 확인하기

9) 스레드풀 알아보기

  • (SET)(<- 윈도우는 추가해야 명령 먹음) UV_THREADPOOL_SIZE=8 이런식으로 한 번에 돌아가는 코어의 개수를 지정할 수 있음.
  • 기본적으로 4개씩 돌아감
  • 아래 코드는 오래 걸리는 작업을 통해 한 번에 4개씩 돌아가는 것을 확인 할 수 있는 예제
const crypto = require('crypto');

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

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

10) 커스텀 이벤트

필요해질 때 다시 공부하기..

3.7 예외 처리하기

1) 예외 처리

  • 예외(Exception) : 처리하지 못한 에러
    • 노드에서는 예외와 에러가 같은 것이라고 생각하기
    • 노드 스레드를 멈춤
    • 노드는 기본적으로 싱글 스레드라 스레드가 멈춘다는 것은 프로세스가 멈추는 것
    • 에러 처리는 필수

2) try catch문

  • 기본적으로 try catch문으로 예외를 처리
    • 에러가 발생할 만한 곳을 try catch로 감싸기
    • 모든 코드에 try catch를 하는 것도 방법이지만, 느낌 zero

3) Promise 에러

  • 프로미스의 에러는 따로 처리하지 않아도 됨
  • 경고문이 console에 길게 찍힘
  • 버전이 올라감에 따라 달라질 수 있음
  • 따라서 프로미스에 catch를 달아주자

4) uncaughtException

  • 최후의 수단으로 사용
  • 콜백 함수의 동작이 보장되지 않음
  • 따라서 복구 작업용으로 쓰는 것은 부적합
  • 에러 내용 기록 용으로만 사용하는게 좋음
process.on('uncaughtException', (err) => {
    console.error('예기치 못한 에러', err);
});
setInterval(() => {
    throw new Error('서버를 고장내주마!');
}, 1000);
setTimeout(() => {
    console.log('실행됩니다');
},2000);

// 출력
예기치 못한 에러 Error: 서버를 고장내주마!
실행됩니다
예기치 못한 에러 Error: 서버를 고장내주마!
예기치 못한 에러 Error: 서버를 고장내주마!
예기치 못한 에러 Error: 서버를 고장내주마!
// 계속 반복

5) 프로세스 종료하기

// 윈도우
$ netstat -ano | findstr 포트
$ taskkill /pid 프로세스아이디 /f

// 맥/리눅스
$ lsof -i tcp:포트
$ kill -9 프로세스아이디
profile
Studying NodeJS...

0개의 댓글