[learning javascript] chapter 20. 노드

노드

노드의 기초

모듈

console.log(amanda_calculate(1, 2, 5)); // 31 console.log(tyler_calculate(2)); // 33.510321638291124

- 아만다는 등비급수(geometric series)의 합인 a + ax + ax^2 + ... + ax^(n-1)
- 타일러는 반지름이 r인 구체의 체적(volumn of a sphere)을 구하고 있음
- app.js에서 더 적당한 이름을 정해서 수정 
```javascript
const geometricSum = require('./amanda.js');
const sphereVolume = require('./tyler.js');

console.log(geometricSum(1, 2, 5));     // 31
console.log(sphereVolume(2));           // 33.510321638291124

코어 모듈, 파일 모듈, npm 모듈

|타입|매개변수|예제| |—|—|—| |코어|/, ./, ../ 등으로 시작하지 않음|require(‘fs’)
require(‘os’)
require(‘http’)
require(‘child_process’)| |파일|/, ./, ../ 등으로 시작|require(‘./debug.js’)
require(‘/full/path/to/module.js’)
require(‘../a.js’)
require(‘../../a.js’)| |npm|코어 모듈이 아니며 /, ./, ../으로 시작하지도 않음|require(‘debug’)
require(‘express’)
require(‘chalk’)
require(‘koa’)|

모듈 전역여부 설명
assert 아님 테스트 목적 사용
buffer 전역 입출력 I/O 작업에 사용(주로 파일과 네트워크)
child_process 아님 외부 프로그램 실행시 필요
cluster 아님 다중 프로세스를 이용해 성능 향상
crypto 아님 내장 암호화 라이브러리
dns 아님 도메인 네임 시스템 함수
domain 아님 에러를 고립시키기 위해 I/O 작업이나 비동기적 작업을 그룹으로 묶을 수 있음
events 아님 비동기적 이벤트 지원
fs 아님 파일시스템 작업
http 아님 HTTP 서버 및 관련된 유틸
https 아님 HTTPS 서버 및 관련된 유틸
net 아님 비동기적 소켓 기반 네트워크 API
os 아님 운영체제 유틸리티
path 아님 파일시스템에서 사용하는 경로(path)관련 유틸리티
punycode 아님 유니코드 인코딩을 지원하며 ASCII 부분집합을 일부 사용
querystring 아님 URL 쿼리스트링 해석
readline 아님 대화형 I/O 유틸리티, 주로 콘솔 프로그램에서 사용
smalloc 아님 버퍼에 메모릴르 명시적으로 할당할 때 사용
stream 전역 스트림 기반 데이터 전송에 사용
string_decoder 아님 버퍼를 문자열로 변환
tls 아님 보언 전송 계층(TLS) 통신 유틸
tty 아님 저수준 TTY(TeleTYPewriter)함수
dgram 아님 사용자 데이터그램 프로토콜(UDP) 네트워크 유틸
url 전역 URL 파싱 유틸
util 아님 내부 노드 유틸
vm 아님 자바스크립트 가상 머신. 메타프로그래밍이나 컨텍스트 생성에 사용
zlib 아님 압축 유틸

함수 모듈을 통한 모듈 커스터마이징

module.exports = function(prefix) { return function(message) { const now = Date.now(); const sinceLastMessage = now - (lastMessage || now); console.log(${prefix}${message} + ${sinceLastMessage}ms); lastMessage = now; } }

- 이 모듈이 내보내는 함수는 즉시 호출해서 `prefix`값으로 모듈 자체를 커스터마이즈 하도록 설계 
- `lastMessage`는 마지막 메시지를 기록했을 때의 타임스탬프. 이 값을 사용하여 메시지 사이 시간을 계산할 수 있음 
- 모듈을 여러번 임포트 하면 어떻게 되는가?
```javascript
const debug1 = require('./debug')('one');
const debug2 = require('./debug')('two');

debug1('started first debugger!');
debug2('started second debugger!');

setTimeout(function() {
    debug1('after some time...');
    debug2('what happens?');
}, 200);

파일시스템 접근

fs.writeFile(‘hello.txt’, ‘hello from Node!’, function(err) { if(err) return console.log(‘Error writing to file.’); });

