Nodejs 개요

초기 자바스크립트는 느린언어로 서버에서 사용하기엔 부적절 했으나 구글이 c++기반 V8엔진을 만들면서 자바스크립트또한 v8엔진을 통해 머신코드로 변경이 가능해졌고 속도가 빨라졌다.

https://engineering.huiseoul.com/자바스크립트는-어떻게-작동하는가-v8-엔진의-내부-최적화된-코드를-작성을-위한-다섯-가지-팁-6c6f9832c1d9

자바스크립트 속도가 빨라지자 웹 브라우저 이외에 공간에서도 사용되며 Nodejs가 만들어졌다.

이벤트 기반 비동기 방식

기존 톰캣 웹서버는 다중 스레드 기반의 동기방식의 요청을 지원한다.
동기방식이기 때문에 한 요청의 대한 처리(메서드)를 끝낼 때까지 기다려야 되고 사용자는 답답해진다.
따라서 사용자가 많아질 수록 요청을 처리할 스레드를 늘리게 되고 사용자의 요청또한 동기적으로 빠르게 처리되는 것이 스레드 기반 동기방식 네트워크 입출력이다.

Nodejs 는 단일스레드 이벤트 기반 비동기방식 네트워크 입출력을 지원한다.
비동기 방식이기 때문에 요청이 들어오면 요청에 대한 처리(워커스레드)를 실행만 해놓고 곧바로 다른 요청을 처리하러 간다.
워커스레드가 일을 끝내고 다끝냈음을 알리는 이벤트를 발생하면 그제서야 메인스레드가 해당 요청에 대한 결과값을 가져다 사용자에게 전달한다.

요청/반환 처리는 실제 하나의 메인 스레드가 모두 처리하지만 이벤트처리는 스레드풀의 여러 워커 스레드가 처리해준다.
하지만 이런 특성때문에 메인 스레드에서 에러발생시 서버가 종료되어 버린다.

효율적인 측면에서 이벤트가 발생할 때 마다 CPU 가 병목되는 톰캣보다는 Nodejs 가 좋다.

맥, 리눅스에서 npm을 사용해 라이브러리 설치시 발생하는 권한문제 해결방법
https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally

Nodejs 설치

https://nodejs.org/ko/

위 주소로 이동해 안정버전이 LTS를 설치하자.

설치가 다 되었다면 테스트를 위해 다음과 같은 파일을 생성

node.basic.js 내용은 아래 한줄 작성

console.log("hello world");

그리고 해당 파일 위치에서 $ node node.basic.js 명령 실행하면 hello world가 출력되어야 한다.

nodejs1

Nodejs 기본 모듈

JavaScript 를 사용한 프레임워크가 많아지면서 모듈화가 필요하게 되었다.

exports, require

C언어에서 헤더파일을 정의하듯이 정의된 함수, 객체 등을 외부로 반출하고 싶을때 exports 키워드를 사용한다.
require 를 사용해 해당 반출된 정의 속성, 메서드를 사용할 수 있다.

// moudule.js
exports.abs = function (number) {
    if (number > 0) {
        return number;
    } else {
        return -number;
    }
};

exports.circleArea = function (radius) {
    return radius * radius * Math.PI;
}

abscircleArea 메서드 정의 및 exports

// main.js
var module1 = require('./module.js');
console.log(module1.abs(-273)); // 273
console.log(module1.circleArea(3)); //274333882308138

모듈 생성시에는 exports, 모듈 사용시에는 require 를 사용한다.

디렉토리 내의 js 파일을 index.js 로 설정시에 디렉토리 명으로만 require 가능하다.

아래와 같이 area 디렉토리 밑에 index.js 정의

.
├── area
│   └── index.js
├── main.js
├── package-lock.json
└── package.json
// index.js
let PI = Math.PI;
let area = (r) => PI * r * r;
module.exports = {
    area: area,
    pi: PI
}

// main.js
const foo = require('./area')
let area = foo.area(10);
console.log(area); // 314.1592653589793
console.log(foo.pi); // 3.141592653589793

import, from

