공부

[노드교과서] 3장 노드 기능 알아보기

실버dev 2018. 8. 12. 15:45

노드가 기본적으로 제공하는 객체와 모듈에 대해 알아봄. 모듈을 사용할 때 중요한 개념인 버퍼, 스트림, 동기와 비동기, 이벤트, 예외 처리에 대해서도 배움.



1. REPL 사용하기


REPL이란? Read 읽고 Eval 해석하고 Print 결과물을 반환하고 Loop 반복한다고 해서 REPL임.

콘솔에서 node치고 한줄 씩 코드치면 결과나오는게 바로 REPL.



2, JS파일 실행하기


콘솔에 node js파일 치면 실행된다.



3. 모듈로 만들기


모듈이란? -> 특정한 기능을 하는 함수나 변수의 집합. 모듈로 만들면 여러 프로그램에 재사용할 수 있음. 보통은 파일 하나가 모듈이 됨. 파일별로 코드를 모듈화.


var.js

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

module.exports = {
odd,
even
}


func.js

const { odd, even } = require("./var");

const checkOddOrEven = num => {
if(num % 2) {
return odd;
} else {
return even;
}
}

module.exports = checkOddOrEven;


index.js

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

const checkStringOddOrEven = str => {
if (str.length % 2) {
return odd;
} else {
return even;
}
}

console.log(checkNumber(10));
console.log(checkStringOddOrEven('가나다라마바사'));


결과

PS D:\coding\node교과서> node index

짝수

홀수


모듈에는 module.exports, 불러올 js파일에선 require을 사용해서 모듈을 사용함. 




4. 내장 객체 알아보기


아까 module과 require는 따로 선언하지 않았는데 사용할 수 있었음. 이유는 노드에서 기본적으로 제공하는 내장 객체 이기 때문. 이외에 노드에서 자주 사용되는 내장객체에 대해 알아보자.



4-1) global


말 그대로 전역객체임. 모든 파일에서 접근 가능하다.

생략 가능함. 이전에 쓴 require도 사실은 global.require에서 생략된 것.


globalA.js

module.exports = () => global.message;


globalB.js

const A = require("./glabalA");

global.message = "안녕하세요";
console.log(A());


globalB를 실행시키면 안녕하세요가 출력된다. B에서 A의 global.message를 초기화했음을 알 수 있음.


global을 이용해 파일간에 데이터를 공유할 수 있지만 남용하면 프로그램의 규모가 커질수록 어떤 파일에서 global 객체에 값을 대입했는지 찾기 힘들어 유지보수가 어렵다. 남용하지말자.



4-2) console


global객체 안에 들어있음. 보통 디버깅을 위해 사용된다. 종류는 여러가지다


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

console.log(내용): 콘솔에 내용 출력

console.log(에러): 에러 출력

console.dir(객체, 옵션): 콘솔에 객체 출력. 옵션으로 colors=true를 넣으면 콘솔에 색이 추가됨. depth는 객체 안의 객체를 몇 단계까지 보여줄지 결정.

console.trace(레이블): 에러의 발생 위치를 추적해서 알려줌.



4-3) 타이머


setTimeout(콜백함수, 밀리초) : 설정한 시간 뒤에 콜백함수를 실행시킴


setInterval(콜백함수, 밀리초) : 설정한 시간마다 콜백함수를 반복 실행


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


clearTimeout, clearsetInterval, clearImmediate 로 실행을 취소할 수 있다.


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

clearImmediate(immediate2);

즉시실행보다 클리어가 뒤에 있는데도 취소시킨다. 인터프리터 언어라 위에서부터 읽어들이므로 취소가 안될 줄 알았는데 취소됨



4-4) __filename, __dirname


현재 파일의 결로, 현재 디렉토리의 경로를 알려 준다.

console.log(__filename); //d:\coding\node교과서\globalB.js
console.log(__dirname); //d:\coding\node교과서



4-5) module, exports


모듈을 만들때 module.exports를 사용했는데 module 말고 exports 객체로도 모듈을 만들 수 있음.

exports.odd = "홀수";
exports.even = "짝수";


위에서 module.exports를 사용한 것과 동일하게 작동한다. requrie로 똑같이 불러오면 된다.

