Node.js 요즘에 주목 받고 있는 언어 같다.


Node.js 는 자바스크립트, 구글 V8 엔진을 사용 하는데, 자바스크립트는 개발자들에게 익숙한 언어이고, 웹 애플리케이션 서버 개발시 직접 구현도 가능 하지만 Expressjs 라는 대표적인 웹프레임워크를 많이 사용 하는 편이다.


전화통화로 몇일 전 위 이유 때문에 많이 사용 하지 않을까? 라고 말했던 형도 있었다.


Express 공식 사이트


여기서 한가지 짚고 넘어갈 것이 있다.


Node 공식 사이트 소개 글 

Node.js is a platform built on Chrome's JavaScript runtime for easily building fast, scalable network applications. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient, perfect for data-intensive real-time applications that run across distributed devices.


위 내용을 보자면 쉽게 확장이 가능한 네트워크 애플리케이션을 구축 하기 위한 플렛폼이고, 추가적인 기능으로 이벤트 기반, non-blocking I/O 를 지원 한다.


웹프레임워크로 상대적으로 쉽게 개발을 하기 때문에 망각 할 수 있지만, 기억하고 있어야 할 부분이라고 본다.


Node.js 기반으로 만들어진 놀라운 10가지 프로젝트가 있다. 그냥 읽고만 넘기기엔 내용이 좋다고 판단이 되어 글을 남겨 본다.


이거 정말 필요할 것 같다. 라고 생각 된건 두 가지!! 


실제 개발이 완료 된 후 운영에 들어간다면 데스크톱 애플리케이션 처럼 사용하면서 정보를 가공할수 있는 Node-Webkit, 서버 로그 관리 차원에서 훌륭한 도구가 될 Log.io 보고 와~ 했다.


  [그림 1] log.io 공식 페이지


틈틈히 한번씩을 사용해봐야 겠다. 먼가 도움이 많이 될거 같다.


※ 참고 사이트

  - http://nodejs.org/

  - http://www.itworld.co.kr/slideshow/85221




Express middleware는 Java 진영의 Springframework의 interceptor, aop 와 비슷한 성향을 가지고 있습니다.



위 그림을 참고 해보자.


서버로 요청이 들어오면 middleware 는 route / 모든 주소에 대해서 처리하라는 지시를 받습니다. 그 후 특정 url 에 대해서 처리를 한 후 응답을 하게 됩니다.


실제로 log를 확인해 봤습니다.


app object 중 stack

  stack: 
   [ { route: '', handle: [Function: query] },
     { route: '', handle: [Function: expressInit] },
     { route: '', handle: [Function: logger] },
     { route: '', handle: [Function: cookieParser] },
     { route: '', handle: [Function: bodyParser] },
     { route: '', handle: [Function: methodOverride] },
     { route: '', handle: [Function: test] },
     { route: '', handle: [Function: session] },
     { route: '', handle: [Function] },
     { route: '', handle: [Function] },
     { route: '', handle: [Function: router] },
     { route: '', handle: [Function: errorHandler] } ]


app object 중 router

_router: 
   { map: 
      { get: 
         [ { path: '/test/test',
             method: 'get',
             callbacks: [ [Function] ],
             keys: [],
             regexp: /^\/test\/test\/?$/i },
            ....
         ],
         post: 
         [ { path: '/test/test',
             method: 'get',
             callbacks: [ [Function] ],
             keys: [],
             regexp: /^\/test\/test\/?$/i },
            ....
         ]
      }
   }


서버로 request가 들어오는 이벤트가 발생할 경우 stack 을 통과하게 됩니다. 

개발자가 직접 정의 하는 middleware 는 test 라는 함수이고, 모든 url 처리는 stack 를 통과하고 특정 url은 router 에게 처리를 위임 하게 됩니다. 실제 code를 보도록 하자.


./express/lib/application.js 중 일부 입니다.