ES6 부터 모듈링 시에 사용하는 키워드(exports, require 도 사용 가능)

Nodejs 기본 내장 객체

Nodejs 에산 최상위에 해당하는 여러 전역 변수와 기본 내장 객체, 전역 메서드, 변수가 존재한다.

global, console, process, buffer, require(), __filename, __dirname, module, exports, Timeout, setTimeout, setInterval, setImmediate

console, setTimeout, setInterval, setImmediate 등의 객체 또는 메서드는 global 객체에 정의되어 있으며 global을 통해 공용 전역변수 정의가 가능하다. 유지보수를 위해 남발하면 안됨.

process 객체

주요 속성과 메서드

process.argv.forEach(function (item, index) {
    console.log(index + ":" + typeof(item) + ":", item);
    if (item == '--exit') {
        var exitTime = Number(process.argv[index+1]);
        setTimeout(function () {
            process.exit();
        }, exitTime);
    }
});
0:string: /usr/local/bin/node
1:string: /Users/gojiyong/Documents/nodejs/node.process.js
2:string: --exit
3:string: 10000
console.log("- process.evn : ", process.evn);
console.log("- process.version : ", process.version);
console.log("- process.versions : ", process.versions);
console.log("- process.arch : ", process.arch);
console.log("- process.platform : ", process.platform);
console.log("- process.connected : ", process.connected);
console.log("- process.execArgv : ", process.execArgv);
console.log("- process.exitCode : ", process.exitCode);
console.log("- process.mainModule : ", process.mainModule);
console.log("- process.release : ", process.release);
console.log("- process.memoryUsage() : ", process.memoryUsage());
console.log("- process.uptime() : ", process.uptime());
console.log("- process.uptime() : ", process.uptime());
console.log("- process.uptime() : ", process.uptime());
- process.evn :  undefined
- process.version :  v10.16.1
- process.versions :  { http_parser: '2.8.0',
  node: '10.16.1',
  v8: '6.8.275.32-node.54',
  uv: '1.28.0',
  zlib: '1.2.11',
  brotli: '1.0.7',
  ares: '1.15.0',
  modules: '64',
  nghttp2: '1.34.0',
  napi: '4',
  openssl: '1.1.1c',
  icu: '64.2',
  unicode: '12.1',
  cldr: '35.1',
  tz: '2019a' }
- process.arch :  x64
- process.platform :  darwin
- process.connected :  undefined
- process.execArgv :  []
- process.exitCode :  undefined
- process.mainModule :  Module {
  id: '.',
  exports: {},
  parent: null,
  filename: '/Users/gojiyong/Documents/nodejs/node.process2.js',
  loaded: false,
  children: [],
  paths:
   [ '/Users/gojiyong/Documents/nodejs/node_modules',
     '/Users/gojiyong/Documents/node_modules',
     '/Users/gojiyong/node_modules',
     '/Users/node_modules',
     '/node_modules' ] }
- process.release :  { name: 'node',
  lts: 'Dubnium',
  sourceUrl:
   'https://nodejs.org/download/release/v10.16.1/node-v10.16.1.tar.gz',
  headersUrl:
   'https://nodejs.org/download/release/v10.16.1/node-v10.16.1-headers.tar.gz' }
- process.memoryUsage() :  { rss: 26419200,
  heapTotal: 7061504,
  heapUsed: 4256752,
  external: 8272 }
- process.uptime() :  0.156

os 모듈

https://nodejs.org/dist/latest-v10.x/docs/api/os.html

const os = require('os')
console.log(os.hostname());
console.log(os.type());
console.log(os.platform());
console.log(os.arch());
console.log(os.release());
console.log(os.uptime());
console.log(os.loadavg());
console.log(os.totalmem());
console.log(os.freemem());
console.log(os.cpus());
console.log(os.networkInterfaces());

fs 모듈

https://nodejs.org/dist/latest-v10.x/docs/api/fs.html

const http = require('http');
const fs = require('fs');
const url = require('url')

