웹 서비스 개발(FB,BE,SERVER,DB)/Node.js

2. Node.js_Basic

Zoo_10th 2024. 3. 11.

1. JavaScript의 이해

1-1. 호출 스택

Node.js에서 호출 스택은 실행 중인 스크립트, 함수, 또는 내부 API 호출의 추적을 관리하는 자료구조이다.

호출 스택의 원리

1) 스택 구조: 호출 스택은 Last In, First Out (LIFO)의 원리로 작동하는 스택 구조를 갖는다. 이는 가장 마지막에 들어간 요소가 가장 먼저 나오는 구조를 의미한다.

2) 호출 순서: 함수가 호출되면, 호출 스택에는 해당 함수의 실행 컨텍스트가 쌓인다. 이 컨텍스트에는 함수의 매개변수, 지역 변수, 반환 주소 등의 정보가 포함된다.

3) 실행 과정: 호출된 함수가 실행을 완료하고 결과를 반환하면, 해당 함수의 실행 컨텍스트는 스택에서 제거된다. 이후 이전에 중단된 지점부터 실행이 계속된다.

4) Anonymous 컨텍스트: 프로그램이 시작할 때, Node.js는 'Anonymous'라고 불리는 전역 실행 컨텍스트를 스택에 푸시한다. 이는 전역 스코프를 가리키며, 프로그램 실행 동안 항상 존재한다.

function thirdFunction() {
    console.log('세 번째 함수');
}

function firstFunction() {
    secondFunction();
    console.log('첫 번째 함수');
}

function secondFunction() {
    thirdFunction();
    console.log('두 번째 함수');
}


firstFunction();

(1) firstFunction() 호출 -> 스택: [Anonymous, firstFunction]

(2) firstFunction() 내에서 secondFunction() 호출 -> 스택: [Anonymous, firstFunction, secondFunction]

(3) secondFunction() 내에서 thirdFunction() 호출 -> 스택: [Anonymous, firstFunction, secondFunction, thirdFunction]

(4) thirdFunction() 실행 완료 -> 스택: [Anonymous, firstFunction, secondFunction]

(5) secondFunction() 실행 완료 -> 스택: [Anonymous, firstFunction]

(6) firstFunction() 실행 완료 -> 스택: [Anonymous]

호출 스택의 한계

 - 스택 오버플로: 함수 호출이 너무 깊게 중첩되거나 재귀 호출이 제어 없이 계속되면 스택 오버플로가 발생할 수 있다. 이는 스택의 크기가 한정되어 있기 때문에 발생하는 문제이다.

 - 동기 실행: 호출 스택은 동기적으로 실행된다. 하나의 함수가 스택에 있을 때, 다른 함수는 실행될 수 없다. 이는 Node.js의 비동기 처리와 이벤트 루프의 역할이 중요한 이유이다.

1-2. 이벤트 루프

function run() {
    console.log('3초 후 실행');
}

console.log('시작');
setTimeout(run, 3000);
console.log('끝');

1) 코드가 실행되면 console.log('시작');이 호출 스택에 들어가고 실행되어 '시작'이 로그에 출력된다.

2) setTimeout(run, 3000);이 호출 스택에 들어간다. setTimeout은 Web API(브라우저 환경) 또는 Node.js의 타이머 함수(API)로 처리되므로, 호출 스택에서는 바로 제거되고, run 함수는 지정된 3000밀리초(3초) 후에 실행될 콜백으로 이벤트 루프와 함께 타이머에 의해 관리된다.

3) console.log('끝');이 호출 스택에 들어가고 실행되어 '끝'이 로그에 출력된다. 

4) 호출 스택이 비워집니다. (이 때점에서 run 함수는 아직 호출 스택에 들어가지 않았다.)

5) 3초가 지나면, 타이머가 완료되고 run 함수는 이벤트 루프에 의해 태스크 큐에 넣어진다.