/** * Proxy `connect#use()` to apply settings to * mounted applications. * * @param {String|Function|Server} route * @param {Function|Server} fn * @return {app} for chaining * @api public */ app.use = function(route, fn){ var app; // default route to '/' if ('string' != typeof route) fn = route, route = '/'; // express app if (fn.handle && fn.set) app = fn; // restore .app property on req and res if (app) { app.route = route; fn = function(req, res, next) { var orig = req.app; app.handle(req, res, function(err){ req.app = res.app = orig; req.__proto__ = orig.request; res.__proto__ = orig.response; next(err); }); }; } connect.proto.use.call(this, route, fn); // mounted an app if (app) { app.parent = this; app.emit('mount', this); } return this; };

"default route to '/'" 내부적으로 기본 값으로 설정이 되는 것을 확인 할 수 있습니다. 


이제 실제 사용법을 알아보도록 하자.



http://expressjs.com/api.html#app.use 공식 홈페이지 Reference 자료 입니다.

두번째 인자로 function 이 사용 됩니다.


위 자료를 참고 하여 Sample Code 작성 해보자.

함수형으로 return 하자.

function test() {
  return function test(req, res, next) {
    console.log('test');   

    next();
  }
}

next() 는 stack 의 다음 주소로 가라는 함수 입니다. 실제로 next() 를 호출 하지 않는 경우 응답을 하지 못하고 서버에서 멈춰버리게 됩니다.


app.js 설정의 일부 입니다. 실제로 적용 하자.

function setting() {
  app.configure(function(){
    app.set('port', process.env.PORT);
    app.use(express.logger('dev'));
    app.use(express.cookieParser());
    app.use(express.bodyParser());
    app.use(express.methodOverride());

    // user middleware
    app.use(test());
    
    // session config
    app.use(express.session({
      secret: config.redis.secret,
      store: new RedisStore(config.options),
      cookie: config.cookie
    }));

    app.use(passport.initialize());
    app.use(passport.session());

    // router
    app.use(app.router);
  });  
}

use 함수 호출 하는 부분을 볼 수 있는데, 해당 행위는 모든 요청에 대해서 처리를 하겠다는 말입니다. 

위와 같이 적용 하면 모든 요청에 대해서 test 함수가 호출 되는 것을 확인 할 수 있습니다.


※ 참고 사이트: http://expressjs.com/



nodejs logging 라이브러리에서 winston이 활발하게 사용 되고 있다. 사용 하다보면 시간이 안맞는 경우가 발생 하기도 한다.


ntp 타임서버 설정을 해서 linux 상에서 time 확인을 해보았다.

2013. 09. 23. (월) 12:02:11 KST

정상적으로 나온다. 하지만 log 에서는 -9시간 전으로 나온다.

2013-09-23T03:02:11.913Z ...

해결 방법

../winston/lib/winston/common.js 파일을 수정 한다.

//
// ### function timestamp ()
// Returns a timestamp string for the current time.
//

exports.timestamp = function () {
  // 수정
};

위 function 을 재 구현하면 시간이 원하는 포맷으로 출력 된다.


※ 참고 사이트

  - nodejs/winston: https://github.com/flatiron/winston


'Nodejs' 카테고리의 다른 글

Nodejs Memory 관리  (0) 2013.11.11
Express req.param 사용 하기  (0) 2013.09.23
Nodejs cluster process 값 공유 하기  (0) 2013.08.29
Nodejs Express logger format 설정 하기  (0) 2013.08.01
Nodejs pg library connection pool 관련  (0) 2013.05.16


Sample Code.

Express와 같이 사용 하였다.

var express = require('express')
  , routes = require('./routes')
  , user = require('./routes/user')
  , http = require('http')
  , path = require('path');

var app = express();

var cluster = require('cluster')
  , numCPUs = require('os').cpus().length;

function setting() {
  app.configure(function(){
    app.set('port', process.env.PORT || 3000);
    app.set('views', __dirname + '/views');
    app.set('view engine', 'ejs');
    app.use(express.favicon());
    app.use(express.logger('dev'));
    app.use(express.bodyParser());
    app.use(express.methodOverride());
    app.use(express.cookieParser('your secret here'));
    app.use(express.session());
    app.use(app.router);
    app.use(express.static(path.join(__dirname, 'public')));
  });

  app.configure('development', function(){
    app.use(express.errorHandler());
  });

  app.get('/', routes.index);
  app.get('/users', user.list);

  http.createServer(app).listen(app.get('port'), function(){
    console.log("Express server listening on port " + app.get('port'));
  });
}

function eachWorker(callback) {
  for (var id in cluster.workers) {
    callback(cluster.workers[id]);
  }
}

// master
if (cluster.isMaster) {
  for (var i=0; i<numCPUs; i++) {
    var worker = cluster.fork();

    worker.on('message', function(message) {
      if (message.cmd == 'broadcast') {
        console.log('send worker');

        setInterval(function() {
          eachWorker(function(_worker) {
            _worker.send({ cmd: 'broadcast', message: 'test message' });
          })
        }, 1000);
      }
    });
  }

  cluster.on('exit', function(worker, code, signal) {
    log.info('worker ' + worker.process.pid + ' died');
  });

// worker
} else {
  if (cluster.isWorker) {
    console.log(' I am worker ' + cluster.worker.id);

    process.on('message', function(message) {
      if (message.cmd == 'broadcast') {
        console.log('receive message: ' + message.message);
      }
    });

    process.send({ cmd: 'broadcast' });
  }
}

1.1  Master (Parent)

부모 프로세스 로 볼 수 있으며 자식 프로세스를 생성 한다. 위 코드를 참고 시 'broadcast' 라는 명령어로 event 감지 하여 데이터를 주고 받는다.


1.2  Worker (Child)

부모 프로세스 가 fork() 할 경우 생성 된다. 해당 프로세스는 생성 되는 시점에서 'message' 라고 명령어를 보낸다.


1.3  Test 

sample를 실행해 보자.


whitelife@whitelife:~/work/cluster_test$ node app.js 

 I am worker #1

send worker.....

 I am worker #2

send worker.....

receive message: test message

receive message: test message


※ 참고 사이트

  - node api: http://www.nodejs.org/api/cluster.html



app.js 파일 중 일부

// ..
app.set('view engine', 'ejs');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.cookieParser());
app.use(express.bodyParser());
// ..