동일하게 동작하는 이유는 module.exports와 exports가 같은 객체를 참조하기 때문임. 


module.exports에는 어떤 값이든 대입해도 되지만 exports에는 반드시 객체처럼 속성명과 속성값을 대입해야 한다. 객체만 사용할 수 있으므로 func.js 같은 함수는 대입불가.


exports와 module.exports 는 참조관계에 있으므로 한 모듈에서 둘을 동시에 사용하지 않는게 좋음.




4-6) process


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

REPL에 process 치면 모든 정보를 볼 수 있음.


4-6-1) process.env


시스템의 환경 변수임. 서비스의 중요한 키를 저장하는 공간으로 사용된다.

const secretId = process.env.SECRET_ID;

이런식으로 사용함. 그리고 process.env에 SECRET_ID를 넣어주면 된다.



4-6-2) process.nextTick(콜백)


이벤트 루프가 다른 콜백 함수들보다 nextTick의 콜백함수를 우선으로 처리함

setImmediate(() => {
console.log("setImmediate");
});

process.nextTick(() => {
console.log("nextTick");
});


D:\coding\node>node var

nextTick

setImmediate


nextTick이 setImmediate보다 우선적으로 실행됨을 알 수 있다.

추가적으로  promise가 resolve되어 실행되는 콜백함수도 다른 콜백보다 우선됨. 이유는 promise와 nextTick은 일반 태스크 큐에 추가되지않고 마이크로 태스크라고 따로 분류됨.



4-6-3) process.exit(코드)


실행중인 노드 프로세스를 종료함. 서버에선 서버가 멈추므로 거의 사용하지 않지만 그 외에 독립적인 프로그램에서 수동으로 노드를 멈추게 하기 위해 사용된다.




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


필요할 때 찾아보면 되는 부분이라 대강 보고 넘어감



5-1) os


운영체제의 정보를 가져오는 모듈.



5-2) path


폴더와 파일의 경로를 쉽게 조작하도록 도와주는 모듈. 운영체제별로 경로 구분자가 다르기 때문에 필요함. 



5-3) url


인터넷 주소를 쉽게 조작하도록 도와주는 모듈. 주소는 WHATWG 방식과 노드 방식 두가지가 있음.

WHATWG 방식은 search 부분을 searchParams라는 특수한 객체로 반환함. 주소에 ?표부터 시작되어 키=값 형식으로 데이터를 전달하는 것.



5-4) querystring


WHATWG 방식의 wul 대신 기존 노드의 url을 사용할 때 search 부분을 사용하기 쉽게 객체로 만드는 모듈.



5-5) crypto


암호화를 도와주는 모듈. 로그인시 비밀번호는 무조건 암호화 해야함.



5-5-1) 단방향 암호화


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

비밀번호는 한번 암호화 하면 복호화 할 필요가 없음. 암호화해서 데이터베이스에 저장하고 로그인시에도 입력받은 패스워드를 암호화 해서 데이터 베이스와 암호화된 데이터끼리 비교함.


const crypto = require('crypto');
console.log('암호화 문자열 : ', crypto.createHash('sha512').update('password123').digest('base64'));

이렇게 사용한다.

createHash는 암호화에 사용될 해시 알고리즘임. md5, sha1. sha256, sha512등이 있음. md5와 sha1은 이미 취약점이 발견됨.

update가 입력할 비밀번호이고 digest는 인코딩할 알고리즘임. base64 hex latin1 등이있음.


현재는 pdkdf2나 bcrypt, scrypt알고리즘을 사용함.

crypto.randomBytes(64, (err, buf) => {
const salt = buf.toString("base64");
console.log("salt : ", salt);
crypto.pbkdf2("password123", salt, 10000, 64, 'sha512', (err, key)=> {
console.log('password : ', key.toString('base64'));
})
});


이렇게 사용한다. 패스워드에 salt 문자열을 붙인 후 10000번 해시 알고리즘을 반복해서 적용하는 것이다. 키의 길이는 64글자이고 sha512방식의 알고리즘을 사용함


결과:


5-5-2) 양방향 암호화.


단방향 암호화를 배우면서 유추했듯이 양방향은 암호화한 문자열을 복호화 할 수 있는 암호화 방식이다. 암호를 복호화 하려면 암호화할 때 사용한 키와 같은 키를 사용해야 한다.