6) 호출 스택이 비어 있는 경우(현재 실행 중인 다른 스크립트가 없는 경우), 이벤트 루프는 태스크 큐에서 run 함수를 호출 스택으로 옮긴다.

7) run 함수가 호출 스택에 들어가고 실행되어 '3초 후 실행'이 로그에 출력된다.

비동기 함수인 setTimeout은 호출 스택에서 즉시 처리되지 않고, 지정된 시간이 지난 후에 콜백 함수가 실행된다. 이는 자바스크립트가 단일 스레드로 동작하면서도 비동기 작업을 가능하게 하는 이벤트 루프와 태스크 큐의 동작 때문이다.

2. Module

Node.js의 모듈 시스템은 코드의 재사용성과 관리를 효율적으로 도와주며, 크게 CommonJS 모듈 시스템과 ES 모듈 시스템으로 나뉜다.

2-1. CommonJS 모듈 시스템

1) 기본 개념: CommonJS는 Node.js에서 기본적으로 사용되는 모듈 시스템입니다. 모듈은 특정 기능을 하는 함수나 변수들의 집합이며, module.exports와 require()를 사용하여 모듈을 생성하고 가져온다.

2) 모듈 생성: module.exports를 사용하여 모듈에서 내보낼 객체, 함수, 변수 등을 지정한다. 예를 들어, var.js 파일에 변수를 선언하고 module.exports로 내보낼 수 있다.

3) 모듈 사용: 다른 파일에서 require(파일 경로)를 통해 모듈을 가져올 수 있으며, 가져온 모듈의 함수나 변수를 사용할 수 있다.

4) exports 객체: module.exports와 exports는 참조 관계에 있다. exports를 사용하여 모듈을 생성할 수도 있지만, 객체 자체를 변경하면 참조 관계가 깨질 수 있다.

2-2. ES 모듈 시스템

1) 기본 개념: ES6 이후에 도입된 자바스크립트 고유의 모듈 시스템이다. import와 export 키워드를 사용하여 모듈을 가져오고 내보낸다.

2) 파일 확장자와 설정: .mjs 확장자를 사용하거나 package.json에 "type": "module"을 추가하여 ES 모듈로 작동하도록 설정할 수 있다.

3) 다이내믹 임포트와 최상위 await: ES 모듈에서는 import()를 사용하여 모듈을 동적으로 불러올 수 있으며, .mjs 파일 또는 "type": "module"로 설정된 환경에서는 최상위 스코프에서 await를 사용할 수 있다.

2-3. Node.js 모듈의 특징 및 주의사항

1) this 키워드: 최상위 스코프에서의 this는 module.exports를 가리키지만, 함수 내부에서의 this는 전역 객체를 가리킨다.

2) 캐싱: require한 모듈은 require.cache에 저장되어 재사용된다.

3) 순환 참조: 서로를 require하는 파일 간 순환 참조가 발생하는 경우, Node.js는 빈 객체를 반환하여 무한 반복을 방지한다.

4) 파일 경로와 디렉터리 경로: __filename과 __dirname은 파일 및 디렉터리의 경로를 나타내는 Node.js 전용 전역 변수다. ES 모듈에서는 사용할 수 없으며, 대신 import.meta.url을 사용한다.

Node.js에서 모듈을 사용함으로써 코드의 분리 및 재사용, 관리가 용이해지며, 다양한 패턴과 기법을 통해 유연하게 모듈 시스템을 활용할 수 있다.

3. 내장 객체

3-1. global

1) 전역 객체: Node.js의 global은 브라우저의 window와 유사한 전역 객체이다. 모든 파일에서 접근할 수 있으며, 일부 전역 변수와 함수(console, require 등)는 실제로 global의 속성이다.

2) 속성 공유: global 객체에 값을 추가하면, 어떤 모듈에서든 해당 값을 사용할 수 있게 된다.

3-2. console 객체