- `write.js`파일을 저장한 디렉토리에 쓰기 권한이 있고, 읽기 전용 `hello.txt`파일이 존재하지 않으면 `hello.txt`파일이 생성됨 
- 노드 애플리케이션을 실행하면 해당 애플리케이션은 자신이 실행된 작업 디렉토리를 `__dirname`변수로 보관 
- 이 변수를 사용해 `write.js`파일을 다음과 같이 고칠 수 있음 
```javascript

const fs = require('fs');
fs.writeFile(__dirname + '/hello.txt', 
        'hello from Node!', function(err) {
    if(err) return console.log('Error writing to file.');
});

fs.writeFile(path.join(__dirname, ‘hello.txt’), function(err, data) { if(err) return console.error(‘Error reading file.’); console.log(‘Read file contents:’); console.log(data); });

- 실행결과는?

Read file contents: <buffer 68 65 6c 6f 20 66 72 6d 4e 64 21>

- 이 16진수 코드를 ASCII/Unicode로 바꾸면 `hello from Node!`이긴 함
- `fs.readFile`에 인코딩 정보를 제공하지 않으면 `fs.readFile`은 가공되지 않은 바이너리 데이터인 버퍼를 반환 
- `read.js`에 UTF-8인코딩을 지정하면 원하는 문자로 나옴 
```javascript
const fs = require('fs');
const path = require('path');

fs.writeFile(path.join(__dirname, 'hello.txt'), 
        { encoding: 'utf8' }, function(err, data) {
    if(err) return console.error('Error reading file.');
    console.log('Read file contents:');
    console.log(data);
});

fs.readdir(__dirname, function(err, files) { if(err) return console.error(‘Unable to read directory contents’); console.log(Contents of ${__dirname}:); console.log(files.map(f => ‘\t’ + f).join(‘\n’)); })

- `fs` 모듈에는 여러가지 함수들이 있음  
- 파일 지울 때 `fs.unlink`, 파일을 옮기거나 이름을 바꿀때 `fs.rename`, 파일이나 디렉토리 정보를 얻을 때 `fs.stat`

## process
- 실행 중인 노드 프로그램은 모두 `process`에 접근할 수 있음 
- 이 변수는 해당 프로그램에 관한 정보를 담고 있으며 실행 자체를 컨트롤할 수도 있음 
- 예를 들어 애플리케이션이 치명적인 에러를 만나서 계속 실행하지 않는 편이 좋거나 더 실행해도 의미가 없는 상황이라면 (이런 에러를 fatal error라고 함) `process.exit`를 호출해 즉시 실행을 멈출 수 있음 
- 숫자형 종료 코드(exit code)를 쓰면 프로그램이 성공적으로 종료 했는 지 에러가 있었는 지 외부 스크립트를 통해 알 수 있음 
- 보통 에러 없이 끝냈을 땐 종료 코드 0을 사용
- data 서브 디렉토리에 있는 `.txt`파일을 모두 처리하는 프로그램이 있다고 치고 data 서브 디렉토리에 `.txt`파일이 없다면?
```javascript
const fs = require('fs');

fs.readdir('data', function(err, files) {
    if(err) {
        console.error("Fatal error: couldn't read data directory.");
        process.exit(1);
    }
    const txtFiles = files.filter(f => /\.txt$/i.test(f));
    if(txtFiles.length === 0) {
        console.log("No .txt files to process.");
        process.exit(0);
    }
    // .txt 파일 처리 
});

const filenames = process.argv.slice(2);

let counts = filenames.map(f => { try { const data = fs.readFileSync(f, { encoding: ‘utf8’ }); return ${f}: ${data.split('\n').length}; } catch(err) { return ${f}: couldn't read file; } });

console.log(counts.join(‘\n’));

- `process.env`를 통해 환경 변수에 접근할 수도 있음 
- 디버그 정보에 대한 기록 여부를 환경 변수로 컨트롤 한다라고 하면?(환경변수 DEBUG를 1로 설정하면 기록, 아니면 하지 않음)
```javascript
const debug = process.env.DEBUG === "1" ?
    console.log :
    function() {};