const crypto = require("crypto");

const cipher = crypto.createCipher("aes-256-cbc", "키");
let result = cipher.update("암호화할 문장", "utf8", "base64");
result += cipher.final("base64");
console.log("암호화 : ", result);

const decipher = crypto.createDecipher("aes-256-cbc", "키");
let result2 = decipher.update(result, "base64", "utf8");
result2 += decipher.final("utf8");
console.log("복호화 : ", result2);


결과


aes-256-cbc가 암호화 알고리즘이다.




5-6) util


말 그대로 각종 편의 기능을 모아둔 모듈이다.




6. 파일 시스템 접근하기


fs 모듈은 파일 시스템에 접근하는 모듈이다. 파일을 생성 삭제 읽거나 쓸 수 있음.


읽기 readFile


const fs = require("fs");

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



readFile의 결과물은 버퍼 형식으로 제공된다. toString()을 사용해 문자열로 변환.


fs.writeFile("./writeme.txt", "글 입력", err => {
if (err) {
throw err;
}
});

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




현재 디렉토리에 txt파일이 생성되었음.



6-1) 동기 메서드와 비동기 메서드


readFile은 비동기 방식의 메서드임. 비동기 메서드는 백그라운드에 해당 파일을 읽으라고 요청만 하고 다음 작업으로 넘어감. 그러면 백그라운드에서 작업을 하고 메인 스레드에 알림을 주면 그제서야 콜백함수를 실행함. 비동기 망식은 수백 개의 i/o요청이 들어와도 요청을 백그라운드에 넘기고 다른 작업을 할 수 있으므로 효율적임.

readFileSync는 동기 방식의 메서드임. 이는 순서대로 작업하지만 앞 순서의 작업을 마쳐야만 다음 작업을 진행하므로 i/o 요청이 많으면 성능에 문제가 생김.

보통 동기 메서드는 Sync가 붙어서 구분하기 쉽다.

비동기 방식으로 하되 순서를 유지하고 싶으면 콜백지옥, 프로미스, asyn/await 사용하면 된다.



6-2) 버퍼와 스트림 이해하기


파일을 읽거나 쓰는 방식이 크게 두가지가 있는데 버퍼를 이용하는 방식과 스트림을 이용하는 방식이다.

버퍼는 데이터를 모아서 한번에 전송하는 방식이고 스트림은 버퍼의 크기를 작게 만들어서 데이터를 여러번에 나눠서 보내는 방식이다.



6-3) 기타 js 메서드




7. 이벤트 이해하기


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.emit("event1");
myEvent.emit("event2");

myEvent.once("event3", () => {
console.log("이벤트 3");
});
myEvent.emit("event3");
myEvent.emit("event3");

myEvent.on("event4", () => {
console.log("이벤트 4");
});
myEvent.removeAllListeners('event4');
myEvent.emit('이벤트 4');

const listener = () => {
console.log('이벤트 5');
};
myEvent.on('event5', listener);
myEvent.removeAllListeners('event5', listener);
myEvent.emit('event5');

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



on(이벤트명, 콜백) : 이벤트 이름과 발생시 콜백을 연결해줌.


addListener : on이랑 같다


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


once : 한번만 실행되는 이벤트


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


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


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


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




3.8 예외처리하기


멀티 스레드 프로그램은 스레드 하나가 멈추면 그 일을 다른 스레드가 대신하지만 노드는 싱글 스레드이므로 예외처리가 매우 중요하다.


setInterval(() => {

console.log("시작");
try{
throw new Error("에러");
} catch (err) {
console.error(err);
}
}, 1000);

try catch문으로 처리하면 프로세스가 멈추지 않는다.


const fs = require("fs");

setInterval(() => {
console.log("시작");
fs.unlink("./asfksdf.js", err => {
if (err) {
console.error(err);
}
});
}, 1000);

노드 내장 모듈의 에러는 실행 중인 프로세스를 멈추지 않음.


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

그외에 정말 예측 불가능한 에러는 process 객체에 uncaughtException 이벤트 리스너를 달아서 처리한다. -> 이건 최후의 수단이고 노드 공식문서는 uncaughtException 이벤트 발생 후 다음 동작이 제대로 동작하는지 보증하지않음.