1) 로깅 메서드: console 객체는 로깅을 위한 다양한 메서드를 제공한다. 예를 들어 console.time과 console.timeEnd는 시간을 측정하고, console.error는 에러를, console.log는 일반적인 로그를, console.dir은 객체를, console.trace는 호출 스택을 로깅한다.

3-3. 타이머 메서드

1) 타이머 생성과 취소: setTimeout, setInterval, setImmediate는 각각 타이머를 생성하는 함수이다. 이와 대응되는 clearTimeout, clearInterval, clearImmediate 함수는 생성된 타이머를 취소한다.

3-4. process

1) 프로세스 정보: process 객체는 현재 실행 중인 Node.js 프로세스에 대한 정보를 제공한다. 시스템마다 출력값이 다를 수 있는 정보들을 포함하고 있다.

 - process.env

1) 환경 변수: process.env 객체는 시스템 환경 변수들을 담고 있습니다. 주로 애플리케이션 설정이나 비밀키 등을 저장하는 데 사용된다.

 - process.nextTick

1) 이벤트 루프와 태스크 큐: process.nextTick은 주어진 콜백을 이벤트 루프의 현재 단계가 끝나고 다음 단계가 시작되기 전에 실행하도록 예약한다. 이는 Promise와 마찬가지로 마이크로태스크 큐에 속하며, 다른 타이머 함수보다 우선순위가 높다.

 - process.exit

1) 프로세스 종료: process.exit 메서드는 프로세스를 즉시 종료한다. 인자로 전달된 코드에 따라 정상 종료 또는 비정상 종료를 나타낼 수 있다.

4. 내장 모듈

Node.js의 내장 모듈은 다양한 기본적인 시스템 수준의 기능을 제공하며, 별도의 설치 없이 require를 통해 쉽게 불러와 사용할 수 있다.

4-1. OS 모듈

1) 운영체제의 정보를 다루는 모듈이다.

2) 예를 들어 os.type()은 운영체제의 종류를 반환하고, os.uptime()은 시스템이 부팅된 후부터의 시간을 초 단위로 반환한다.

3) os.freemem()과 os.totalmem()은 각각 시스템의 사용 가능한 메모리와 전체 메모리 용량을 반환한다.

4-2. Path 모듈

1) 파일 경로를 다루는 데 유용한 모듈이다.

2) path.join()과 path.resolve()는 경로를 합치는 방법에 있어서 동작 방식에 차이가 있다.

3) path.sep은 시스템별 경로 구분자를 반환하며, path.parse()는 경로를 구성 요소별로 분해한다.

4-3. URL 모듈

1) URL을 쉽게 분석하고 조작할 수 있도록 도와주는 모듈이다.

2) WHATWG 방식은 브라우저에서 사용하는 방식과 동일하며, new URL() 생성자를 사용하여 URL 객체를 생성할 수 있다.

3) searchParams 객체는 URL의 쿼리스트링 부분을 쉽게 다룰 수 있게 해준다.

4-4. DNS 모듈

1) 도메인 이름을 IP 주소로 변환하거나 다양한 DNS 레코드를 조회할 때 사용하는 모듈이다.

2) dns.lookup(), dns.resolve() 등의 함수를 제공한다.

4-5. 암호화(crypto)

 -  단방향 암호화

Node.js의 crypto 모듈은 다양한 암호화 기능을 제공하며, 그 중에서도 단방향 암호화는 특히 해시 함수를 이용하여 구현된다. 단방향 암호화는 암호화된 데이터를 원래의 평문으로 되돌릴 수 없는 방식이다.

 - 해시 기법

1) 해시 함수: 데이터를 고정된 길이의 다른 데이터로 매핑하는 함수이다. 해시 함수는 어떤 길이의 데이터라도 동일한 길이의 유일한 문자열(해시)로 변환한다.

2) 고유성: 이상적인 해시 함수는 서로 다른 두 입력값에 대해 동일한 해시 값을 생성하지 않아야 한다(충돌 방지).

