[project] Node.js Happy Hacking 첫번째 이야기

소개

Node.js Black Edition 개인 프로젝트관련 내용을 전달해드립니다.

서버 솔루션의 대부분은 configure 옵션과 확장모듈이 존재할시 옵션설정으로 설치가 가능하도록 제공하고 있습니다. 실제 확장모듈의 대중화는 서버 솔루션 패키지의 포함유무에 달려 있다고 해도 과언이 아닙니다. PHP를 예로 들어보겠습니다. PHP의 확장모듈은 pecl을 통해서 설치되고 있습니다. PHP 소스트리의 ext 디렉토리안에는 인증되거나 많이 사용되어지는 확장모듈이 점점 많아지는 추세입니다. 요즘은 어떨런지 모르겠네요. 그리고, 이 모듈들의 설치방법은 php configure 옵션과 공유객체를 추가할 수 있도록 phpize 명령에 의해 결정됩니다. 이 경우의 장점은 100대 서버에 PHP를 설치하고, 별도로 pecl을 통해서 확장모듈을 설치하는 번거로움을 없앨 수 있고, 개발자의 입장에서 확장모듈을 설치하는 어려움 없이, 편하게 인증된 확장모듈을 바로 사용할 수 있는 장점이 존재합니다. 이런 결과 PHP에서 대표적으로 인기를 얻은 확장모듈은 curl 입니다. 기존에는 fsockopen을 사용하여 다양한 Request 옵션을 부여하기가 어려웠고, keep-alive 흉내기기조차 어려웠습니다. (옛날 일이긴 하죠 ㅋ) 그런데, PHP 기본 확장모듈로 탑재되고, configure option중 --enable-curl 옵션만 주면 누구나 쉽게 사용할 수 있게 되었습니다.

node.js도 확장모듈을 설치할 수 있는 막강한 npm 도구가 있습니다. 이곳에 존재하는 확장모듈 중 인증되고 인기있는 것은 기본 탑재해주면 안될까 라는 생각이 들었습니다. 그럼 어떻게 해야할까요? node.js를 hacking한다? 반대로 node.js는 hacking 할 수 있도록 자리를 마련해두었습니다. (어디??) 의외로 간단하고, 이번 연재글에서는 탑재하는 방법까지만 소개해드리고, 다음편에서는 v8에서 제공하는 js2c 모듈에 의하여 node native module이 탑재되는 과정을 설명하겠습니다.

개요

console API 객체는 브라우져에서 제공하는 것과 달리 logging message의 prefix에는 아무것도 붙지를 않아, 겉으로 보기에 어떤 레벨의 로그인지 알 수 없습니다. console.info는 느낌표, console.warn는 경고표시, 기타 색상들.. 이런거 node.js에 기본적으로 사용할 수 있으면 좋지 아니할까?

@firejune님이 제작하신 clog의 제작의도를 찾아보면 아래와 같이 작성해놓았습니다.