log 파일 중 일부

GET / 200 119ms - 505b

GET /stylesheets/style.css 304 27ms


log를 좀더 자세히 보고 싶은 경우

app.use(express.logger({ format: ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"' }));


format 설정을 위와 같이 넣어 준다.

192.168.0.xx - - [Thu, 01 Aug 2013 01:53:19 GMT] "GET / HTTP/1.1" 200 505 "-" "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.72 Safari/537.36"


위와 같이 상세하게 정보를 볼 수 있다.




npm 저장소에 있는 pg library 를 설치 할 경우 아래 경고 메시지가 적용이 된다.


WARNING!!

pg.connect(function(err, client) { ...}) is deprecated and will be removed it v1.0.0 (very soon)

instead, use pg.connect(function(err, client, done) { ... })

automatic releasing of clients back to the pool was a mistake and will be removed

please see the following for more details:

https://github.com/brianc/node-postgres/wiki/pg

https://github.com/brianc/node-postgres/issues/227

https://github.com/brianc/node-postgres/pull/274

feel free to get in touch via github if you have questions


- Connect

기존 방식

pg.connect(function(err, client) { ...});


위의 형태로 connection을 생성 한다면 connection pool에 반환이 되지 않으므로 pool error 가 발생 한다.

connection 자원을 받을 수 없으므로 db와의 통신 불능 상태가 된다.


해결 방법


Result 1.  connect 방식 변경

pg.connect(function(err, client, done) { 

  // ...

  done();

});


done을 call 함으로서 connection pool 에 자원을 반환 한다.


Result 2.  version 0.14.1 이용

0.14.1 을 이용하면 기존 방식으로도 통신이 가능 하다.




Nodejs, npm 은 설치 되어 있다고 가정 한다.


Step 1.  express 설치 하기

-g 옵션을 주게 되면 전역으로 설치 된다.


whitelife@whitelife:~/work$ sudo npm install -g express


Step 2.  프로젝트 생성 하기

생성하기 전에 도움말부터 보자.


whitelife@whitelife:~/work$ express --help


Usage: express [options]


  Options:


    -h, --help          output usage information

    -V, --version       output the version number

    -s, --sessions      add session support

    -e, --ejs           add ejs engine support (defaults to jade)

    -J, --jshtml        add jshtml engine support (defaults to jade)

    -H, --hogan         add hogan.js engine support

    -c, --css <engine>  add stylesheet <engine> support (less|stylus) (defaults to plain css)

    -f, --force         force on non-empty directory


참고해야 하는 옵션은 보통 view, session 추가 설정 부분 이다. 

이제 프로젝트를 생성해 보자.

view engine 은 따로 설정을 하지 않으면 jade로 설정 된다.


whitelife@whitelife:~/work$ express -s -e whitelife


   create : whitelife

   create : whitelife/package.json

   create : whitelife/app.js

   create : whitelife/public

   create : whitelife/public/javascripts

   create : whitelife/public/images

   create : whitelife/public/stylesheets

   create : whitelife/public/stylesheets/style.css

   create : whitelife/views

   create : whitelife/views/index.ejs

   create : whitelife/routes

   create : whitelife/routes/index.js

   create : whitelife/routes/user.js


   install dependencies:

     $ cd whitelife && npm install


   run the app:

     $ node app


whitelife@whitelife:~/work$ 


Step 3.  의존성 모듈 설치 하기

프로젝트에 대한 정의는 package.json을 참고 하자. 생성한 프로젝트 디덱토리를 보면 찾을 수 있다.


whitelife@whitelife:~/work/whitelife$ ll

합계 28

drwxr-xr-x  5 whitelife whitelife 4096  5월  3 11:24 ./

drwxrwxr-x 25 whitelife whitelife 4096  5월  3 11:24 ../

-rw-rw-r--  1 whitelife whitelife  937  5월  3 11:24 app.js

-rw-rw-r--  1 whitelife whitelife  181  5월  3 11:24 package.json

drwxr-xr-x  5 whitelife whitelife 4096  5월  3 11:24 public/

drwxr-xr-x  2 whitelife whitelife 4096  5월  3 11:24 routes/

drwxr-xr-x  2 whitelife whitelife 4096  5월  3 11:24 views/

whitelife@whitelife:~/work/whitelife$ vim package.json 


package.json

{

  "name": "application-name",

  "version": "0.0.1",

  "private": true,

  "scripts": {

    "start": "node app"

  },  

  "dependencies": {

    "express": "3.1.0",

    "ejs": "*" 

  }

}


npm을 이용하여 의존성 모듈을 설치하는데 위 dependencies 속성을 메타 정보로 활용 한다.

java 진영의 소스 관리 툴 maven 과 흡사하다고 생각 하면 된다.

이제 의존성 모듈을 설치 하자.


whitelife@whitelife:~/work/whitelife$ npm install -d

npm info it worked if it ends with ok

npm info using npm@1.2.18

npm info using node@v0.10.5

npm WARN package.json application-name@0.0.1 No README.md file found!

npm info preinstall application-name@0.0.1

npm info trying registry request attempt 1 at 11:32:05

npm http GET https://registry.npmjs.org/ejs

npm info trying registry request attempt 1 at 11:32:05


// ...... ing

ejs@0.8.3 node_modules/ejs

express@3.1.0 node_modules/express
├── methods@0.0.1
├── fresh@0.1.0
├── range-parser@0.0.4
├── cookie-signature@0.0.1
├── buffer-crc32@0.1.1
├── cookie@0.0.5
├── commander@0.6.1
├── debug@0.7.2
├── mkdirp@0.3.3
├── send@0.1.0 (mime@1.2.6)
└── connect@2.7.2 (pause@0.0.1, bytes@0.1.0, formidable@1.0.11, qs@0.5.1)
npm info ok 


설치가 완료되면 의존성 모듈의 구조를 볼 수 있다.

심심할 때 마다 보다보면 좋은 모듈도 많다. mkdirp 를 여기서 보다가 참고해서 사용하고 있다.~ 


따로 모듈을 추가해야 할 경우 --save 옵션을 주면 package.json 에 같이 적용 된다.

whitelife@whitelife:~/work/whitelife$ npm install --save super

whitelife@whitelife:~/work/whitelife$ vim package.json 


// ...

"dependencies": {

  "express": "3.1.0",

  "ejs": "*" 

  "super": "~0.2.1" 

}

// ...


위와 같이 적용 한다면 해당 프로젝트를 다른 PC에 셋팅을 해야 한다고 해도 npm install -d 로 한번에 의존성 모듈 설치가 완료 된다. 


Step 4.  프로젝트 실행 하기

app.js 가 express의 설정 파일이라고 생각 하면 된다. 실행 해보자.


whitelife@whitelife:~/work/whitelife$ node app.js 

Express server listening on port 3000


브라우저를 띄워서 확인 하자.



여기까지 같이 왔다면 성공이다.~ 


※ 참고 사이트: http://expressjs.com/


 

var mkdirp = require('mkdirp');

mkdirp('/tmp/foo/bar/baz', function (err) {
    if (err) console.error(err)
    else console.log('pow!')
});

 

node.js file system 을 확장 해서 사용하는 라이브러리 유용하다~~.

 

https://github.com/substack/node-mkdirp

 

 

+ Recent posts