3) 일방향성: 해시는 원본 데이터로부터 계산될 수 있지만, 해시로부터 원본 데이터를 계산하는 것은 불가능하다.

4) 예측 불가성: 해시는 원본 데이터의 작은 변화에도 완전히 다른 값을 생성해야 한다.

const crypto = require('crypto');

// 'sha256' 알고리즘을 사용하여 해시 생성
const hash = crypto

//위 코드는 Python에서 'abcdefgh' 문자열에 대해 SHA-256 해시 알고리즘을 사용하여 해시를 생성하는 예시 생성된 해시는 `'9c56cc51b374c3ba189210d5b6d4bf57790d351c96c47c02190ecf1e430635ab'` 

//Node.js에서도 유사하게 `crypto` 모듈을 사용하여 해시를 생성할 수 있다

const crypto = require('crypto');

const hash = crypto.createHash('sha256').update('abcdefgh').digest('hex');
console.log(hash);

 - pbkdf2

PBKDF2 (Password-Based Key Derivation Function 2)는 비밀번호 기반의 키 유도 함수로, 원래의 비밀번호로부터 안전한 암호화 키를 생성하는 데 사용된다. 이 함수는 원 비밀번호에 소금(salt)을 추가하고, 해시 함수를 여러 번 반복 적용하여, 원본 비밀번호를 직접 알아낼 수 없게 만드는 데 도움을 준다. 이 과정은 무차별 대입 공격(brute-force attack)과 레인보우 테이블(rainbow table) 공격에 대한 저항력을 증가시키는 데 목적이 있다.

const crypto = require('crypto');

// 비밀번호, 솔트, 반복 횟수, 생성될 키의 길이, 해시 알고리즘을 지정
crypto.pbkdf2('secret password', 'salt', 100000, 64, 'sha512', (err, derivedKey) => {
  if (err) throw err;
  console.log(derivedKey.toString('hex')); // 생성된 키를 16진수 문자열로 출력
});

'secret password'는 사용자의 비밀번호이며, 'salt'는 비밀번호에 추가적으로 붙여 해시 값을 보다 복잡하게 만들기 위한 데이터다. 100000은 해시 함수를 적용하는 반복 횟수이고, 64는 생성될 키의 바이트 길이, 'sha512'는 사용할 해시 알고리즘이다.

PBKDF2는 보안 수준을 높이기 위해 해시 함수를 여러 번 반복 적용하기 때문에, 공격자가 비밀번호를 유추하기 위해 각 비밀번호에 대해 해시를 계산해야 하는 시간이 길어지므로 비밀번호 해킹의 난이도가 상승한다. 그러나 컴퓨터 처리 능력의 발달로 인해 기존의 암호화 알고리즘이 위협받고 있는 상황에서, 더 강력한 알고리즘으로의 전환을 고려할 필요가 있다. 예를 들어 SHA-512가 취약해지면, 더 안전한 SHA-3와 같은 알고리즘으로 전환하거나, bcrypt나 scrypt와 같은 다른 암호화 방식을 사용할 수 있다.

 - 양방향 암호화

암호화된 데이터를 복호화할 수 있는 암호화 방법이다. 대칭키 암호화라고도 하며, 암호화와 복호화에 동일한 키를 사용한다. Node.js에서 crypto 모듈을 이용해 양방향 암호화를 구현할 때 사용된다.

 - 암호화 과정

1) crypto.createCipheriv(알고리즘, 키, iv): 암호화할 때 사용할 Cipher 인스턴스를 생성합니다. 여기서 알고리즘은 사용할 암호화 알고리즘(예: 'aes-256-cbc'), 키는 암호화 키, iv는 초기화 벡터(initialization vector)를 나타낸다.