const userList = [
    { name: 'ko', age: 25 },
    { name: 'ho', age: 24 },
    { name: 'jo', age: 27 },
]
fs.writeFileSync('./list.json', JSON.stringify(userList), (err) => {
    if (err) throw err;
    console.log('write success');
})
// 생성된 `list.json` 데이터
// [{"name":"ko","age":25},{"name":"ho","age":24},{"name":"jo","age":27}]

fs.readFile('./list.json', (err, data) => {
    if (err) throw err;
    const json = JSON.parse(data);
    console.log(json[0].name); // ko
    console.log(json[1].name); // ho
    console.log(json[2].name); // jo
})

http 모듈

https://nodejs.org/dist/latest-v10.x/docs/api/http.html

아래처럼 request(IncomingMessage), response(ServerResponse), fs 모듈을 사용해 html 데이터 반환

const server = http.createServer((request, response) => {
    let pahtName = url.parse(request.url).pathName;
    if (pathName === '/') {
        fs.readFile('./136.index.html', (err, data) => {
            response.writeHead(200, { 'Content-type': 'text/html' });
            response.end(data);
            console.log(url.parse(request.url));
        })
    } else if (pathName === './example') {
        fs.readFile('./136.example.html', (err, data) => {
            response.writeHead(200, { 'Content-type': 'text/html' });
            response.end(data);
            console.log(url.parse(request.url));
        })  
    }
}).listen(5000, () => {
    console.log('server is running, http://localhost:5000');
});

http - event

const http = require('http');

const server = http.createServer().listen(5000, () => {
    console.log('server is running, http://localhost:5000');
});

// client request 시 console 에 출력
server.on('request', () => {
    console.log('request')
})

클라이언트 request 시에 설정한 콜백함수 (이벤트) 발생, request 외에 아래와 같은 이벤트가 있음.

const http = require('http');

const server = http.createServer((request, response) => {
    response.writeHead(200, {
        'Content-Type': 'text/html',
        'Set-Cookie': ['soju=grill', 'beer=chicken']
    });
    response.end(`<h1>${request.headers.cookie}</h1>`)
}).listen(5000, () => {
    console.log('server is running, http://localhost:5000');
});

request.headers.cookie 속성에서 가져올 수 있으며 아래와 같은 string type 의 데이터가 저장된다.
soju=grill; beer=chicken

splitmap 을 사용해 객체로 추출 가능

const cookie = request.headers.cookie.split(';').map((element) => {
    element = element.split('=');
    return { name: element[0], value: element[1] };
});
response.end(`<h1>${JSON.stringify(cookie)}</h1>`)
출력값
[{"name":"soju","value":"grill"},{"name":" beer","value":"chicken"}]

url.parse

https://nodejs.org/dist/latest-v10.x/docs/api/url.html

url.parse(urlStr, [parseQueryString], [slashesDenoteHost])

urlStr 문자열을 아래 Url 객체로 반환

interface Url {
    auth: string | null;
    hash: string | null;
    host: string | null;
    hostname: string | null;
    href: string;
    path: string | null;
    pathname: string | null;
    protocol: string | null;
    search: string | null;
    slashes: boolean | null;
    port: string | null;
    query: string | null | ParsedUrlQuery;
}
┌────────────────────────────────────────────────────────────────────────────────────────────────┐
│                                              href                                              │
├──────────┬──┬─────────────────────┬────────────────────────┬───────────────────────────┬───────┤
│ protocol │  │        auth         │          host          │           path            │ hash  │
│          │  │                     ├─────────────────┬──────┼──────────┬────────────────┤       │
│          │  │                     │    hostname     │ port │ pathname │     search     │       │
│          │  │                     │                 │      │          ├─┬──────────────┤       │
│          │  │                     │                 │      │          │ │    query     │       │
"  https:   //    user   :   pass   @ sub.example.com : 8080   /p/a/t/h  ?  query=string   #hash "
│          │  │          │          │    hostname     │ port │          │                │       │
│          │  │          │          ├─────────────────┴──────┤          │                │       │
│ protocol │  │ username │ password │          host          │          │                │       │
├──────────┴──┼──────────┴──────────┼────────────────────────┤          │                │       │
│   origin    │                     │         origin         │ pathname │     search     │ hash  │
├─────────────┴─────────────────────┴────────────────────────┴──────────┴────────────────┴───────┤
│                                              href                                              │
└────────────────────────────────────────────────────────────────────────────────────────────────┘
(All spaces in the "" line should be ignored. They are purely for formatting.)

