Node 3장
REPL 사용하기
노드 콘솔은 REPL이라하는데 이유는
- Read : 입력한 코드를 읽고
- Eval : 해석하고
- Print : 결과물을 반환하고
- Loop : 종료할 때가지 반복함.
- 터미널에 node를 입력함으로서 접속가능
- 간단한 명령어 수행
JS 파일 실행
- helloworld.js 라는 파일을 만들었으면 node helloworld 로 접근할 수 있음
모듈로 만들기
노드는 코드를 모듈로 만들 수 있다는 점에서 브라우저의 자바스크립트와는 다르다.
- 크롬 60버전부터는 브라우저에서도 모듈을 사용할 수 있음
require(‘./var’); 로 불러오고, module.exports = 로 export함
- 이때, 다른이름으로 불러올 수 있다.
- 예를들어, module.exports = addTwoNum을 const addTwo = require(‘./temp’); 로 불러올 수 있음.
- 단, 이때 ES6 이상의 문법에서 비구조화와 헷갈리지말자.
자바스크립트 자체 모듈
- import {odd, even} from ‘./var’;
- export defualt checkOddOrEven
- 이런 식으로 함.
- export : 함수, 객체, 원시값을 내보낼때 사용
- 받을땐 import {a, b, c, d} from ‘./temp’ 로 받음
- exports : 함수, 객체, 원시값을 객체의 형태로 내보낼 때 사용
1 2 3 4 5
exports.a = "a"; exports.b = "b"; //이렇게 내보내고 import importTemp from "./temp"; //이렇게 받음. 이름은 달라도 됨. exports 객체를 받는 것이기에
- exports default : 파일내에서 하나의 고정된 값만 내보낼때 사용
노드 내장객체
- global
- 브라우저의 window와 같은 전역객체
- 모든 파일에서 접근가능
- 이때 부를땐 global을 생략하고 부를 수있다.
- ex) globa.require() -> require()
- global 객체의 속성에 값을 대입하여 파일간 데이터를 공유할 수 있지만, 남용하지는 마라. 프로그램이 커질수록 어디서 넣었는지 찾기 힘들어져 유지보수가 어려워진다. 모듈형식으로 사용하라
- console
- console.log()
- console.error()
- console.time() - console.timeEnd()
- time()부터 timeEnd()까지의 시간 출력
- 이때 양쪽에 같은 레이블을 넣어, 레이블별로 구분가능하다.
- console.table([{name:’제로’, birth:1994}, { name: ‘hero’, birth: 1988}])
- 이런식으로 테이블을 만들 수도 있음
- console.dir()
- 객체를 출력하는데, 두번째 인자로 옵션을 넣어서 출력가능
- console.trace()
- 에러위치추적
- 타이머
- setTimeout(callback, milisec) : 주어진 milisec 이후에 콜백 함수 실행
- setInterval(callback, milisec) : 주어진 milisec마다 콜백 실행
- setImmediate(callback) : callback을 즉시 실행. 큐에있는 콜백이 실행 후 실행하도록 일단 큐에 넣는 것.
- clearTimeout(id) : setTimeout에서 반환한 아이디를 넣어, 종료시키는 것
- clearInterval(id) : 마찬가지
- clearImmediate(id) : 마찬가지
- setImmediate vs setTimeout(callback, 0)
- 우선 setTimeout(callback, 0) 는 사용하지 않는걸 권장
- 파일시스템 접근, 네트워킹 등 I/O 작업의 콜백함수 안에서 타이머를 호출하는 경우 setImmediate가 먼저 호출됨.
- __filename, __dirname
- 현재 파일의 경로나 파일명을 알아야할때 사용
- console.log(__filename); 을 통해 현재 파일 경로를 알려줌
- __dirname은 폴더까지만
- 구분자 문제가 있어 보통은 path 모듈을 함께 사용함
- module, exports, require
- module.exports 대신 exports 단독으로 쓰일수있다.
- exports.odd = “홀수입니다”
- module.exports와 exports는 같은 객체, exports.add 이런식으로 exports객체를 만들면, 이것이 module.exports로 들어감.
- 따라서 한 모듈에 두개 동시에 쓰지말자
- require.cache : 한번 require한 파일이 저장됨. 다시 require할땐 여기있는 것을 불러옴.
- require.main : 노드 실행시 첫모듈을 가리킴.
- 순환참조가 발생할 경우, 대상을 빈 객체로 만듬.
- module.exports 대신 exports 단독으로 쓰일수있다.
- this
- 최상위 스코프에 있는 this는 module.exports 객체를 가리킴.
- 함수 선언문 내부의 this는 global 객체를 가리킴
- process
- 현재 실행되고 있는 노드 프로세스의 정보를 담고 있음.
- process.env
- 시스템 환경변수
- 비밀번호나 각종 API 키를 코드에 직접입력하는 대신 .env(dotenv)에 입력함
- 코드에서 참조할땐 process.env.SECRET_KEY로 접근
- process.nextTick(callback)
- 이벤트루프가 다른 콜백함수보다 nextTick의 콜백함수를 우선으로 처리하도록 함.
- process.exit(코드)
- 0이면 정상 1이면 비정상
- 프로세스 종료
노드 내장 모듈
- os : os 관련 정보 및 에러와 신호에 대한 정보를 담음.
- path : 폴더와 파일의 경로를 쉽게 조작하도록 도와주는 모듈
- 운영체제별로 경로 구분자가 다르기에 필요함.
- path.parse(경로) : 파일 경로를 root, dir, base, ext, name으로 분리함.
- path.format(객체) : path.parse()한 객체를 파일경로로 합침.
- path.normalize(경로) : 구분자를 실수로 사용했을때 정상적인 경로로 변환함.
- url : 인터넷주소를 쉽게 조작하도록 도와주는 모듈
- 두가지방식이 있음
- WHATWG 방식의 url: 노드 버전 7부터 추가됨.
- classic url
- url 생성자 불러오기
1 2 3
const url = require("url"); const { URL } = url; // WHATWG 방식의 생성자 들어감. const myURL = new URL("https://naver.com");
- WHATWG 방식은 search부분을 searchParams로 반환하므로 편하다.
- searchParams에서 여러 메소드가 존재함. 잘 찾아서 사용.
- 두가지방식이 있음
- querystring
- const qs = require(‘querystring’);
- search 부분을 사용하기 쉽게 객체로 만드는 모듈
- querystring.parse(쿼리) : url의 query부분을 넣고 객체로 분해함.
- querystring.stringify(객체) : 분해된 query 객체를 문자열로 다시 조립함.
- crypto
- 다양한 방식의 암호화를 도와줌.
- 단방향 암호화 알고리즘 == 복호화할수없는 암호화 == 해시함수
- 해시기법 : 어떠한 문자열을 고정된 길이의 다른 문자열로 바꿔버림.
- 사용법
1 2 3 4 5
const crypto = require("crypto"); console.log( "base64", crypto.createHash("sha512").update("비밀번호").digest("base64") );
- createHash(알고리즘) : 사용할 알고리즘을 넣음.
- sha256, sha512
- update(문자열) : 비밀번호
- digest : 인코딩할 알고리즘을 넣음
- base64, hex, latin1
- 현재 주로사용하는 알고리즘 : pdkdf2, bcrypt, scrypt 등
- pdkdf2 : 기존 문자열에 salt라고 불리는 문자열을 붙인 후에, 해시 알고리즘을 반복해서 적용
1 2 3 4 5 6 7 8
//random으로 64바이트 문자열을 만듬. crypto.randomBytes(64, (err, buf) => { const salt = buf.toString("base64"); //비밀번호, salt, 반복횟수, 출력바이트, 해시알고리즘 순으로 넣음. crypto.pbkdf2("비밀번호", salt, 100000, 64, "sha512", (err, key) => { console.log("password:", key.toString("base64")); }); });
- 10만번 해도 1초밖에 안걸림. 내부적으로 스레드풀을 사용해 멀티스레딩을 하기에, 블로킹 걱정x
- 양방향암호화
- 암호화된 문자열을 복호화할수 있으며, 키를 사용함. 같은키를 사용해야 복호화가능
1 2 3 4 5 6 7 8 9 10 11
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"); //복호화 const decipher = crypto.createDecipheriv(algorithm, key, iv); let result2 = decipher.update(result, "base64", "utf8"); result2 += decipher.final("utf8");
- 사용가능한 알고리즘 목록은 crypto.getCiphers()를 호출하면 볼수있음.
- 암호화된 문자열을 복호화할수 있으며, 키를 사용함. 같은키를 사용해야 복호화가능
- util
- util.deprecate(func, message) = 함수가 deprecated처리됐음을 알림.
- util.promisify(func) : 콜백 함수를 프로미스패턴으로 바꿈. async/await 가능, then/catch가능
- worker_threads
- 노드에서 멀티스레드로 작업하는 방법
- const {Worker, isMainThread, parentPort} = require(‘worker_threads’) 로 불러옴
- isMainThread 메인 스레드에서 실행되는지 구분.
- 부모에서는 워커생성 후 worker.postMessage로 데이터를 보낼 수 있음.
- 자식에선 parentPort.on(‘message’) 로 메세지를 받음
- child_process
- 다른 프로그램을 실행하고 싶거나 명령어를 수행하고 싶을때 사용함.
- 이 모듈을 통해 다른 언어의 코드(파이썬 등)을 실행하고 결과값을 받을 수 있음.
- exec : 셀을 실행해서 명령어를 수행
- spawn : 새로운 프로세스를 띄우면서 명령어를 실행함.
- 기타모듈들
- assert: 값을 비교하여 프로그램이 제대로 동작하는지 테스트하는 데 사용합니다.
- dns: 도메인 이름에 대한 IP 주소를 얻어내는 데 사용합니다.
- net: HTTP보다 로우 레벨인 TCP나 IPC 통신을 할 때 사용합니다.
- string_decoder: 버퍼 데이터를 문자열로 바꾸는 데 사용합니다.
- tls: TLS와 SSL에 관련된 작업을 할 때 사용합니다.
- tty: 터미널과 관련된 작업을 할 때 사용합니다.
- dgram: UDP와 관련된 작업을 할 때 사용합니다.
- v8: V8 엔진에 직접 접근할 때 사용합니다.
- vm: 가상 머신에 직접 접근할 때 사용합니다.
파일시스템 접근하기
- fs 모듈. 파일시스템에 접근하는 모듈. 파일생성/삭제/read/write 가능
- read : fs.readFile(‘경로’, callback)
1 2 3 4 5 6 7 8
const fs = require("fs"); //require('fs').promises; 로하면 프로미스형태로 사용가능. fs.readFile("./readme.txt", (err, data) => { if (err) { throw err; } console.log(data); // 읽은 데이터는 버퍼형식으로 반환됨. console.log(data.toString()); // 읽을 수 없으므로 toString()으로 변환 시켜줌 });
write : fs.writeFile(경로, 내용);
동기 메소드와 비동기메소드
- 동기메소드 : readFileSync, writeFileSync / 콜백함수대신에 직접 리턴값을 받아옴.
- 비동기메소드 : readFile, writeFile / 명령이 실행되면 백그라운드에 해당파일을 읽으라고 요청하고 다음으로 넘어감.
- 동기로 하게되면 요청이 처리되는 동안 메인스레드는 놀게됨.
- 동기로 하되, 요청이 백그라운드에서 처리되도록 하려면, readFile을 사용하되 콜백안에서 다시 readFile을 실행하도록 한다. 이때 콜백지옥은 promises로 실행하면 해결할 수 있다.
버퍼와 스트림
- 노드는 파일을 일을때 메모리에 파일크기만큼 공간을 마련해두며 파일 데이터를 메모리에 저장한 뒤 사용자가 조작할 수 있도록 함.
- 이때 메모리에 저장된 데이터가 버퍼
- Buffer 클래스
- 버퍼를 직접 다룰 수 있는 클래스
1
const buffer = Buffer.from("저를 버퍼로 바꾸세요");
- .toString() : 문자열로 바꿈
- .length : 길이
- .concat() : 합치기
- .alloc(바이트) : 빈 버퍼를 생성.
- 버퍼를 직접 다룰 수 있는 클래스
- 버퍼의 단점 : 100MB 파일이 10개 필요하면 1GB 공간이 필요 -> 비효율 -> 따라서 버퍼의 크기를 작게만든 후 여러번으로 나눠보내는 방식이 탄생 -> 스트림
fs.createReadStream(path, option), fs.writeReadStream(path, option)
- 나눠진 조각은 chunk라고 부름
- option
- highWaterMark : 버퍼의 크기를 정함.
- readStream을 반환. 따라서 다음같이 작성
1 2 3 4 5 6 7 8 9
const readStream = fs.createReadStream("./readme3.txt", { highWaterMark: 16, }); const data = []; readStream.on("data", (chunk) => { data.push(chunk); console.log("data :", chunk, chunk.length); });
- 이벤트리스너
- data : 읽기가 시작되면 방생
- error : 읽다가 에러발생시
- end : 다 읽으면
기타 fs메소드
- fs.access(경로, 옵션, 콜백) : 접근할 수 있는지
- fs.mkdir(경로, 콜백)
- fs.open(경로, 옵션, 콜백)
- fs.rename(기존경로, 새경로, 콜백)
- fs.readdir(경로, 콜백)
- fs.unlink(경로, 콜백) : 파일을 지움
- fs.rmdir(경로, 콜백) : 폴더를 지움
- fs.watch(경로, 콜백) : 파일/폴더의 변경사항을 감시할 수 있음
- change 이벤트는 두번 발생할 수 있으므로 주의
- 스레드풀
- 기본적인 스레드풀의 개수는 4개.
- 처음 4개의 작업이 동시에 실행되고, 후에 4개가 실행
- 스레드풀의 개수는 변경가능. 나중에 찾아서 함
- 이때 스레드풀의 개수는 프로세서 코어의 개수와 같거나 많게 두어야 효과가 나옴
이벤트 이해하기
- .on(‘data’, 콜백) 이렇게 이벤트리스너를 등록할 수 있음.
- .addListener(이벤트, 콜백) 이것도 가능
- .emit(이벤트) : 이벤트 호출
- .removeListener(이벤트, 콜백) : 삭제
- .removeAllListener(이벤트) : 이 이벤트에 연결된 모든 콜백 삭제
- .listenerCount(이벤트)
예외처리
- try-catch
- 노드내장 모듈의 에러는 실행중인 프로세스를 멈추지 않음. 에러로그를 기록함.
- throw : 발생시 노드 프로세스 중지. 반드시 try-catch로 잡아야함
- 프로미스의 에러는 catch하지 않아도 알아서 처리됨. 그래도 일단 붙여라
- 예측불가능한 에러
- process.on(‘uncaughtException’, (err) => { console.log(‘예기치못한 에러’, err); }); 이렇게 이벤트로 잡으면 됨. 이건 최후의 수단으로 사용함. 에러를 기록한 후