2) cipher.update(문자열, 인코딩, 출력 인코딩): 암호화할 데이터를 넣고, 데이터의 인코딩(일반적으로 'utf8')과 결과물의 인코딩(일반적으로 'base64'나 'hex')을 지정하다.

3) cipher.final(출력 인코딩): 암호화 작업을 완료하고, 마지막 블록을 반환한다.

 - 복호화 과정

1) crypto.createDecipheriv(알고리즘, 키, iv): 복호화할 때 사용할 Decipher 인스턴스를 생성합니다. 암호화에 사용된 동일한 알고리즘, 키, iv를 사용해야한다. 

2) decipher.update(문자열, 인코딩, 출력 인코딩): 복호화할 암호화된 데이터를 넣고, 이 데이터의 인코딩(암호화할 때 사용한 결과물의 인코딩)과 결과물의 인코딩(원래의 데이터 인코딩)을 지정한다.

3) decipher.final(출력 인코딩): 복호화 작업을 완료하고, 복호화된 데이터의 마지막 블록을 반환한다.

const crypto = require('crypto');

const algorithm = 'aes-256-cbc';
const key = crypto.randomBytes(32);
const iv = crypto.randomBytes(16);

// 암호화
const cipher = crypto.createCipheriv(algorithm, key, iv);
let encrypted = cipher.update('some clear text data', 'utf8', 'base64');
encrypted += cipher.final('base64');

// 복호화
const decipher = crypto.createDecipheriv(algorithm, key, iv);
let decrypted = decipher.update(encrypted, 'base64', 'utf8');
decrypted += decipher.final('utf8');

aes-256-cbc 알고리즘을 사용하여 문자열을 암호화하고, 같은 키와 IV를 사용하여 복호화하는 과정을 보여줍니다. 실제 사용 시에는 키와 IV를 안전하게 저장하고 관리해야 하며, 키를 공유할 필요가 있는 통신 상대와는 안전한 방법으로 키를 교환해야 한다.

4-6. Util 모듈

1) 다양한 편의 기능을 제공하는 유틸리티 모듈이다.

2) util.promisify() 함수는 콜백 기반의 함수를 Promise를 반환하는 함수로 변환하여 async/await와 함께 사용할 수 있게 해준다.

4-7. Worker_threads 모듈

1) Node.js에서 멀티 스레딩을 가능하게 하는 모듈이다.

2) 워커 스레드를 생성하고, 메시지를 주고받으며 병렬 처리 작업을 할 수 있다.

# prime-work.js

const { Worker, isMainThread, parentPort, workerData} = require('worker_threads');

const min = 2;
let primes = [];

function findPrime(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 = 6;
    const threads = new Set();
    const range = Math.floor((max - min) / threadCount);
    let start = min;
    console.time('prime')
    for (let i = 0; i < threadCount; i++){
        const wStrat = start;
        threads.add(new Worker(__filename, {workerData: {start: wStrat, range}}));
        start += range;
    }
    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 {
    findPrime(workerData.start, workerData.range);
    parentPort.postMessage(primes);
}
# prime.js

const min = 2;
const max = 10000000;
const prime = []

function findPrime(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) {
            prime.push(i)
        }
        isPrime = true;
    }
}

console.time('prime');
findPrime(min, max);
console.timeEnd('prime');
console.log(prime.length)

4-8. Child_process 모듈

1) 별도의 프로세스를 생성하여 다른 프로그램을 실행하거나 명령어를 실행할 때 사용하는 모듈이다.

2) Node.js 프로세스 외부의 프로그램, 예를 들어 Python 스크립트를 실행할 수 있다. 

728x90

'웹 서비스 개발(FB,BE,SERVER,DB) > Node.js' 카테고리의 다른 글

5. Node.js WebServer 2  (0) 2024.03.13
4. Node.js WebServer  (0) 2024.03.12
3. Node.js_Basic2  (0) 2024.03.11
0. Node.js 설치하기  (0) 2024.03.11
1. Node.js  (0) 2024.03.11

댓글