debug("Visible only if environment variable DEBUG is set!");

운영체제

console.log(“Hostname: “+os.hostname()); console.log(“OS type: “+os.type()); console.log(“OS platform: “+os.platform()); console.log(“OS release: “+os.release()); console.log(“OS uptime: “+(os.uptime()/60/60/24).toFixed(1)+” days”); console.log(“CPU architecture: “+os.arch()); console.log(“Number of CPUs: “+os.cpus().length); console.log(“Total memory: “+(os.totalmem()/1e6).toFixed(1)+ “ MB”); console.log(“Free memory: “+(os.freemem()/1e6).toFixed(1)+” MB”);


## 자식 프로세스 
- `child_process` 모듈은 애플리케이션에서 다른 프로그램을 실행할 때 사용 
- `child_process`에서 제공하는 주요 함수는 `exec`, `execFile`, `fork`
- `fs`와 마찬가지로 동기적 버전 `execSync`, `execFileSync`, `forkSync` 존재
- `exec`는 쉘을 호출하므로 쉘에서 호출할 수 있는 무엇이든 실행 가능 
- `execFile`은 쉘을 통하지 않고 실행파일을 직접 실행
- `fork`는 다른 노드 스크립트를 실행할 때 사용 
- NOTE

fork는 별도의 노드 엔진을 호출하므로 소모하는 자원 면에서는 exec와 같음. 하지만 fork를 사용하면 프로세스와의 통신이 가능해짐

- 아래 예제는 `dir`명령어를 통해 디렉토리 내용을 출력함
```javascript
const exec = require('child_process').exec;

exec('dir', function(err, stdout, stderr) {
    if(err) return console.error('Error executing "dir"');
    stdout = stdout.toString();     // Buffer를 문자열로 변환 
    console.log(stdout);
    stderr = stderr.toString();
    if(stderr != '') {
        console.error('error:');
        console.errlr(stderr);
    }
});

스트림

웹 서버

const server = http.createServer(function(req, res) { console.log(${req.method} ${req.url}); res.end(‘Hello world!’); });

const port = 8080; server.listen(port, function() { // 서버가 시작 됐을 때 호출될 콜백을 넘길 수 있음 console.log(server started on port ${port}); });

- NOTE

대부분의 운영체제는 보안상의 이유로 기본 HTTP 포트(80)을 막아놓음 1024 미만의 포트를 사용하려면 항상 관리자 권한이 필요 개발과 테스트 목적으로는 보통 1024 이상의 포트를 사용하는 편

- 이 프로그램을 실행하고 브라우저에서 `http://localhost:8080`에 방문하면 Hello world!가 보임 
- 터미널을 보면 모든 요청이 기록되어 있음 
- 노드 웹 버서의 핵심은 들어오는 요청에 모두 응답하는 콜백 함수임 
- 이 함수는 매개변수로 `IncomingMessage` 객체(보통 req라 함)와 `ServerResponse` 객체(보통 res라 함)를 받음
- `IncomingMessage` 객체에는 요청받은 URL, 보낸 헤더, 바디에 들어있는 데이터등 HTTP 요청에 관한 모든 정보가 있음 
- `ServerResponse`객체는 클라이언트(보통 브라우저)에 보낼 응답을 컨트롤하는 프로퍼티와 메소드가 있음 
- `ServerResponse`객체는 쓰기 스트림 인터페이스이며 이를 통해 데이터를 클라이언트에 보냄 
- `ServerResponse`객체가 쓰기 스트림이므로 파일을 보내기도 쉬움 
- 파일 읽기 스트림을 만들어 HTTP 응답에 파이프로 연결하기만 하면 됨 
- 예를 들어 `favicon.ico` 파일을 사용한다면 파비콘 요청을 가밎하고 파일을 바로 보낼 수 있음 
```javascript
const server = http.createServer(function(req, res) {
    if(req.method === 'GET' && req.url === '/favicon.ico') {
        const fs = require('fs');
        fs.createReadStream('favicon.ico');
        fs.pipe(res);       // end 대신 사용 가능 
    } else {
        console.log(`${req.method} ${req.url}`);
        res.end('Heelo world!');
    }
});

요약