[js] 노드 교과서 개정판 스터디
서버 : 네트워크를 통해 클라이언트에 정보나 서비스를 제공하는 컴퓨터 또는 프로그램
서버라고 해서 요청에 대한 응답만 하는 것은 아니다. 다른 서버에 요청을 보낼 수도 있다. 이때는 요청을 보낸 서버가 클라이언트 역할을 한다.
서버는 클라이언트의 요청에 대해 응답. 응답으로 항상 Yes를 해야 하는 것은 아니고, No를 할 수도 있다. 어떤 사이트로부터 차단당했다면 그 사이트의 서버는 여러분의 요청에 매번 No를 응답
클라이언트: 요청을 보내는 주체로 브라우저일 수도 있고, 데스크톱 프로그램일 수도 있고, 모바일 앱일 수도 있고, 다른 서버에 요청을 보내는 서버
Node.js란
- Node.js란
- Node.jsⓇ는 Chrome V8 Javascript 엔진으로 빌드된 Javascript 런타임
- 자바스크립트 런타임
- 런타임은 특정 언어로 프로그램 실행할 수 있는 환경
- 노드는 자바스크립트 프로그램을 컴퓨터에서 실행가능
- 즉, 자바스크립트 실행기라고 봐도 무방.
- 자바스크립트를 전혀 모른다면 노드 사용이 불가
- 런타임은 특정 언어로 프로그램 실행할 수 있는 환경
이벤트 기반
이벤트 기반 : 이벤트가 발생할 때 미리 지정해둔 작업을 수행하는 방식을 의미. 이벤트로는 클릭이나 네트워크 요청 등
이벤트 기반 시스템에서는 특정 이벤트가 발생할 때 무엇을 할지 미리 등록해두어야 함. 이를 이벤트 리스너(event listener)에 콜백(callback) 함수를 등록
이벤트 기반 모델에서는 이벤트 루프(event loop)라는 개념이 등장. 여러 이벤트가 동시에 발생했을 때 어떤 순서로 콜백 함수를 호출할지를 이벤트 루프가 판단
노드는 자바스크립트 코드의 맨 위부터 한 줄씩 실행. 함수 호출 부분을 발견했다면 호출한 함수를 호출 스택(call stack)에 넣음.
function first() {
second();
console.log('첫 번째');
}
function second() {
third();
console.log('두 번째');
}
function third() {
console.log('세 번째');
}
first();
first 함수가 제일 먼저 호출되고, 그 안의 second 함수가 호출된 뒤, 마지막으로 third 함수가 호출. 실행은 호출된 순서와 반대로 실행이 완료
결과는
세 번째
두 번째
첫 번째
function run() {
console.log('3초 후 실행');
}
console.log('시작');
setTimeout(run, 3000);
console.log('끝');
결과
시작
끝
3초 후 실행
콘솔 결과는 쉽게 예측할 수 있지만, 호출 스택으로 설명하기는 힘듬
위를 알기 위해선 이벤트 루프, 태스크 큐(task queue), 백그라운드(background)를 알아야 함.
이벤트 루프: 이벤트 발생 시 호출할 콜백 함수들을 관리하고, 호출된 콜백 함수의 실행 순서를 결정하는 역할을 담당. 노드가 종료될 때까지 이벤트 처리를 위한 작업을 반복하므로 루프(loop)라고 부른다.
• 백그라운드: setTimeout 같은 타이머나 이벤트 리스너들이 대기하는 곳. 자바스크립트가 아닌 다른 언어로 작성된 프로그램이라고 봐도 됨. 여러 작업이 동시에 실행될 수 있다.
• 태스크 큐: 이벤트 발생 후, 백그라운드에서는 태스크 큐로 타이머나 이벤트 리스너의 콜백 함수를 보낸다. 정해진 순서대로 콜백들이 줄을 서 있으므로 콜백 큐라고도 부른다. 콜백들은 보통 완료된 순서대로 줄을 서 있지만 특정한 경우에는 순서가 바뀌기도 한다.
논 블로킹 I/O
이벤트 루프를 잘 활용하면 오래 걸리는 작업을 효율적으로 처리할 수 있다. 작업에는 두 가지 종류가 있는데, 동시에 실행될 수 있는 작업과 동시에 실행될 수 없는 작업이 있다.
기본적으로 작성한 자바스크립트 코드는 동시에 실행될 수 없습니다.
하지만 자바스크립트상에서 돌아가는 것이 아닌 I/O 작업 같은 것은 동시에 처리될 수 있다
I/O는 입력(Input)/출력(Output)을 의미한다.
파일 시스템 접근(파일 읽기, 파일 쓰기, 폴더 만들기 등)이나 네트워크를 통한 요청 같은 작업이 I/O의 일종이다.
이러한 작업을 할 때 노드는 논 블로킹 방식으로 처리하는 방법을 제공한다.
논 블로킹이란 이전 작업이 완료될 때까지 대기하지 않고 다음 작업을 수행함을 뜻한다.
반대로 블로킹은 이전 작업이 끝나야만 다음 작업을 수행하는 것을 의미.
블로킹 방식보다 논 블로킹 방식이 같은 작업을 더 짧은 시간에 처리할 수 있음을 알 수 있다. 다만 작업들이 모두 동시에 처리될 수 있는 작업이라는 전제가 있다.
작업 순서에 따라 성능이 크게 달라진다. 동시에 처리될 수 있는 I/O 작업이라도 논 블로킹 방식으로 코딩하지 않으면 의미가 퇴색되므로 논 블로킹 방식으로 코딩하는 습관을 들여야 한다.
블로킹 방식 예제
function longRunningTask() {
// 오래 걸리는 작업
console.log('작업 끝');
}
console.log('시작');
longRunningTask();
console.log('다음 작업');
결과
시작
작업 끝
다음 작업
작업 완료 전 다음 작업 호출되지 않음
논 블로킹 예제
function longRunningTask() {
// 오래 걸리는 작업
console.log('작업 끝');
}
console.log('시작');
setTimeout(longRunningTask, 0);
console.log('다음 작업');
결과
시작
다음 작업
작업 끝
setTimeout(콜백, 0)은 코드를 논 블로킹으로 만들기 위해 사용하는 기법 중 하나다. 사실 노드에서는 setTimeout(콜백, 0) 대신 다른 방식을 주로 사용하긴 하나 setTimeOut도 논 블로킹 중 하나.
이벤트 루프를 이해했다면, setTimeout의 콜백 함수인 longRunningTask가 태스크 큐로 보내지므로 순서대로 실행되지 않는다는 것을 알 수 있다. 다음 작업이 먼저 실행된 후, 오래 걸리는 작업이 완료된다.
아무리 논 블로킹 방식으로 코드를 작성하더라도 코드가 전부 작성한 것이라면 전체 소요 시간이 짧아지지는 않는다. 코드는 서로 동시에 실행되지 않기 때문입니다. 단순히 실행 순서만 바뀔 뿐이다.
위 예제에서는 console.log(‘다음 작업’)과 longRunningTask 모두 작성한 코드입니다.
그렇다고 I/O 작업이 없다고 해서 논 블로킹이 의미가 없는 것은 아니다.
오래 걸리는 작업을 처리해야 하는 경우, 논 블로킹을 통해 실행 순서를 바꿔줌으로써 그 작업 때문에 간단한 작업들이 대기하는 상황을 막을 수 있다는 점에서 의의가 있다.
또한, 논 블로킹과 동시가 같은 의미가 아니다. 동시성은 동시 처리가 가능한 작업을 논 블로킹 처리해야 얻을 수 있다.
(참고)
setTimeout(콜백, 0)
밀리초를 0으로 설정했으므로 바로 실행되는 것으로 착각할 수 있다. 하지만 브라우저와 노드에서는 기본적인 지연 시간이 있으므로 바로 실행되지 않는다. HTML5 브라우저에서는 4ms, 노드에서는 1ms의 지연 시간이 있다.
노드에서는 동기와 블로킹이 유사하고 비동기와 논 블로킹이 유사하다고만 알아두면 된다.
싱글 스레드
싱글 스레드입니다. 싱글 스레드란 스레드가 하나뿐이라는 것을 의미한다. 자바스크립트 코드가 동시에 실행될 수 없는 이유이기도 한다.
스레드를 이해하기 위해서는 프로세스부터 알아야 한다. 프로세스와 스레드의 차이는 다음과 같다
• 프로세스는 운영체제에서 할당하는 작업의 단위. 노드나 웹 브라우저 같은 프로그램은 개별적인 프로세스다. 프로세스 간에는 메모리 등의 자원을 공유하지 않는다.
• 스레드는 프로세스 내에서 실행되는 흐름의 단위. 프로세스는 스레드를 여러 개 생성해 여러 작업을 동시에 처리할 수 있다. 스레드들은 부모 프로세스의 자원을 공유합니다. 같은 주소의 메모리에 접근 가능하므로 데이터를 공유할 수 있다.
노드가 싱글 스레드라는 말을 들어봤을 것이다.
하지만 엄밀히 말하면 싱글 스레드로 동작하지는 않는다.
노드를 실행하면 먼저 프로세스가 하나 생성된다.
그리고 그 프로세스에서 스레드들을 생성하는데, 이때 내부적으로 스레드를 여러 개 생성한다.
그중에서 여러분이 직접 제어할 수 있는 스레드는 하나뿐이다.
그래서 흔히 노드가 싱글 스레드라고 여겨지는 것이다.
스레드를 작업을 처리하는 일손으로 표현하기도 하는데, 하나의 스레드만 직접 조작할 수 있으므로 일손이 하나인 셈이다.
요청이 많이 들어오면 한 번에 하나씩 요청을 처리한다. 블로킹이 심하게 일어나는 작업을 처리하지만 않는다면 스레드 하나로도 충분하다.
블로킹이 발생할 것 같은 경우에는 논 블로킹 방법으로 대기 시간을 최대한 줄이는 것이다.
다만 멀티 스레드 방식으로 프로그래밍하는 것은 상당히 어려우므로 멀티 프로세싱 방식을 대신 사용한다.
I/O 요청에는 멀티 프로세싱이 더 효율적이기도 하다.
멀티 스레딩과 멀티 프로세싱 비교
1.2 서버로서의 노드
노드는 기본적으로 싱글 스레드, 논 블로킹 모델을 사용하므로(자바스크립트 언어의 특성이기도 함), 노드 서버 또한 동일한 모델일 수밖에 없다. 따라서 노드 서버의 장단점은 싱글 스레드, 논 블로킹 모델의 장단점과 크게 다르지 않다.
서버에는 기본적으로 I/O 요청이 많이 발생하므로, I/O 처리를 잘하는 노드를 서버로 사용하면 좋다. 노드는 (여러분이 논 블로킹 방식으로 코드를 작성했다는 가정하에) libuv 라이브러리를 사용하여 I/O 작업을 논 블로킹 방식으로 처리.
스레드 하나가 많은 수의 I/O를 혼자서도 감당할 수 있다. 하지만 노드는 CPU 부하가 큰 작업에는 적합하지 않다.
사람이 작성하는 코드는 모두 스레드 하나에서 처리된다. 그러나 코드가 CPU 연산을 많이 요구하면 스레드 하나가 혼자서 감당하기 어렵게 된다.
이 같은 특성으로 인해 노드는
개수는 많지만 크기는 작은 데이터를 실시간으로 주고받는 데 적합하다. 네트워크나 데이터베이스, 디스크 작업 같은 I/O에 특화되어 있기 때문. 실시간 채팅 애플리케이션이나 주식 차트, JSON 데이터를 제공하는 API 서버가 노드를 많이 사용한다.
노드에는 웹 서버가 내장되어 있어 입문자가 쉽게 접근할 수 있다. 노드 외의 서버를 개발하다 보면 아파치(Apache), nginx, IIS처럼 별도의 웹 서버를 설치해야 하는 경우가 많다.
노드는 생산성은 매우 좋지만, Go처럼 비동기에 강점을 보이는 언어나 nginx처럼 정적 파일 제공, 로드 밸런싱에 특화된 웹 서버에 비해서는 속도가 느리다. 그렇긴 해도 극단적인 성능이 필요하지 않다면 이러한 단점은 노드의 생산성으로 어느 정도 극복할 수 있다
자바스크립트를 사용함으로써 얻을 수 있는 소소한 장점도 있다. 요즘은 XML 대신 JSON을 사용해서 데이터를 주고받는데, JSON이 자바스크립트 형식이므로 노드에서는 쉽게 처리할 수 있다.
프로미스
ES2015부터는 자바스크립트와 노드의 API들이 콜백 대신 프로미스(Promise) 기반으로 재구성되며, 악명 높은 콜백 지옥(callback hell) 현상을 극복했다는 평가를 받는다. 프로미스는 반드시 알아두어야 하는 객체이므로 이 책뿐만 아니라 다른 자료들을 참고해서라도 반드시 숙지해야 ㅏㄴㅎ다.
const condition = true; // true면 resolve, false면 reject
const promise = new Promise((resolve, reject) => {
if (condition) {
resolve('성공');
} else {
reject('실패');
}
});
// 다른 코드가 들어갈 수 있음
promise
.then((message) => {
console.log(message); // 성공(resolve)한 경우 실행
})
.catch((error) => {
console.error(error); // 실패(reject)한 경우 실행
})
.finally(() => { // 끝나고 무조건 실행
console.log('무조건');
});
new Promise로 프로미스를 생성할 수 있으며, 그 내부에 resolve와 reject를 매개변수로 갖는 콜백 함수를 넣는다. 이렇게 만든 promise 변수에 then과 catch 메서드를 붙일 수 있다. 프로미스 내부에서 resolve가 호출되면 then이 실행되고, reject가 호출되면 catch가 실행됩니다. finally 부분은 성공/실패 여부와 상관없이 실행된다.
REPL
자바스크립트는 스크립트 언어이므로 미리 컴파일을 하지 않아도 즉석에서 코드를 실행할 수 있다.
노드도 비슷한 콘솔을 제공하는데, 입력한 코드를 읽고(Read), 해석하고(Eval), 결과물을 반환하고(Print), 종료할 때까지 반복한다(Loop)고 해서 REPL(Read Eval Print Loop)이라고 부른다.
노드는 코드를 모듈로 만들 수 있다는 점에서 브라우저의 자바스크립트와 다르다.
모듈이란 특정한 기능을 하는 함수나 변수들의 집합이다.
예를 들면 수학에 관련된 코드들만 모아서 모듈을 하나 만들 수 있다.
모듈은 자체로도 하나의 프로그램이면서 다른 프로그램의 부품으로도 사용할 수 있다.
ES2015 모듈
이 문법은 노드의 모듈 시스템과 조금 다르다. func.js를 ES2015 모듈 스타일로 바꾼게 mjs
func.mjs
import { odd, even } from './var';
function checkOddOrEven(num) {
if (num % 2) { // 홀수면
return odd;
}
return even;
}
export default checkOddOrEven;
require와 module.exports가 import, export default로 바뀌었다.
상당한 부분에서 차이가 있으므로 단순히 글자만 바꿔서는 제대로 동작하지 않을 수 있다.
위 예제에서는 require를 import로, module.exports를 export default로 바꾸기만 하면 된다.
노드에서도 9 버전부터 ES2015의 모듈 시스템을 사용할 수 있다.
하지만 파일의 확장자를 mjs로 지정해야 하는 제한이 있다.
mjs 확장자 대신 js 확장자를 사용하면서 ES2015 모듈을 사용하려면 5장에서 배울 package.json에 type: “module” 속성을 넣으면 된다.
방금 썼던 require 함수나 module 객체는 따로 선언하지 않았음에도 사용할 수 있다.
이것이 어떻게 가능한가? 바로 노드에서 기본적으로 제공하는 내장 객체이기 때문이다.
노드 내장 객체 알아보기
노드에서는 기본적인 내장 객체와 내장 모듈(3.5절 참조)을 제공한다.
따로 설치하지 않아도 바로 사용할 수 있으며, 브라우저의 window 객체와 비슷하다고 보면 된다.
global
먼저 global 객체 브라우저의 window와 같은 전역 객체이다.
전역 객체이므로 모든 파일에서 접근할 수 있다.
또한, window.open 메서드를 그냥 open으로 호출할 수 있는 것처럼 global도 생략할 수 있다.
이전 절에서 사용했던 require 함수도 global.require에서 global이 생략된 것이다.
console
console도 노드에서는 window 대신 global 객체 안에 들어 있으며, 브라우저에서의 console과 거의 비슷하다.
console 객체는 보통 디버깅을 위해 사용한다.
개발하면서 변수에 값이 제대로 들어 있는지 확인하기 위해 사용하고, 에러 발생 시 에러 내용을 콘솔에 표시하기 위해 사용하며, 코드 실행 시간을 알아보려고 할 때도 사용한다.
대표적으로 console.log 메서드
타이머
타이머 기능을 제공하는 함수인 setTimeout, setInterval, setImmediate는 노드에서 window 대신 global 객체 안에 들어 있다.
setTimeout과 setInterval은 웹 브라우저에서도 자주 사용된다.
• setTimeout(콜백 함수, 밀리초): 주어진 밀리초(1,000분의 1초) 이후에 콜백 함수를 실행합니다.
• setInterval(콜백 함수, 밀리초): 주어진 밀리초마다 콜백 함수를 반복 실행합니다.
• setImmediate(콜백 함수): 콜백 함수를 즉시 실행합니다.
이 타이머 함수들은 모두 아이디를 반환한다. 아이디를 사용하여 타이머를 취소할 수 있다.
• clearTimeout(아이디): setTimeout을 취소합니다.
• clearInterval(아이디): setInterval을 취소합니다.
• clearImmediate(아이디): setImmediate를 취소합니다.
__filename, __dirname
노드에서는 파일 사이에 모듈 관계가 있는 경우가 많으므로 때로는 현재 파일의 경로나 파일명을 알아야 한다.
노드는 __filename, __dirname이라는 키워드로 경로에 대한 정보를 제공한다.
파일에 __filename과 __dirname을 넣어두면 실행 시 현재 파일명과 현재 파일 경로로 바뀐다.
module, exports, require
module.exports로 한 번에 대입하는 대신, 각각의 변수를 exports 객체에 하나씩 넣었습니다.
동일하게 동작하는 이유는 module.exports와 exports가 같은 객체를 참조하기 때문입니다.
실제로 console.log(module.exports === exports)를 하면 true가 나옵니다.
따라서 exports 객체에 add 함수를 넣으면 module.exports에도 add 함수가 들어갑니다.
module.exports에는 어떤 값이든 대입해도 되지만, exports에는 반드시 객체처럼 속성명과 속성값을 대입해야 한다.
exports에 다른 값을 대입하면 객체의 참조 관계가 끊겨 더 이상 모듈로 기능하지 않는다.
exports와 module.exports에는 참조 관계가 있으므로 한 모듈에 exports 객체와 module.exports를 동시에 사용하지 않는 것이 좋다.
노드에서 this는 무엇인가
다른 부분은 브라우저의 자바스크립트와 동일하지만 최상위 스코프에 존재하는 this는 module.exports(또는 exports 객체)를 가리킨다. 또한, 함수 선언문 내부의 this는 global 객체를 가리킨다.
process.env
process.env를 입력하면 매우 많은 정보가 출력됩니다. 자세히 보면 이 정보들이 시스템의 환경 변수임을 알 수 있다.
시스템 환경 변수는 노드에 직접 영향을 미치기도 한다.
대표적인 것으로 UV_THREADPOOL_SIZE와 NODE_OPTIONS가 있습니다.
process.nextTick(콜백)
process.exit(코드)
process
process 객체는 현재 실행되고 있는 노드 프로세스에 대한 정보를 담고 있다.
$ node
> process.version
v14.0.0 // 설치된 노드의 버전입니다.
> process.arch
x64 // 프로세서 아키텍처 정보입니다. arm, ia32 등의 값일 수도 있습니다.
> process.platform
win32 // 운영체제 플랫폼 정보입니다. linux나 darwin, freebsd등의 값일 수도 있습니다.
> process.pid
14736 // 현재 프로세스의 아이디입니다. 프로세스를 여러 개 가질 때 구분할 수 있습니다.
> process.uptime()
199.36 // 프로세스가 시작된 후 흐른 시간입니다. 단위는 초입니다.
> process.execPath
C:\\Program Files\\nodejs\\node.exe // 노드의 경로입니다.
> process.cwd()
C:\\Users\\zerocho // 현재 프로세스가 실행되는 위치입니다.
> process.cpuUsage()
{ user: 390000, system: 203000 } // 현재 cpu 사용량입니다.
노드 내장 모듈 사용하기
os
먼저 os 모듈입니다. 웹 브라우저에 사용되는 자바스크립트는 운영체제의 정보를 가져올 수 없지만, 노드는 os 모듈에 정보가 담겨 있어 정보를 가져올 수 있습니다.
process 객체와 겹치는 부분도 조금 보입니다. 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(): 전체 메모리 용량을 보여줍니다.
path
폴더와 파일의 경로를 쉽게 조작하도록 도와주는 모듈입니다.
path 모듈이 필요한 이유 중 하나는 운영체제별로 경로 구분자가 다르기 때문입니다.
크게 윈도 타입과 POSIX 타입으로 구분됩니다. POSIX는 유닉스 기반의 운영체제들을 의미하며 맥과 리눅스가 속해 있습니다.
• path.sep: 경로의 구분자입니다. 윈도는 \, POSIX는 /입니다.
• path.delimiter: 환경 변수의 구분자입니다. process.env.PATH를 입력하면 여러 개의 경로가 이 구분자로 구분되어 있습니다. 윈도는 세미콜론(;)이고, POSIX는 콜론(:)입니다.
• path.dirname(경로): 파일이 위치한 폴더 경로를 보여줍니다.
• path.extname(경로): 파일의 확장자를 보여줍니다.
• path.basename(경로, 확장자): 파일의 이름(확장자 포함)을 표시합니다. 파일의 이름만 표시하고 싶다면 basename의 두 번째 인수로 파일의 확장자를 넣으면 됩니다.
• path.parse(경로): 파일 경로를 root, dir, base, ext, name으로 분리합니다.
• path.format(객체): path.parse()한 객체를 파일 경로로 합칩니다.
• path.normalize(경로): /나 \를 실수로 여러 번 사용했거나 혼용했을 때 정상적인 경로로 변환합니다.
• path.isAbsolute(경로): 파일의 경로가 절대경로인지 상대경로인지를 true나 false로 알립니다.
• path.relative(기준경로, 비교경로): 경로를 두 개 넣으면 첫 번째 경로에서 두 번째 경로로 가는 방법을 알립니다.
• path.join(경로, …): 여러 인수를 넣으면 하나의 경로로 합칩니다. 상대경로인 ..(부모 디렉터리)과 .(현 위치)도 알아서 처리합니다.
• path.resolve(경로, …): path.join()과 비슷하지만 차이가 있습니다. 차이점은 다음에 나오는 Note에서 설명합니다.
join과 resolve의 차이
path.join과 path.resolve 메서드는 비슷해 보이지만 동작 방식이 다릅니다. /를 만나면 path.resolve는 절대경로로 인식해서 앞의 경로를 무시하고, path.join은 상대경로로 처리합니다. 코드로 보면 이해하기 쉽습니다.
어떤 때 \를 사용하고 어떤 때 \를 사용하나요?
콘솔 결과를 보면 어떤 때는 \를 사용하고, 어떤 때는 그냥 \를 사용하여 윈도 경로를 표시했습니다. 기본적으로 경로는 \ 하나를 사용해서 표시합니다. 하지만 자바스크립트 문자열에서는 \가 특수 문자이므로 \를 두 개 붙여 경로를 표시해야 합니다. 예를 들어 \n은 자바스크립트 문자열에서 줄바꿈이라는 뜻이므로. C:\node와 같은 경로에서 의도하지 않은 오류가 발생할 수 있습니다.
이때는 C:\node처럼 표시해야 합니다.
path 모듈은 위와 같은 경우에 발생하는 문제를 알아서 처리합니다. 이는 윈도에서 path 모듈이 꼭 필요한 이유이기도 합니다.
상대경로와 절대경로
절대경로는 루트 폴더(윈도의 C:\나 POSIX의 /)나 노드 프로세스가 실행되는 위치가 기준이 됩니다.
상대경로는 현재 파일이 기준이 됩니다. 현재 파일과 같은 경로면 점 하나(.)를, 현재 파일보다 한 단계 상위 경로면 점 두 개(..)를 사용해 표현합니다.
C:\users\zerocho\path.js에서 C:\로 가고 싶다면 절대경로에서는 그냥 C:\를 입력하면 됩니다. 하지만 상대경로에서는 ....을 해야 두 디렉터리 위로 올라가 C:\가 됩니다.
url
인터넷 주소를 쉽게 조작하도록 도와주는 모듈입니다. url 처리에는 크게 두 가지 방식이 있습니다.
url.js
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 모듈 안에 URL 생성자가 있습니다. 이 생성자에 주소를 넣어 객체로 만들면 주소가 부분별로 정리됩니다. 이 방식이 WHATWG의 url입니다. WHATWG에만 있는 username, password, origin, searchParams 속성이 존재합니다.
기존 노드 방식에서는 두 메서드를 주로 사용합니다.
• url.parse(주소): 주소를 분해합니다. WHATWG 방식과 비교하면 username과 password 대신 auth 속성이 있고, searchParams 대신 query가 있습니다.
• url.format(객체): WHATWG 방식 url과 기존 노드의 url을 모두 사용할 수 있습니다. 분해되었던 url 객체를 다시 원래 상태로 조립합니다.
노드의 url은 취향에 따라 사용하면 되지만, 노드의 url 형식을 꼭 사용해야 하는 경우가 있습니다.
host 부분 없이 pathname 부분만 오는 주소인 경우(예시: /book/bookList.apsx)에는 WHATWG 방식이 처리할 수 없습니다.(url 모듈 안에 URL 생성자가 있습니다. 이 생성자에 주소를 넣어 객체로 만들면 주소가 부분별로 정리됩니다. 이 방식이 WHATWG의 url입니다. WHATWG에만 있는 username, password, origin, searchParams 속성이 존재합니다.)
4장에서 서버를 만들 때는 host 부분 없이 pathname만 오는 주소를 보게 될 것입니다.
WHATWG 방식은 search 부분을 searchParams라는 특수한 객체로 반환하므로 유용합니다. search 부분은 보통 주소를 통해 데이터를 전달할 때 사용됩니다. search는 물음표(?)로 시작하고, 그 뒤에 키=값 형식으로 데이터를 전달합니다. 여러 키가 있을 경우에는 &로 구분합니다.
• getAll(키): 키에 해당하는 모든 값들을 가져옵니다. category 키에는 nodejs와 javascript라는 두 가지 값이 들어 있습니다.
• get(키): 키에 해당하는 첫 번째 값만 가져옵니다.
• has(키): 해당 키가 있는지 없는지를 검사합니다.
• keys(): searchParams의 모든 키를 반복기(iterator)(ES2015 문법) 객체로 가져옵니다.
• values(): searchParams의 모든 값을 반복기 객체로 가져옵니다.
• append(키, 값): 해당 키를 추가합니다. 같은 키의 값이 있다면 유지하고 하나 더 추가합니다.
• set(키, 값): append와 비슷하지만, 같은 키의 값들을 모두 지우고 새로 추가합니다.
• delete(키): 해당 키를 제거합니다.
• toString(): 조작한 searchParams 객체를 다시 문자열로 만듭니다. 이 문자열을 search에 대입하면 주소 객체에 반영됩니다.
query 같은 문자열보다 searchParams가 유용한 이유는 query의 경우 다음에 배우는 querystring 모듈을 한 번 더 사용해야 하기 때문입니다.
querystring
WHATWG 방식의 url 대신 기존 노드의 url을 사용할 때, search 부분을 사용하기 쉽게 객체로 만드는 모듈입니다.
querystring.js
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 객체를 문자열로 다시 조립합니다.
crypto
다양한 방식의 암호화를 도와주는 모듈입니다. 몇 가지 메서드는 익혀두면 실제 서비스에도 적용할 수 있어 정말 유용합니다.
암호화는 멀티스레드로 돌아감. 비밀번호는 암호화가 아니라 해시라 하는데 암호화라하는데 엄밀히 말하면 해시. 해시는 평문을 암호같이 만드는데 해시는 다시 되돌리기는 어렵다.
해시의 특징은 언제나 해쉬화 하면 같은값이 된다. 홍길동을 해시화 하면 서버에서 해쉬화 하면 사람이 되는데 홍길동 입력시 사람이 됨. 홍길동은 서버에 아예 존재하지 않는다. 만약 홍길동이 서버에 존재하면 해킹 바로 돌림. 물론 브루트포스 이런거로 밀면 잡을수야 있겠지만 이러면 매우 오래걸림.
비밀번호 해쉬화 하는데 다양한 알고리즘이 있음. bcrpyt는 지원을 안함(노드에서) salt는 해독 더 어렵게 하기 위함 결국은 다 해쉬.
양방향은
만약 바보 보내면 진짜 메시지 궁금하면 다시 봐야됨. 이게 대칭형 암호화.
대칭형 암호화는 바보를 쓰고 홍길동라는 키 쓰면 상대방도 홍길동라는 키 써야 다시 돌림. 같은 키를 써야됨(당연한거)
근데 이 키만 훔쳐지면 해킹이 가능. 그래서 위험. 프론트와 서버이런건 또 안되는게 프론트에선 다 노출되는데(개발자 도구라던가) 서버에서 준 키가 같아지면 바로 노출이 된다.
crpytciperiv는 초기화 벡터공격 이런게 있는데 iv 16바이트를 쓰고 뭐 어쩌구 이런데 암호학 수업을 들어야. kms
단방향 암호화
비밀번호는 보통 단방향 암호화 알고리즘을 사용해서 암호화합니다. 단방향 암호화란 복호화할 수 없는 암호화 방식을 뜻합니다.
복호화는 암호화된 문자열을 원래 문자열로 되돌려놓는 것을 의미합니다. 즉, 단방향 암호화는 한 번 암호화하면 원래 문자열을 찾을 수 없습니다. 복호화할 수 없으므로 암호화라고 표현하는 대신 해시 함수라고 부르기도 합니다.
복호화할 수 없는 암호화가 왜 필요한지 의문이 들 수도 있습니다. 하지만 생각해보면 고객의 비밀번호는 복호화할 필요가 없습니다. 먼저 고객의 비밀번호를 암호화해서 데이터베이스에 저장합니다. 그리고 로그인할 때마다 입력받은 비밀번호를 같은 암호화 알고리즘으로 암호화한 후, 데이터베이스의 비밀번호와 비교하면 됩니다. 원래 비밀번호는 어디에도 저장되지 않고 암호화된 문자열로만 비교하는 것입니다.
단방향 암호화 알고리즘은 주로 해시 기법을 사용합니다. 해시 기법이란 어떠한 문자열을 고정된 길이의 다른 문자열로 바꿔버리는 방식입니다. 예를 들면 abcdefgh라는 문자열을 qvew로 바꿔버리고, ijklm이라는 문자열을 zvsf로 바꿔버리는 겁니다. 입력 문자열의 길이는 다르지만, 출력 문자열의 길이는 네 자리로 고정되어 있습니다.
비밀번호라는 문자열을 해시를 사용해 바꿔봤습니다.
• createHash(알고리즘): 사용할 해시 알고리즘을 넣습니다. md5, sha1, sha256, sha512 등이 가능하지만, md5와 sha1은 이미 취약점이 발견되었습니다. 현재는 sha512 정도로 충분하지만, 나중에 sha512마저도 취약해지면 더 강화된 알고리즘으로 바꿔야 합니다.
• update(문자열): 변환할 문자열을 넣습니다.
• digest(인코딩): 인코딩할 알고리즘을 넣습니다. base64, hex, latin1이 주로 사용되는데, 그중 base64가 결과 문자열이 가장 짧아 애용됩니다. 결과물로 변환된 문자열을 반환합니다.
언젠가는 sha512의 취약점도 발견될 것입니다. 그렇게 된다면 더 강력한 알고리즘인 sha3으로 이전하면 됩니다.
현재는 주로 pbkdf2나 bcrypt, scrypt라는 알고리즘으로 비밀번호를 암호화하고 있습니다. 그중 노드에서 지원하는 pbkdf2에 대해 알아보겠습니다. pbkdf2는 간단히 말하면 기존 문자열에 salt라고 불리는 문자열을 붙인 후 해시 알고리즘을 반복해서 적용하는 겁니다.
양방향 암호화
이번에는 양방향 대칭형 암호화를 알아보겠습니다. 암호화된 문자열을 복호화할 수 있으며, 키(열쇠)라는 것이 사용됩니다. 대칭형 암호화에서 암호를 복호화하려면 암호화할 때 사용한 키와 같은 키를 사용해야 합니다.
다음은 노드로 양방향 암호화하는 방법입니다. 하지만 다음 코드를 완벽하게 이해하려면 암호학을 추가로 공부해야 합니다.
• crypto.createCipheriv(알고리즘, 키, iv): 암호화 알고리즘과 키, iv를 넣습니다. 암호화 알고리즘은 aes-256-cbc를 사용했으며, 다른 알고리즘을 사용해도 됩니다. aes-256-cbc 알고리즘의 경우 키는 32바이트여야 하고, iv는 16바이트여야 합니다. iv는 암호화할 때 사용하는 초기화 벡터를 의미하지만, 이 책에서 설명하기에는 내용이 많으므로 AES 암호화에 대해 따로 공부하는 것이 좋습니다. 사용 가능한 알고리즘 목록은 crypto.getCiphers()를 호출하면 볼 수 있습니다.
• cipher.update(문자열, 인코딩, 출력 인코딩): 암호화할 대상과 대상의 인코딩, 출력 결과물의 인코딩을 넣습니다. 보통 문자열은 utf8 인코딩을, 암호는 base64를 많이 사용합니다.
• cipher.final(출력 인코딩): 출력 결과물의 인코딩을 넣으면 암호화가 완료됩니다.
• crypto.createDecipheriv(알고리즘, 키, iv): 복호화할 때 사용합니다. 암호화할 때 사용했던 알고리즘과 키, iv를 그대로 넣어야 합니다.
• decipher.update(문자열, 인코딩, 출력 인코딩): 암호화된 문장, 그 문장의 인코딩, 복호화할 인코딩을 넣습니다. createCipheriv의 update()에서 utf8, base64순으로 넣었다면 createDecipheriv의 update()에서는 base64, utf8순으로 넣으면 됩니다.
• decipher.final(출력 인코딩): 복호화 결과물의 인코딩을 넣습니다.
util
util이라는 이름처럼 각종 편의 기능을 모아둔 모듈입니다. 계속해서 API가 추가되고 있으며, 가끔 deprecated되어 사라지는 경우도 있습니다.
• util.deprecate: 함수가 deprecated 처리되었음을 알립니다. 첫 번째 인수로 넣은 함수를 사용했을 때 경고 메시지가 출력됩니다. 두 번째 인수로 경고 메시지 내용을 넣으면 됩니다. 함수가 조만간 사라지거나 변경될 때 알려줄 수 있어 유용합니다.
• util.promisify: 콜백 패턴을 프로미스 패턴으로 바꿉니다. 바꿀 함수를 인수로 제공하면 됩니다. 이렇게 바꿔두면 async/await 패턴까지 사용할 수 있어 좋습니다. 3.5.5.1절의 randomBytes와 비교해보세요. 프로미스를 콜백으로 바꾸는 util.callbackify도 있지만 자주 사용되지는 않습니다.
worker_threads
위 에선 0이 되면 모든 워커들이 종료되서(이런거도 우리가 다 판다나. 워커스레드에서 판단해주는게 없음. 수동으로 다 해야. 일 나누는게 복잡)
하나 끝날때마다 delete해서 0이 되면 모든 워커스레드 종료
노드에서 멀티 스레드 방식으로 작업하는 방법을 소개합니다. worker_threads 모듈로 가능합니다.
워커스레드는 실제로 노드에서쓰는 경우는 많이 드물다. CPU를 많이 쓰는 작업이나 압축 이런작업 직접 구현시에나 씀. 대부분은 싱글스레드. 원래 노드에선 멀티스레드 안되는게 되게 해주는 거. 멀티스레드 방식으로 프로그래밍 하는게 쉬운 일은 아님.
먼저 간단한 사용 방법을 알아보겠습니다.
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();
});
}
워커스레드에서 일을 나눠갖는게 아니라 메인 스레드에서 워커스레드에서 동시에 분배했던게 나눠간다.
메인스레드에서 워커스레드로 핑이라는 메시지 보냄.(워커스레드 한개 생성)
워커스레드에서 ParentPort로 불러온걸 받고 메시지로 퐁을 준다음워커스레드가 할일 다하면 워커스레드가 종료된다.
그리고 부모에서 워커.on으로 밸류하고 찍은 다음(부모로 전달)
원리는 부모에서 일 분배하고 워커는 다시 부모로 이랗ㄴ 내용 쏴주고 종료
결국 일을 나눠서 처리하는게 중요
child_process
결국 이건 다른언어를 호출해서 쓸 일이 있을 떄 필요
노드에서 다른 프로그램을 실행하고 싶거나 명령어를 수행하고 싶을 때 사용하는 모듈입니다. 이 모듈을 통해 다른 언어의 코드(예를 들면, 파이썬)를 실행하고 결괏값을 받을 수 있습니다.
이름이 child_process(자식 프로세스)인 이유는 현재 노드 프로세스 외에 새로운 프로세스를 띄워서 명령을 수행하고, 노드 프로세스에 결과를 알려주기 때문입니다.
파이썬 코드를 실행하는 명령어인 python test.py를 노드의 spawn을 통해 실행합니다. spawn의 첫 번째 인수로 명령어를, 두 번째 인수로 옵션 배열을 넣으면 됩니다. 결과는 exec과 마찬가지로 stdout, stderr의 데이터로 나옵니다.
기타 모듈들 이 책에서 언급하지 않은 모듈들이 많습니다. 여기서는 각 모듈의 이름과 용도만 간단히 소개하고 넘어가겠습니다. 자세한 사항을 알고 싶다면 공식 문서를 참조하길 바랍니다. 실험적인 모듈들은 제외했고, 여기에 언급되지 않은 모듈들은 추후에 나옵니다.
• assert: 값을 비교하여 프로그램이 제대로 동작하는지 테스트하는 데 사용합니다.
• dns: 도메인 이름에 대한 IP 주소를 얻어내는 데 사용합니다.
• net: HTTP보다 로우 레벨인 TCP나 IPC 통신을 할 때 사용합니다.
• string_decoder: 버퍼 데이터를 문자열로 바꾸는 데 사용합니다.
• tls: TLS와 SSL에 관련된 작업을 할 때 사용합니다.
• tty: 터미널과 관련된 작업을 할 때 사용합니다.
• dgram: UDP와 관련된 작업을 할 때 사용합니다.
• v8: V8 엔진에 직접 접근할 때 사용합니다.
• vm: 가상 머신에 직접 접근할 때 사용합니다.
파일 시스템 접근하기
fs 모듈은 파일 시스템에 접근하는 모듈입니다. 즉, 파일을 생성하거나 삭제하고, 읽거나 쓸 수 있습니다. 폴더도 만들거나 지울 수 있습니다.
웹 브라우저에서 자바스크립트를 사용할 때는 일부를 제외하고는 파일 시스템 접근이 금지되어 있으므로 노드의 fs 모듈이 낯설 것입니다.
간단한 예제를 통해 fs 모듈의 사용 방법을 알아보겠습니다. readme.txt와 readFile.js를 만들고 readFile.js를 실행합니다.
fs 모듈을 불러온 뒤 읽을 파일의 경로를 지정합니다. 여기서는 파일의 경로가 현재 파일 기준이 아니라 node 명령어를 실행하는 콘솔 기준이라는 점에 유의해야 합니다. 지금은 크게 상관없으나 폴더 내부에 들어 있는 파일을 실행할 때 경로 문제가 발생할 수 있습니다. 만약 C:\ 디렉터리에서 node folder/file.js를 실행하면 C:\folder\readme.txt가 실행되는 게 아니라 C:\readme.txt가 실행됩니다.
파일을 읽은 후에 실행될 콜백 함수도 readFile 메서드의 인수로 같이 넣습니다. 이 콜백 함수의 매개변수로 에러 또는 데이터를 받습니다. 파일을 읽다가 무슨 문제가 생겼다면 에러가 발생할 것이고, 정상적으로 읽었다면 다음과 같이 콘솔에 결과가 나올 것입니다.
const fs = require('fs');
fs.readFile('./readme.txt', (err, data) => {
if (err) {
throw err;
}
console.log(data);
console.log(data.toString());
});
fs는 기본적으로 콜백 형식의 모듈이므로 실무에서 사용하기가 불편합니다. 따라서 fs 모듈을 프로미스 형식으로 바꿔주는 방법을 사용합니다.
const fs = require('fs').promises;
fs.readFile('./readme.txt')
.then((data) => {
console.log(data);
console.log(data.toString());
})
.catch((err) => {
console.error(err);
});
동기 메서드와 비동기 메서드
setTimeout 같은 타이머와 process.nextTick 외에도, 노드는 대부분의 메서드를 비동기 방식으로 처리합니다.
하지만 몇몇 메서드는 동기 방식으로도 사용할 수 있습니다. 특히 fs 모듈이 그러한 메서드를 많이 가지고 있습니다.
어떤 메서드가 동기 또는 비동기 방식으로 동작하는지와 언제 어떤 메서드를 사용해야 하는지를 알아보겠습니다
비동기 메서드들은 백그라운드에 해당 파일을 읽으라고만 요청하고 다음 작업으로 넘어갑니다.
따라서 파일 읽기 요청만 세 번을 보내고 console.log(‘끝’)을 찍습니다.
나중에 읽기가 완료되면 백그라운드가 다시 메인 스레드에 알립니다. 메인 스레드는 그제서야 등록된 콜백 함수를 실행합니다.
이 방식은 상당히 좋습니다. 수백 개의 I/O 요청이 들어와도 메인 스레드는 백그라운드에 요청 처리를 위임합니다.
그 후로도 얼마든지 요청을 더 받을 수 있습니다. 나중에 백그라운드가 각각의 요청 처리가 완료되었다고 알리면 그때 콜백 함수를 처리하면 됩니다.
백그라운드에서는 요청 세 개를 거의 동시에 실행합니다.
백그라운드는 어떻게 파일 읽기 작업을 처리할까요? 이 부분은 3.6.4절에서 스레드풀을 다루며 자세히 알아봅니다.
동기와 비동기, 블로킹과 논 블로킹
동기와 비동기, 블로킹과 논 블로킹이라는 네 개의 용어가 노드에서 혼용되고 있으며, 의미도 서로 다릅니다.
• 동기와 비동기: 백그라운드 작업 완료 확인 여부
• 블로킹과 논 블로킹: 함수가 바로 return되는지 여부
노드에서는 동기-블로킹 방식과 비동기-논 블로킹 방식이 대부분입니다. 동기-논 블로킹이나 비동기-블로킹은 없다고 봐도 됩니다.
동기-블로킹 방식에서는 백그라운드 작업 완료 여부를 계속 확인하며, 호출한 함수가 바로 return되지 않고 백그라운드 작업이 끝나야 return됩니다. 비동기-논 블로킹 방식에서는 호출한 함수가 바로 return되어 다음 작업으로 넘어가며, 백그라운드 작업 완료 여부는 신경 쓰지 않고 나중에 백그라운드가 알림을 줄 때 비로소 처리합니다.
버퍼와 스트림 이해하기
파일을 읽거나 쓰는 방식에는 크게 두 가지 방식, 즉 버퍼를 이용하는 방식과 스트림을 이용하는 방식이 있습니다. 버퍼링과 스트리밍이라는 용어를 들어본 적이 있나요? 아마 인터넷으로 영상을 시청할 때 두 용어를 본 적이 있을 겁니다. 영상을 로딩할 때는 버퍼링한다고 하고, 영상을 실시간으로 송출할 때는 스트리밍한다고 합니다.
버퍼링은 영상을 재생할 수 있을 때까지 데이터를 모으는 동작이고, 스트리밍은 방송인의 컴퓨터에서 시청자의 컴퓨터로 영상 데이터를 조금씩 전송하는 동작입니다. 스트리밍하는 과정에서 버퍼링을 할 수도 있습니다. 전송이 너무 느리면 화면을 내보내기까지 최소한의 데이터를 모아야 하고, 영상 데이터가 재생 속도보다 빠르게 전송되어도 미리 전송받은 데이터를 저장할 공간이 필요하기 때문입니다.
노드의 버퍼와 스트림도 비슷한 개념입니다. 앞에서 readFile 메서드를 사용할 때 읽었던 파일이 버퍼 형식으로 출력되었습니다. 노드는 파일을 읽을 때 메모리에 파일 크기만큼 공간을 마련해두며 파일 데이터를 메모리에 저장한 뒤 사용자가 조작할 수 있도록 합니다. 이때 메모리에 저장된 데이터가 바로 버퍼입니다.
버퍼를 직접 다룰 수 있는 클래스가 있습니다. 바로 Buffer입니다.
Buffer 객체는 여러 가지 메서드를 제공합니다.
• from(문자열): 문자열을 버퍼로 바꿀 수 있습니다. length 속성은 버퍼의 크기를 알립니다. 바이트 단위입니다.
• toString(버퍼): 버퍼를 다시 문자열로 바꿀 수 있습니다. 이때 base64나 hex를 인수로 넣으면 해당 인코딩으로도 변환 가능합니다.
• concat(배열): 배열 안에 든 버퍼들을 하나로 합칩니다.
• alloc(바이트): 빈 버퍼를 생성합니다. 바이트를 인수로 넣으면 해당 크기의 버퍼가 생성됩니다.
readFile 방식의 버퍼가 편리하기는 하지만 문제점도 있습니다. 만약 용량이 100MB인 파일이 있으면 읽을 때 메모리에 100MB의 버퍼를 만들어야 합니다. 이 작업을 동시에 열 개만 해도 1GB에 달하는 메모리가 사용됩니다. 특히 서버처럼 몇 명이 이용할지 모르는 환경에서는 메모리 문제가 발생할 수 있습니다.
또한, 모든 내용을 버퍼에 다 쓴 후에야 다음 동작으로 넘어가므로 파일 읽기, 압축, 파일 쓰기 등의 조작을 연달아 할 때 매번 전체 용량을 버퍼로 처리해야 다음 단계로 넘어갈 수 있습니다.
그래서 버퍼의 크기를 작게 만든 후 여러 번으로 나눠 보내는 방식이 등장했습니다. 예를 들면 버퍼 1MB를 만든 후 100MB 파일을 백 번에 걸쳐서 나눠 보내는 것입니다. 이로써 메모리 1MB로 100MB 파일을 전송할 수 있습니다. 이를 편리하게 만든 것이 스트림입니다.
파일을 읽는 스트림 메서드로는 createReadStream이 있습니다. 다음과 같이 사용합니다.
readme3.txt
저는 조금씩 조금씩 나눠서 전달됩니다. 나눠진 조각을 chunk라고 부릅니다.
먼저 createReadStream으로 읽기 스트림을 만듭니다. 첫 번째 인수로 읽을 파일 경로를 넣습니다. 두 번째 인수는 옵션 객체인데 highWaterMark라는 옵션이 버퍼의 크기(바이트 단위)를 정할 수 있는 옵션입니다. 기본값은 64KB이지만, 여러 번 나눠서 보내는 모습을 보여주기 위해 16B로 낮췄습니다.
readStream은 이벤트 리스너를 붙여서 사용합니다. 보통 data, end, error 이벤트를 사용합니다. 위 예제의 readStream.on(‘data’)와 같이 이벤트 리스너를 붙이면 됩니다. 파일을 읽는 도중 에러가 발생하면 error 이벤트가 호출되고, 파일 읽기가 시작되면 data 이벤트가 발생합니다. 16B씩 읽도록 설정했으므로 파일의 크기가 16B보다 크다면 여러 번 발생할 수도 있습니다. 파일을 다 읽으면 end 이벤트가 발생합니다.
예제에서는 미리 data 배열을 만들어놓고 들어오는 chunk들을 하나씩 push한 뒤 마지막에 Buffer.concat()으로 합쳐서 다시 문자열을 만들었습니다.
파일의 크기가 99B라 무려 일곱 번에 걸쳐 데이터를 전송했습니다. 하지만 기본값으로는 64KB씩 전송하므로 대부분의 txt 파일들은 한 번에 전송됩니다.
조금씩 나눠서 보낸게 파이프로 들어감. read와 write를 16바이트로 pipe로 연결하고 read에서 보낸걸 write가 흔히 말하는파일복사라고 생각하면 됨
gzip 방식으로 쓰기도 하는데 이는 압축해서 쓰는 방식. 확장자로 압축하고 다양한 파이프로 연결이 가능. 파이프가 다 지원하는게 아니고 스트림을 지원하는 거만 가능. 스트림도 여러번 가능은 함.
readme.txt를 16바이트씩 읽어서 writeme.txt에 16바이트씩 쓰는 그런거
다음 예시는 압축 스트림
여기서는 네 가지 메서드를 소개합니다. 모두 비동기 메서드이므로 한 메서드의 콜백에서 다른 메서드를 호출합니다.
• fs.access(경로, 옵션, 콜백): 폴더나 파일에 접근할 수 있는지를 체크합니다. 두 번째 인수로 상수들(constants를 통해 가져옵니다)을 넣었습니다. F_OK는 파일 존재 여부, R_OK는 읽기 권한 여부, W_OK는 쓰기 권한 여부를 체크합니다. 파일/폴더나 권한이 없다면 에러가 발생하는데 파일/폴더가 없을 때의 에러 코드는 ENOENT입니다.
• fs.mkdir(경로, 콜백): 폴더를 만드는 메서드입니다. 이미 폴더가 있다면 에러가 발생하므로 먼저 access 메서드를 호출해서 확인하는 것이 중요합니다.
• fs.open(경로, 옵션, 콜백): 파일의 아이디(fd 변수)를 가져오는 메서드입니다. 파일이 없다면 파일을 생성한 뒤 그 아이디를 가져옵니다. 가져온 아이디를 사용해 fs.read나 fs.write로 읽거나 쓸 수 있습니다. 두 번째 인수로 어떤 동작을 할 것인지를 설정할 수 있습니다. 쓰려면 w, 읽으려면 r, 기존 파일에 추가하려면 a입니다. 앞의 예제에서는 w를 했으므로 파일이 없을 때 새로 만들 수 있었습니다. r이었다면 에러가 발생했을 것니다.
• fs.rename(기존 경로, 새 경로, 콜백): 파일의 이름을 바꾸는 메서드입니다. 기존 파일 위치와 새로운 파일 위치를 적으면 됩니다. 꼭 같은 폴더를 지정할 필요는 없으므로 잘라내기 같은 기능을 할 수도 있습니다.
• fs.readdir(경로, 콜백): 폴더 안의 내용물을 확인할 수 있습니다. 배열 안에 내부 파일과 폴더명이 나옵니다.
• fs.unlink(경로, 콜백): 파일을 지울 수 있습니다. 파일이 없다면 에러가 발생하므로 먼저 파일이 있는지를 꼭 확인해야 합니다.
• fs.rmdir(경로, 콜백): 폴더를 지울 수 있습니다. 폴더 안에 파일들이 있다면 에러가 발생하므로 먼저 내부 파일을 모두 지우고 호출해야 합니다.
node fsDelete를 한 번 더 실행하면 ENOENT 에러가 발생합니다. 존재하지 않는 파일을 지웠다는 에러입니다.
스레드풀
- 백그라운드에서 돌아가는게 몇개까지 동시에 돌아가나가 일단 기본
- 노드는 일단 4개
- fs, crypto, zlib 같은 애들은 백그라운드에서 동시에 실행됨.
- 스레드풀이 동시에 처리해줌
- 몇개까지 같이 돌아가나
- 무턱대고 동시에 돌아가는지. 기본적으로 4개 설정됨.
이걸 보는게 threadpool.js 백그라운드 보려면 crpyto같은 복잡한 연산해야 보인다.
100만 sha512로 복잡해서 해시화로 100만번 계산하는거
동시에 몇개씩 돌아가는지 보인다.
실행할 때마다 시간과 순서가 달라집니다. 스레드풀이 작업을 동시에 처리하므로 여덟 개의 작업 중에서 어느 것이 먼저 처리될지 모릅니다. 하지만 하나의 규칙을 발견할 수는 있습니다. 1~4와 5~8이 그룹으로 묶여져 있고, 5~8이 1~4보다 시간이 더 소요됩니다. 바로 기본적인 스레드풀의 개수가 네 개이기 때문입니다. 스레드풀이 네 개이므로 처음 네 작업이 동시에 실행되고, 그것들이 종료되면 다음 네 개의 작업이 실행됩니다. 만약 여러분 컴퓨터의 코어 개수가 4보다 작다면 다른 결과가 생길 수는 있습니다.
타이머 같은거 로 시간 재면 좋은데 처음 4개랑 그다음 4개랑. 이런거 ㄹ4개씩 돌린다.여러번
컴퓨터가 코어가 6개인데 4개밖에 안 쓰는거 이걸 비효율적이라 생각하면 코어개수에 맞게 설정도 가능.
코어 개수에 맞게 설정해야 편함
이벤트 (커스텀 이벤트)
이벤트 패턴으로 여러 동작 구현 가능
이벤트 1에서 이벤트 2의 콜백함수 호출해서 실행시킨다거나.
event.js에서 이벤트 하나 만들고 addeventlistner의 별명으로 이벤트 리스너로 on이 있는데 이걸 2개 만들었다(콜백). once는 이벤트 있으면 하번만 실행되고 다음은 실행 안됨.
event 지우는 것도 있다.
remove했을떄는 이벤트4 지우고 싶으면 removealllister, removelistner이 두개가 있다.
5장
npm 이란
Node Packaged Manager 터미널에서 그냥 import하고 설치하면 끝
js
Babel
nodemon
- forever 툴이라는 것도 있다.
socket.io
ajax
- patch 추가
Axios에서 좀 더 쉽게
Express
electron
- Http 보안 필요
- 그떄 helmet이라는 거 쓰기도 함
노드 서버가 종종 잘 죽기도 함
- 프로세스 제어 잘 도와주는 거로. 잘 죽기도 함.
Babel
- 브라우저는 최신 js 지원 안하고 pc 웹 브라우저 다양함.
- 모바일 웹 브라우저가 최신 지원 안하거나
- Express
- Babel으로 호환성 챙김
설레발 떨지 마라
프로시저는 SQL 모듈(함수)같은거.
한번 실행하면 SQL문장들이 실행.
- 근데 프로시저 쿼리콜이 부하가 많이 걸림
- 최대한 쿼리 지양한다.
미들웨어 = c에서의 함수포인터의 개념
next 로 포인터로 잡고 넘어간다 이런 느낌
미들웨어는 리퀘스트 받아서 다음처리 연결해서 쓰는 것.
body-parser
express는 json방식
cookie-parser
쉼표 잘라서 문자열 해석해서 객체에 저장.
쿠키는 전통적ㅇ로 저장되어있는 파일 로그인 비번이 저장되면 세션아이디로 저장해줌. 이걸 서버로 보내면 j세션으로만으로는 저장 안됨
쿠키세션은 답지를 서버에 저장 jwt는 답지는 클라이언트에 주고 (키를 주진 않지만) jwt문자열을 많이 하는데 토큰의 구조 헤더 바디 시그니처
헤더와 바디의 키를 암호화 한걸 시그니처에 저장
만약 바디를 하나라도 달라져도 시그니처가 달라짐-> 조작여부 판단.
세션은 서버 부하가 적어짐 세션은 서버내용을 다 저장해야하므로
jwt는 서버에 안 올라가도 되므로 서버 다중화를 안해도 됨(클러스터)