"Node.JS 코드를 다루면서 조금 아쉬운점 하나는 콘솔 디버깅이었습니다. 이게 console.log, console.info, console.warn 그리고 console.error를 구분하지 못하고 모두 일반 텍스트로 나오는 것이었습니다." - [http://bit.ly/ApN0N7 블로깅에서 발췌]

자 그럼 clog module을 NativeModule로 탑재시켜보겠습니다.

native module로서 clog 추가

설치과정

전제: 리눅스

빌드과정

기존 node를 설치했다는 가정이고, make 해주면 됩니다. 그러면 총 35단계로 빌드과정이 구성되는데 3, 9, 35단계만 진행하게 됩니다. 왜냐하면 lib 디렉토리의 변화를 WAF 가 감지했기 때문입니다.

$ make
Waf: Entering directory `/home/nanhadev/node-v0.6.9/out'
DEST_OS: linux
DEST_CPU: x64
Parallel Jobs: 1
Product type: program
[ 3/35] src/node_natives.h: src/node.js lib/dgram.js lib/console.js lib/buffer.js lib/querystring.js lib/punycode.js lib/http.js lib/net.js lib/foo.js lib/vm.js lib/util.js lib/module.js lib/clog.js lib/_debugger.js lib/assert.js lib/fs.js lib/child_process.js lib/os.js lib/dns.js lib/events.js lib/url.js lib/tls.js lib/crypto.js lib/sys.js lib/freelist.js lib/https.js lib/stream.js lib/readline.js lib/_linklist.js lib/buffer_ieee754.js lib/tty.js lib/cluster.js lib/repl.js lib/path.js lib/string_decoder.js lib/timers.js lib/zlib.js lib/constants.js -> out/Release/src/node_natives.h
......
......
......
Waf: Leaving directory `/home/nanhadev/node-v0.6.9/out'
'build' finished successfully (4.927s)
-rwxr-xr-x 1 root root 9.4M  2월 19 21:41 out/Release/node

위 빌드과정을 보면 lib 디렉토리안에 제가 임의로 추가한 clog.jsfoo.js가 보입니다. [ 3/35] 과정은 Node docu 에서 소개하는 NativeModule을 빌드하는 과정이며, 결국 out/Release/src/node_natives.h 라는 헤더파일을 제작하게 됩니다. 열어보시면 static C의 형태로 숫자로 변환되어 저장되어 있습니다. 이것은 node.cc에서 헤더로 선언되어 v8엔진으로 컴파일되어지는 과정을 거치게 됩니다.

설치확인

#######################
# not npm install
#######################
$ vi test.js

var clog = require('clog');
clog.log('hello', 'world');                      // console.log
clog.info(['foo', 'bar']);                       // console.info
clog.warn('baz is deprecated.');                 // console.warn
clog.error('HTTP/1.1 400 Bad Request');          // console.error
clog.debug('headers', {                          // console.debug
    'Content-Type': 'text/javascript'
});

위와 같이 예제파일을 만들고 실행해보겠습니다. 굳이 make install 하지 않아도 node 소스트리 제일 상위에 마지막 빌드과정에서 추출된 out/Release/node 을 가리키고 있는 node 파일을 사용하면 됩니다.

$ ./node test.js
log: hello world
info: [ 'foo', 'bar' ]
warn: baz is deprecated.
error: HTTP/1.1 400 Bad Request
debug: headers { 'Content-Type': 'text/javascript' }

자 위와같이 npm으로 설치하지 않아도 기본적으로 clog를 require으로 사용할 수 있습니다. 다음으로 한발짝 더 나아가 console과 같이 global 객체에 포함시켜 require하지 않고도 사용는 방법을 소개하겠습니다.

require하지 않고 Global 객체로서 사용하는 방법

위에서 자바스크립트로 제작된 라이브러리를 Native module으로 설치를 했습니다. 이것을 console 객체와 같이 global 하게 사용할 수 있는 방법을 소개합니다.

src/node.js 파일을 오픈

해당 파일은 src/node.cc에서 실행되는 과정 중 callback pattern (NativeModule addon 참조)으로 실행되어 process 인자를 전달받게 됩니다. 173 라인에 (적당히) 아래와 같이 선언하는 부분이 있습니다. 참고로 console 선언 부분도 확인할 수 있습니다.

startup.globalClog = function() {
    // global 객체에 선언되어지는 모듈명
    global.__defineGetter__('clog', function() {
        // lib 디렉토리에 존재하는 모듈명
        return NativeModule.require('clog');
    }); 
};  

위와 같이 NativeModule.require 사용 (lib 디렉토리참조) 하여 clog를 불러오고 이를 global 객체에 defineGetter으로 객체명을 지정하면 됩니다. 혹은 global.property = ... 이라고 선언해도 무관합니다.

44번 라인 (적당히) 에 아래구문을 삽입 후 make

startup.globalClog();

require 없이 clog를 불러와 보세요.

# ./node
> clog
{ [Function]
  log: [Function],
  error: [Function],
  warn: [Function],
  info: [Function],
  debug: [Function],
  configure: [Function] }

후후 global 객체로서 사용할 수 있게 되었군요. ^^

결론

언젠가 node.js 에도 기본 확장모듈이 마음대로 빌드될 수 있기를 기다려 봅니다. 아님 말구. ㅋㅋ

KIN 플~

추천 관련글