parseQueryString(boolean): true 지정시 반환타입이 UrlWithParsedQuery, 기본값 false

interface UrlWithParsedQuery extends Url {
    query: ParsedUrlQuery; // dict 형식의 데이터
}

slashesDenoteHost(boolean): true 지정시 // 뒤의 값을 host, 그 뒤의 값을 path 로 인식. http:// 가 붙으면 true, false 상관 없이 자동으로 파싱하기에 별도 설정할 필요 없음. 기본값 false

const http = require('http');
const fs = require('fs');
const url = require('url')

const get1 = url.parse('//127.0.0.1:5000/path?test=1234#hash', true, true);
const get2 = url.parse('//127.0.0.1:5000/path?test=1234#hash', true, false);
console.log(get1.host, get1.path)
console.log(get2.host, get.path)

출력값

127.0.0.1:5000 /path?test=1234
null '/path?test=1234'

유용한 실행도구들

supervisor

supervisor 는 개발시 유용한 도구로 파일이 수정되면 알아서 서버를 재실행해준다.
사용법은 node 와 똑같이 뒤에 파일명을 지정하면 됨.

$ npm install -g supervisor
$ supervisor <파일명>

forever

forever 백그라운드 서버 실행 툴로 서버를 뒤에서 관리할 수 있음.
크게 start, restart, list, stop 명령어가 있음.

$ npm install -g forever
$ forever start app.js
warn:    --minUptime not set. Defaulting to: 1000ms
warn:    --spinSleepTime not set. Your script will exit if it does not stay up for at least 1000ms
info:    Forever processing file: app.js
$ forever restart 0
info:    Forever restarted process(es):
data:        uid  command             script forever pid   id logfile                           uptime  
data:    [0] b9ZY /usr/local/bin/node app.js 16189   16196    /Users/gojiyong/.forever/b9ZY.log 0:0:0:5 
$ forever list
info:    Forever processes running
data:        uid  command             script forever pid   id logfile                           uptime      
data:    [0] b9ZY /usr/local/bin/node app.js 16189   16203    /Users/gojiyong/.forever/b9ZY.log 0:0:0:3.847 
$ forever stop 0
info:    Forever stopped process:
    uid  command             script forever pid   id logfile                           uptime      
[0] b9ZY /usr/local/bin/node app.js 16189   16203    /Users/gojiyong/.forever/b9ZY.log 0:0:0:7.741 

맨앞의 숫자 0 외에도 uid, script 로도 restart, stop 명령 실행 가능

pm2

forever 와 비슷하게 백그라운드 실행, 관리툴

$ pm2 start app.js
[PM2] Applying action restartProcessId on app [app](ids: 0)
[PM2] [app](0) ✓
[PM2] Process successfully started
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
│ id │ name               │ mode     │ ↺    │ status    │ cpu      │ memory   │
├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
│ 0  │ app                │ fork     │ 1    │ online    │ 0%       │ 7.1mb    │
└────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘

$ pm2 stop app
[PM2] Applying action stopProcessId on app [app](ids: 0)
[PM2] [app](0) ✓
┌────┬────────────────────┬──────────┬──────┬───────────┬──────────┬──────────┐
│ id │ name               │ mode     │ ↺    │ status    │ cpu      │ memory   │
├────┼────────────────────┼──────────┼──────┼───────────┼──────────┼──────────┤
│ 0  │ app                │ fork     │ 1    │ stopped   │ 0%       │ 0b       │
└────┴────────────────────┴──────────┴──────┴───────────┴──────────┴──────────┘

lodash

java 의 stream 같은 메서드

http://kbs0327.github.io/blog/technology/lodash/