인사이드 자바스크립트 출간 기념 2천원 할인 쿠폰 배너

드디어 자바스크립트를 주제로 한 책을 출간하게 되었다.
사진

사실 집필은 좀더 일찍 마무리되었는데, 이래저래 시간이 지연되면서, 2014년을 맞이하면서 책이 출간되었다.
타이밍 맞게 출간되었다면, 훨씬 뿌듯하고 좋았을텐데.. 너무 늦게 나와 아쉬운 마음이 더 크게 든다.

아래는 2천원 할인 쿠폰을 받을 수 있는 배너입니다. 교보문고와 yes24에서 할인받을 수 있도록 프로모션을 진행중입니다.
2014년 1월 19일까지 진행되니 많은 참여바랍니다.

[ YES24 ]
http://me2.do/Gj9HJKb5

이곳으로 접속하시면 예스24 회원 로그인을 하라는 메시지가 뜹니다(회원 로그인을 해야 쿠폰이 발급됩니다).
로그인을 하면 2천원 할인쿠폰이 발급됩니다.
발급된 쿠폰은 메뉴 상단 [마이페이지]를 클릭하고, 이어서 나오는 페이지의 좌측 [쿠폰]을 클릭하면 확인할 수 있습니다.

<인사이드 자바스크립트> 구입 시에만 적용되는 2천원 할인쿠폰이 보입니다.

[ 교보문고 ]
http://www.kyobobook.co.kr/index.laf?emailCheck=true&couponId=CB5IOcZhIUaqOI6MgsKdOQ~

이곳으로 접속하시면 교보문고 로그인을 하라는 메시지가 뜹니다(회원 로그인을 해야 쿠폰이 발급됩니다).
로그인을 하면 2천원 할인쿠폰이 발급됩니다.
발급된 쿠폰은 메뉴 상단 [마이룸]를 클릭하고, 이어서 나오는 페이지의 좌측 [할인쿠폰]을 클릭하면 확인할 수 있습니다.

<인사이드 자바스크립트> 구입 시에만 적용되는 2천원 할인쿠폰이 보입니다.

socket.io 를 활용한 실시간 survey 예제

socket.io를 활용한 예제를 한 번더 살펴보도록 하겠다.
이에 앞서 socket.io를 설명한 포스트와 예제 포스트를 한번 보시고 본문을 보시면 이해가 더 빠를 것이다.

8회 연재 – socket.io (웹소켓 통신)

Socket.IO를 활용한 예제 분석

이번 예제는 실시간 인터넷 투표 예제를 만들어보려고 한다.

다음 문서에서 같은 주제를 구현한 예제에 대한 설명이 있다.

http://www.netmagazine.com/tutorials/code-real-time-survey-html5-websockets

처음에는 위 문서를 바로 변역해보려고 했는데, 해당 문서에서는 서버는 WebSocket, 클라이언트는 ruby 를 이용하여 구현되어 있었다.
그래서, 그냥 결과물은 위 문서와 같게 하되, 서버는 socket.io, 클라이언트는 javascript를 이용하여 구현하기로 하였다.

그리고 이 포스트에서 구현한 코드는 다음 github에 공개되어 있으니, 참고하기 바란다.
https://github.com/zzoon/realtime-survey

먼저 server code를 보자 – server.js

  var io;

  io = require('socket.io').listen(4000);

  io.sockets.on('connection', function(socket) {
    socket.on('data-changed', function(data) {
	  console.log("data-changed received!");
      socket.broadcast.emit('data-changed', {
	  	index: data.index,
        vote: data.vote + 1
      });
    });
  });

역시 socket.io를 쓰니 굉장히 간단히 구현이 된다. 사실 예제니까 그렇다.^^

간단하게 설명하자면,

io.sockets.on.('connection', function(socket) { });

클라이언트로 연결 요청이 오면, 연결하고 두번 째 인자인 anonymous function을 실행시킨다.

socket.on('data-changed', function(data) { ... });

서버가 리스닝을 하고 있다가, 클라이언트로부터 ‘data-changed’ 이벤트가 오면 두번 째 인자인 anonymous function을 실행시킨다.

socket.boradcast.emit('data-changed', { ... });

‘data-changed’ 이벤트를 두번째 인자인 object와 함께 broadcasting 한다. ( 연결되어 있는 모든 클라이언트에게 해당 이벤트를 전달한다. )
broadcasting 되는 object는 index, vote 를 가지는데, index는 클라이언트의 버튼 index이고, vote는 현재 해당 버튼에 대한 vote 값을 1을 더해 전달한다.
이에 대해서는 밑에 client 예제를 설명할 때 이해할 수 있을 것이다.

위에서 보듯이 서버가 하는일은 딱 3가지다.
(물론 제대로 실시간 투표 시스템을 구현하려고 한다면, 이보다 훨씬 더 많은 일을 해야할 것이다.)

client 예제를 보자.
참고로 클라이언트는 다음과 같은 화면이 나오도록 구현할 것이다.

[index.html]
(1) visualize jquery plugin : 그래프를 표현하기 위해, 여러가지 플러그인을 찾아보았는데, visualize 가 꽤 괜찮아 보여서, 채택했다. 더 마음에 드는 것이 있다면, 그것을 사용해보도록 하자.
참고로 visualize에 대한 자세한 사항은 http://www.filamentgroup.com/lab/update_to_jquery_visualize_accessible_charts_with_html5_from_designing_with를 참고하도록 하자.

(2) index.html
- table 태그 안의 구조는 visualize가 해석할 수 있도록 만들어야 한다. 이는 사용하는 플러그인마다 조금씩 다르므로, 주의하도록 하자.
- visualize로 표현된 그래프 밑에 사용자가 입력할 수 있는 버튼을 추가하였다. 각 버튼마다 id를 두었다. ( 이 id가 websocket을 통해 전달되는 index이다.)
- javascript code : 아래 주석을 살펴보자. 조금 길다…

   // visualize 플러그인을 통해  html에 그대로 정의된 data를 바탕으로 그래프를 그린다.
   $("#example").visualize({type: 'bar', width: '420px'}).trigger('visualizeRefresh');

	var $opt_item;
	var $opt_vote;

	// 서버에 접속한다.
	app_socket = io.connect('http://zzoon.just4fun.co.kr:10706');

	// 서버로부터 'data-changed' 이벤트를 받으면 다음 함수를 실행한다.
	// 여기서는 vote값을 받아 해당 항목을 update하고 그래프를 refresh한다.
	app_socket.on('data-changed', function(data) {
		console.log("data changed!");
		console.log("index:" + data.index + " vote : " + data.vote);

		$opt_arr[data.index].vote = data.vote;
		$opt_arr[data.index].tr_item.innerText = data.vote;

		// visualize refresh
		$('.visualize').trigger('visualizeRefresh');
	});

	// $opt_arr 은 각 항목의 데이타를 저장하는 배열이다.
	// 이 배열의 각 element는 index, vote, tr_item 을 가지는 object를 가리킨다.
	// index : click 이벤트가 발생한 버튼의 id를 나타내는 프로퍼티
	// vote : 해당 항목의 현재 vote 값을 나타내는 프로퍼티
	// tr_item : 해당 항목의 DOM object를 가리키는 프로퍼티
	$opt_arr = new Array();

	// table안의 tr 태그에 대한 DOM object을 찾는다.
	$opt = $('body').find('#example tbody tr');

	// each 함수를 통해 각각의 TR item에 해당하는 DOM object를 찾아 여러가지 값을 얻어낸다.
	$opt.each(function(i, item) {
		// i 번째 TR item
		$opt_item = $(item);
		// i 번째 TR object에서 TD object를 찾아낸다.
		$opt_find = $opt_item.find('td')[0];
		// 해당 TH항목의 innerText값 ( 여기서는 각 여자연예인 이름이 된다 )
		$opt_name = $opt_item.find('th')[0].innerText;
		// 해당 TD항목의 innerText값 ( 여기서는 vote 값이 된다. )
		$opt_vote = $opt_item.find('td')[0].innerText;

		console.log("Name : " + $opt_name);
		console.log("votes : " + $opt_vote);

		// $opt_arr 배열의 i번째에 위에서 찾아낸 값을 가지는 object를 가리키도록 한다.
		$opt_arr[i] = {
			index: i,  // index는 click된 버튼의 id와 같고, 동시에 $opt_arr 배열 안에서의 element index이기도 하다.
			tr_item: $opt_find,  // 여기서 tr_item은 DOM tree의 object를 직접 가리키고 있음을 명심하자.
			vote: Number($opt_vote) // Number 함수를 사용하였다.
		};
	});

	// 버튼의 click 이벤트 핸들러
	var click_handler = function(event) {
		// click된 버튼의 id와 해당 항목의 vote값을 얻어낸다.
		var clicked_id = event.currentTarget.id;
		var vote_cnt = $opt_arr[clicked_id].vote;
		console.log(clicked_id + " clicked");
		console.log("vote : " + vote_cnt);

		// 'data-changed' 이벤트를 서버에 전송한다.
		app_socket.emit('data-changed', {
			index: clicked_id,
			vote: vote_cnt
		});

		// 서버에 이벤트를 보낸후 현재 자신의 화면을 refresh 해야한다.
		vote_cnt++;
		$opt_arr[clicked_id].vote = vote_cnt;
		$opt_arr[clicked_id].tr_item.innerText = vote_cnt;

		// visualize refresh
		$('.visualize').trigger('visualizeRefresh');
	};

	// 버튼의 clieck 이벤트에 대한 핸들러 등록
	$('button').click(click_handler);
	

여기까지가 이번 예제에 대한 설명이다.
여러가지 브라우저에서 클라이언트를 실행하고, 버튼을 클릭하면, 실시간으로 그래프가 바뀌는 것을 아래와 같이 볼 수 있을 것이다. ( 테스트 결과, firefox에서 제대로 동작하지 않음을 확인하였다. 그외, 크롬, IE9, 사파리에서는 정상동작을 확인하였다. 클라이언트에서 DOM에 관한 핸들링을 할 때, 문제가 있는 것으로 추측된다.)

결과가 궁금하신 분은 http://zzoon.just4fun.co.kr/realtime-survey/ 에 여러 브라우저를 통해 접속한 후, 테스트해 보시기 바란다.

언제나 느끼지만, socket.io를 설명할 때, 클라이언트가 훨씬 코드가 길게 나온다. 이는 예제이기 때문에 서버의 코드가 굉장히 짧은 것이고, ( 서버가 해야할 수많은 일들을 생략 ) 클라이언트는 그래도 화면에 무엇인가를 그려줘야 하기때문에, 예제라고는 해도 생략할 수 없는 것들이 있다.

위 예제에서는 단순한 web socket 통신 및 데이타를 받아서 클라이언트가 refresh하는 동작만을 보여주는데, 사실 심각한 문제가 있다.
초기 데이타값이 table 태그안에 그대로 정의되어 있는 것이다. 따라서, 어떤 클라이언트에서 아무리 김태희에 클릭질을 해도, 다른 클라이언트가 접속하면, 초기값이 그대로 나올 것이다.
이를 해결하려면 서버가 데이타베이스(아니면, 뭐든지)를 통해 vote 값을 관리해야 하고, 클라이언트는 초기화면에서 이 값을 서버로부터 내려받아 그려주어야 할 것이다.

이에 대한 코드는 다음 포스트에서 설명하도록 하겠다.
(코딩할 시간이 필요하다.ㅡ.ㅡ;)

Node.js에서 Async 모듈을 통한 중첩 콜백 문제 해결하기

Node.js에서 Async 모듈을 통한 중첩 콜백 문제 해결하기

원문 링크 - http://www.hacksparrow.com/managing-nested-asynchronous-callbacks-in-node-js.html

관련글 – node.js에서 비동기로 코드 디자인 하기

Node.js를 가지고 작업해본 개발자라면 누구나 여러 단계로 중첩된 콜백 함수 처리에 대한 골치아픈 문제를 겪게 될 것이다.

여러 겹의 중첩된 비동기 콜백 함수 문제는 최초 콜백 함수 내부에서 콜백 함수를 호출하고, 그 콜백 함수에서 다시 또다른 콜백 함수를 호출하고.. 이런 식으로 콜백 함수가 계속 중첩되는 상황에서 발생한다. 콜백 함수의 중첩 단계가 늘어날 수록 코드는 지저분해지고 결국엔 관리 불가의 상태가 될 수도 있다. 다음 예제를 살펴보자.

var fs = require('fs');
 
var path = './async.txt';
// check if async.txt exists
fs.stat(path, function(err, stats) {
    if (stats == undefined) {
        // read the contents of this file
        fs.readFile(__filename, function(err, content) {
            var code = content.toString();
            // write the content to async.txt
            fs.writeFile(path, code, function(err) {
                if (err) throw err;
                console.log('async.txt created!');
            });
        });
    }
});

위 예제는 3단계의 중첩된 비동기 콜백 함수가 사용됐다. 여러분은 이것보다 훨씬 심한 콜백 함수의 중첩이 필요한 상황을 접할 수도 있을 것이다.

한 가지 방법은 중첩될 콜백 함수를 외부에 정의하고, 콜백 함수에서 외부에 정의한 함수를 호출하는 것이다. 그러나 이 방법은 네임 스페이스를 복잡하게 하며 코드 또한 지저분하게 보인다.

Node.js에서 중첩 콜백 문제를 처리하기 위한 다양한 비동기 제어 모듈들이 개발되고 있다. 내가 볼때 가장 유력한 모듈은 Async와 Step이다. 나는 Async를 선택했는 데, 그 이유는 확장성과 몇가지 특징들 때문이다. Asyn는 Step이 할 수 있는 일 뿐만 아니라, 더 많은 것을 할 수 있다. 우리가 Async를 이용해서 위 예제를 고쳐보자.

우선 Async 모듈을 설치하자.

그럼 다음, Async 모듈을 이용해서 위 소스 코드를 다시 작성하자.

var fs = require('fs');
var async = require('async');
 
var path = './async.txt';
async.waterfall([
    // check if async.txt exists
    function(cb) {
        fs.stat(path, function(err, stats) {
            if (stats == undefined) cb(null);
            else console.log('async.txt exists');
        });
    },
    // read the contents of this file
    function(cb) {
        fs.readFile(__filename, function(err, content) {
            var code = content.toString();
            cb(null, code);
        });
    },
    // write the content to async.txt
    function(code, cb) {
        fs.writeFile(path, code, function(err) {
            if (err) throw err;
            console.log('async.txt created!');
        });
    }
]);

훨씬 더 보기 좋지 않은가? 실행 순서를 알아보기 쉬울 뿐 아니라, 콜백 함수가 중첩되지도 않는다. (가령, 10단계까지 중첩된 콜백 함수들의 코드를 상상해보자)

Async는 위에서 살펴본 것 이외에 훨씬 많은 기능들을 가지고 있다. Async에 대한 정보를 알고 싶으면 Async GitHub page를 참조하자.

[역자주]

- Async는 Node.js 뿐만 아니라, 기존의 브라우저에서 동작하는 클라이언트 자바스크립트 코드에도 그대로 적용된다.
- Async를 잘 사용하는 것도 중요하지만, 이를 어떻게 구현했는지를 코드를 통해 살펴보는 것도 많은 도움이 될 것이다.

node.js 유용한 모듈 (15) – procstreams

원문 링크 -http://www.catonmat.net/blog/nodejs-modules-procstreams/
본 게시글은 원저자의 허락을 얻어 번역한 것입니다.

1회 연재 – dnode (RPC 라이브러리)
2회 연재 – optimist (옵션 파서)
3회 연재 – lazy (lazy 리스트 처리)
4회 연재 – request (HTTP 스트림 처리)
5회 연재 – hashish (해시 처리)
6회 연재 – read (쉬운 표준 입력 처리)
7회 연재 – ntwitter (트위터 API)
8회 연재 – socket.io (웹소켓 통신)
9회 연재 – redis (redis 클라이언트 라이브러리)
10회 연재 – express (경량의 고속 웹 프레임워크)
11회 연재 – semvar (버전 넘버링 처리)
12회 연재 – cradle (고수준 CouchDB 클라이언트)
13회 연재 – JSONStream (스트리밍 JSON 파서)
14회 연재 – everyauth (페이스북, 트위터 등의 서비스 인증)

오늘 소개할 모듈은 polotek이라는 닉네임으로 알려진 Marco Rogers가 만든 procstreams 모듈이다. procstreams는 node에서 쉘 스크립팅을 실행하는 아직 실험 단계의 모듈이다.

var $p = require('procstreams');

$p('cat lines.txt').pipe('wc -l')
  .data(function(stdout, stderr) {
      console.log(stdout); // prints number of lines in the file lines.txt
  });

위 예제는 cat lines.txt 쉘 명령어를 실행한 다음, 결과값을 wc -l 명령의 입력으로 전달(pipe) 한다. 그런 다음 콜백 함수를 통해  lines.txt 파일의 라인 수를 출력하게 된다.

다음은 또 다른 예제이다.

var $p = require('procstreams');

$p('mkdir foo')
  .and('cp file.txt foo/')
  .and('rm file.txt')
    .on('exit', function() {
      console.log('done');
    });

위 예제는 mkdir foo 을 실행하고, 이 명령이 성공하면 cp file.txt foo/ 명령을 실행한다. 이 명령이 계속 성공했다면, rm file.txt 명령을 계속 수행한다. 위 코드를 쉘 스크립트로 작성하면 다음과 같을 것이다.

mkdir foo && cp file.txt foo/ && rm file.txt

.and(…) 함수는 쉘 스크립트에서 && 와 같다.

procstreams는 또한 쉘 스크립트 명령에서 ||와 유사한 .or(...) 메서드와 ;와 유사한 .then(...)메서드를 지원한다.

다음은 예제 코드이다.

var $p = require('procstreams');

$p('mkdir foo')
  .then('cp file.txt file2.txt')
  .or('echo "failed" > ~/notify')

이 예제는 mkdirs foo 명령을 수행한 다음, file.txt를 file2.txt로 복사한다. 만약 복사가 실패할 경우 “failed” 에러를 ~/notify 파일에 쓴다. 쉘 스크립트로 작성하면 다음과 같을 것이다.

mkdir foo; cp file.txt file2.txt || echo "failed" > ~/notify

procstreams의 전체 기능을 알고 싶다면 Github 사이트상의 procstreams documentation을 살펴봐라.

다음과 같이 npm을 통해 procstreams 를 설치할 수 있다.

npm install procstreams

Procstreams의 GitHub 사이트 : https://github.com/polotek/procstreams.

Socket.IO를 활용한 예제 분석

socket.io를 활용한 재미있는 예제를 찾던 중, 다음을 발견하였다.

http://wesbos.com/html5-canvas-websockets-nodejs/

이 예제는 각 클라이언트가 socket.io 서버에 접속을 하고, 한 명의 클라이언트가 자신의 브라우저에 그림을 그리면, 다른 클라이언트의 브라우저에도 실시간으로 똑같이 그림이 그려지도록 하는 예제이다. 원문을 클릭하면, 해당 예제를 돌리는 동영상을 볼 수 있다.

본 글에서는 이 페이지에서 소개하는 예제를 분석해 보도록 하겠다.
( 원문에는 coffee script를 활용하였으나, 역자는 컴파일을 통해 생성되는 javascript 코드를 가지고 분석하도록 하겠다.)

원문의 소스가 있는 github 주소는 다음과 같다.
https://github.com/wesbos/websocket-canvas-draw

본문으로 들어가기에 앞서, socket.io에 대한 지식을 먼저 갖추도록 하자.
공식 사이트는 http://socket.io 이다.
본 블로그에서는 8회 연재 – socket.io (웹소켓 통신) 에서 socket.io에 대하여 간단히 소개한 글을 번역한 글이 있고, 한글 문서 중에서는 Web Socket과 함께 자세한 설명이 있는 문서를 다음 주소에서 찾을 수 있다.

http://helloworld.naver.com/helloworld/1336

자, 이제 본격적으로 서버 코드를 보자.

[server.js]

var io;
io = require('socket.io').listen(4000);
io.sockets.on('connection', function(socket) {
  socket.on('drawClick', function(data) {
    socket.broadcast.emit('draw', {
      x: data.x,
      y: data.y,
      type: data.type
    });
  });
});

전형적인 socket.io의 예제코드다.

‘connection’ 이벤트가 발생하면, 즉, 연결이 이루어지면, 해당 소켓에서 ‘drawClick’ 이벤트를 리스닝하게 된다.
그리고 ‘drawClick’ 이벤트가 발생하면, { x, y, type } 객체와 함께 ‘draw’ 이벤트를 연결되어 있는 모든 클라이언트에게 브로드캐스팅 하게 된다. 여기서 x와 y는 브라우저의 캔버스의 좌표이고, type은 이벤트 타입( dragstart, drag, dragend, 아래 설명됨 )이다.

서버는 위가 전부다. 그렇다면, 클라이언트는 어떻게 되어 있을까?

[ index.html ]

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="UTF-8">
    <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
    <script type="text/javascript" src="js/jquery.event.drag-2.0.js"></script>
    <script src="http://localhost:4000/socket.io/socket.io.js"></script>
    <script type="text/javascript" src="scripts.js"></script>
    <link rel="stylesheet" href="style.css" />

    <title>HTML5 Canvas + Node.JS Socket.io</title>
</head>
<body>
    <article><!-- our canvas will be inserted here--></article>

    <!-- Scripts required -->
</body>

여기서 클라이언트는 다음의 javascript를 로딩한다.
- jquery
- jquery.event.drag-2.0.js
- socket.io 서버의 socket.io.js
- scripts.js

[ jquery.event.drag-2.0.js ]

이 코드는 다양한 웹브라우저의 여러가지 마우스 이벤트 중에서 drag에 대한 처리를 dragstart, drag, dragend 라는 이벤트로 편하게 통일된 방식으로 처리할 수 있도록 만든 jquery plugin 소스이다. ( 이 플러그인 없이, 브라우저의 이벤트를 통해 드래그에 대한 이벤트 핸들링을 하려한다면, 각 브라우저별로 이벤트 핸들링 코드를 구현해야 할 것이다.) 이 소스코드에 대한 설명은 다음 기회에 하도록 하겠다. ( 사실 웹 관련 지식이 부족한 편이라 ( 특히 browser-specific한 부분) 아직 분석할 능력이 안된다 )

[socket.io.js]

클라이언트에서 sokcet.io 를 사용하기 위해 로딩한다.

[scripts.js]

이 소스에서 하는 일은 다음과 같다.
(1) canvas를 만들어 그림을 그릴 준비를 한다.

(1)의 소스는 다음과 같다.

App.init = function() {
  App.canvas = document.createElement('canvas');
  App.canvas.height = 400;
  App.canvas.width = 800;
  document.getElementsByTagName('article')[0].appendChild(App.canvas);
  App.ctx = App.canvas.getContext("2d");
  App.ctx.fillStyle = "solid";
  App.ctx.strokeStyle = "#bada55";
  App.ctx.lineWidth = 5;
  App.ctx.lineCap = "round";
  ...........................
};

- createElement를 통해 DOM element(canvas)를 만들고 특성(width, height)을 정한다.
- article이라는 DOM Element의 자식으로 canvas를 붙인다. ( 여기서 article은 위 index.html에서 기술된 그 article태그를 말한다.)
- getContext(“2d”) 를 통해 canvas의 context를 얻어온다. 이 context를 통해 그림을 (정확히는 선을) 그릴 것이다.

(2) dragstart, drag, dragend 이벤트가 발생하면 해당 좌표를 담아서 drawClick 이벤트를 socket.io 서버로 보낸다. (2)의 소스는 다음과 같다.


$('canvas').live('drag dragstart dragend', function(e) {
  var offset, type, x, y;
  type = e.handleObj.type;
  offset = $(this).offset();
  e.offsetX = e.layerX - offset.left;
  e.offsetY = e.layerY - offset.top;
  x = e.offsetX;
  y = e.offsetY;

  App.draw(x, y, type);

  App.socket.emit('drawClick', {
    x: x,
    y: y,
    type: type
  });
});

- live 함수는 jquery 함수로서 특정 이벤트에 대한 핸들러를 정의할 때 쓰인다. ( 이 함수는 현재 deprecated 되었다. 현재는 on()을 쓴다. )
- live 함수를 통해 drag, dragstart, dragend 이벤트에 대한 핸들러를 정의하였다. 여기서 drag, dragstart, dragend는 위에서 설명했듯이, jquery.event.drag-2.0.js 에서 발생시킨다.
- 핸들러를 보면, x, y좌표를 계산하고, Draw() 한후, socket.io 서버에 이 좌표와 현재 이벤트의 타입을 담아, ‘drawClick’이벤트를 보낸다.
- 위의 서버 코드에서 설명했듯이, 서버는 클라이언트로부터 drawClick 이벤트를 받으면, {x,y, type}을 담아 모든 클라이언트에 draw 이벤트를 보낸다.

(3) 서버로부터 draw 이벤트를 받으면 해당 좌표에 그림을 그린다.
(3)의 소스는 다음과 같다.

App.init = function() {
  ............................

  App.socket = io.connect('http://localhost:4000');
  App.socket.on('draw', function(data) {
    return App.draw(data.x, data.y, data.type);
  });

  App.draw = function(x, y, type) {
    if (type === "dragstart") {
      App.ctx.beginPath();
      return App.ctx.moveTo(x, y);
    } else if (type === "drag") {
      App.ctx.lineTo(x, y);
      return App.ctx.stroke();
    } else {
      return App.ctx.closePath();
    }
  };
};

- socket.on 을 통해 ‘draw’ 이벤트를 리스닝하고 해당 이벤트가 발생하면 그림을 그린다.
- App.draw()는 그림(선)을 그리는 함수이다.
- beginPath, moveTo, lineTo, stroke, closePath 함수에 대한 설명은 생략하겠다. canvas에 대한 자세한 사항은 https://developer.mozilla.org/en/Drawing_Graphics_with_Canvas 에 설명되어 있다.

이렇게 해서 간단하게 한 클라이언트가 그림을 그리면 다른 클라이언트의 브라우저에서도 똑같이 그림이 그려지는 재미난 예제 코드에 대해 분석해 보았다.

이 예제를 통해 socket.io를 통해 무엇을 만들 수 있을지 감을 잡을 수 있을 것이라 생각한다.

Node.js 도움말 번역 – C/C++ Add-on 작성 관련

Node.js C/C++ Add-on에 관심이 있어서, 가장 기본적인 Node.js 도움말에 관련 내용을 번역해 봤습니다. 이를 토대로 좀더 구체적인 바이너리 모듈 작성 방법에 대해서 정리되는대로 올리겠습니다.

Addons

Add-on은 동적으로 링크되는 공유 오브젝트입니다. (역자주-C언어에서의 .so 라이브러리라고 생각하면 됩니다.) 그것은 기존의 C/C++ 라이브러리를 사용하기 위한 진입점 역할을 할 수 있습니다. Add-on을 만들기 위한 API는 다음과 같은 몇개의 라이브러리를 포함한 지식들을 필요로 합니다.

  • V8 자바스크립트 엔진 (C++ 라이브러리). 자바스크립트 코드와의 인터페이스를 처리하는데 사용 : 객체 생성, 함수 호출 등. v8.h헤더 파일 (Node.js 소스 트리 deps/v8/include/v8.h) 에 필요한 내용이 문서화되어 있습니다.

  • libuv (C 기반 이벤트 루프 라이브러리). 파일 디스크립터가 읽기 가능할 때나, 타이머가 만료될 때, 또는 무언가 수신됐다는 시그널을 기다리는 경우에 libuv를 사용할 수 있습니다. 만약 여러분이 어떤 I/O(입출력)을 수행하던지 libuv를 이용할 수 있습니다.

  • Node 내부 라이브러리. node::ObjectWrap클래스가 가장 중요합니다.

  • 기타 라이브러리. deps/ 디렉토리 내부를 살펴보세요.

Node는 그것의 모든 의존성을 가진 모듈들을 정적으로 컴파일해서 실행 파일을 생성합니다. 따라서 여러분이 모듈을 컴파일 할때 어떤 라이브러리들을 링크해야 하는 지 걱정할 필요가 없습니다.

다음과 같은 자바스크립트 코드와 동일한 기능의 간단한 C++ Add-on을 만드는 것을 시작할 것입니다.

exports.hello = function() { return 'world'; };

우선 다음과 같이 hello.cc 파일을 생성하세요.

#include <node.h>
#include <v8.h>
using namespace v8;

Handle<Value> Method(const Arguments& args) {
  HandleScope scope;
  return scope.Close(String::New("world"));
}

void init(Handle<Object> target) {
  NODE_SET_METHOD(target, "hello", Method);
}

NODE_MODULE(hello, init)

모든 Node Add-on들은 초기화 함수를 익스포트 해야한다는 것을 기억하세요.

void Initialize (Handle<Object> target);
NODE_MODULE(module_name, Initialize)

NODE_MODULE은 함수가 아니기 때문에 문장 끝에 세미콜론(;)이 없습니다. (node.h 파일을 살펴보세요)

module_name은 최종 바이너리의 파일명과 일치하게 합니다. (.node 확장자명을 제거)

위 소스 코드는 바이너리 Add-on인 hello.node 파일로 빌드되야 합니다. 이를 위해서 다음과 같은 파이썬 기반의 빌드 스크립트 파일인 wscript 파일을 작성해야 합니다.

srcdir = '.'
blddir = 'build'
VERSION = '0.0.1'

def set_options(opt):
  opt.tool_options('compiler_cxx')

def configure(conf):
  conf.check_tool('compiler_cxx')
  conf.check_tool('node_addon')

def build(bld):
  obj = bld.new_task_gen('cxx', 'shlib', 'node_addon')
  obj.target = 'hello'
  obj.source = 'hello.cc'

node-waf configure build 명령을 통해 위 스크립트를 쉘 상에서 실행하면, 여러분이 작성한 build/default/hello.node Add-on 파일이 생성됩니다.

node-waf는 파이썬 기반의 빌드 시스템인 WAF와 동일합니다. node-waf는 쉽게 모듈을 빌드할 수 있게 하기 위해 제공하고 있습니다.

여러분은 아래와 같이 hello.js라는 Node 프로젝트를 만들고, 이 파일을 통해 바이너리 Add-on을 바로 사용할 수 있습니다. 작성된 모듈을 사용하기 위해서는 require문을 통해 사용할 모듈을 지정하면 됩니다.

var addon = require('./build/Release/hello');
console.log(addon.hello()); // 'world'

좀더 자세한 개발 예제를 보고 싶다면 https://github.com/pietern/hiredis-node 를 참조하세요.

Node.js를 이용한 웹 기반 메모장 구현하기 – Nodepad 프로젝트 소개

요즘 Node.js 공부를 어떻게 해야하는지에 대한 질문을 종종 받습니다.
사실 저도 배워가는 처지라 정확한 답변 보다는 다음과 같은 원론적인 얘기만을 늘어놓고 있습니다.

“Node.js를 설치하고, 우선 API 공부를 충실히 한 다음 관련 프로그램을 작성해보세요.”

그런데 저같은 초보자에 입장에서는 API 공부까지는 스스로 할 수 있다 치더라도 무엇을 만들지에 대한 고민을 해결하기는 쉽지가 않습니다.

이에 대한 하나의 대안으로 Node.js를 활용한 웹 기반의 메모장 ‘nodepad’라는 프로젝트를 소개하려고 합니다.

제가 즐겨 찾는 자바스크립트 관련 블로그 중에 ‘DailyJS(http://dailyjs.com/)’라는 곳이 있습니다. 이곳은 각종 자바스크립트 관련 라이브러리나 프레임워크의 리뷰 관련 글, 그리고 node.js 글들이 매주 2~3회씩 포스팅 되는 아주 액티브한 블로그입니다.

이곳의 주인장 Allex Young이라는 친구가 Nodepad 라는 Node.js 기반의 튜터리얼 연재를 약 20회에 걸쳐서 진행했습니다. 
여기를 참조하세요.

dailyjs.com 블로그

이 프로젝트에 대해 간단히 소개하면, Node.js의 웹서비스 프레임워크인 Express와 MongoDB를 핸들링할 수 있는 Mongoose 등과 같은 Node 모듈들을 활용해서 웹 기반의 메모장을 만드는 것입니다. 튜터리얼은 Nodepad의 기능을 하나씩 추가하는 방식으로 연재를 진행하고 있습니다. 그리고 해당 회차의 전체 소스들은 Github 사이트에서 공개하고 있습니다. 하지만 해당 회차의 모든 소스들에 대한 설명을 하지는 않기 때문에 튜터리얼과 소스를 같이 살펴봐야 합니다.

아마 Node.js 개발 방법에 대한 전체 사이클이 궁금하신 분들이라면, 차근차근 수행한다면 Node.js에 대한 활용 방법에 대해서도 많은 도움을 얻을 수 있을 것입니다.

그리고 제가 참여하고 있는 Octobersky.js 스터디의 일환으로 Nodepad 프로젝트의 번역을 진행하고 있으니, 이곳를 참고하셔도 괜찮습니다.

node.js 유용한 모듈 (14) – everyauth

원문 링크 - http://www.catonmat.net/blog/nodejs-modules-everyauth/

본 게시글은 원저자의 허락을 얻어 번역한 것입니다.

1회 연재 – dnode (RPC 라이브러리)
2회 연재 – optimist (옵션 파서)
3회 연재 – lazy (lazy 리스트 처리)
4회 연재 – request (HTTP 스트림 처리)
5회 연재 – hashish (해시 처리)
6회 연재 – read (쉬운 표준 입력 처리)
7회 연재 – ntwitter (트위터 API)
8회 연재 – socket.io (웹소켓 통신)
9회 연재 – redis (redis 클라이언트 라이브러리)
10회 연재 – express (경량의 고속 웹 프레임워크)
11회 연재 – semvar (버전 넘버링 처리)
12회 연재 – cradle (고수준 CouchDB 클라이언트)
13회 연재 – JSONStream (스트리밍 JSON 파서)

오늘 소개할 모듈은 Brian Noguchi가 만든 everyauth다. everyauth는 connect 모듈의 미들웨어로 사용되며 페이스북, 트위터, 구글 등과 같은 서비스의 인증을 처리하기 위하 사용된다.

다음은 이 모듈을 통해 로그인 처리를 할 수 있는 사이트 목록이다.


Everyauth supports a plenty of sites.

모듈을 사용하기 위해서는 다음과 같이 connect에서의 미들웨어 설정을 한다.

var everyauth = require('everyauth');
var connect = require('connect');

var app = connect(everyauth.middleware());

그리고 인증 처리를 할 서비스들의 비밀키를 다음과 같이 config.json에 구성한다.

module.exports = {
    fb: {
        appId: '111565172259433'
      , appSecret: '85f7e0a0cc804886180b887c1f04a3c1'
    },
    twit: {
        consumerKey: 'JLCGyLzuOK1BjnKPKGyQ'
      , consumerSecret: 'GNqKfPqtzOcsCtFbGTMqinoATHvBcy1nzCTimeA9M0'
    },
    github: {
        appId: '11932f2b6d05d2a5fa18'
      , appSecret: '2603d1bc663b74d6732500c1e9ad05b0f4013593'
    },
    // ...
};

그런 다음 여러분이 사용하려는 서비스의 로그인을 수행하는 코드를 작성한다. (아래 코드는 페이스북 로그인 예제다)

var conf = require('./config.json');

var usersByFbId = {};

everyauth
  .facebook
    .appId(conf.fb.appId)
    .appSecret(conf.fb.appSecret)
    .findOrCreateUser(function (session, accessToken, accessTokenExtra, fbUserMetadata) {
      return usersByFbId[fbUserMetadata.id] ||
        (usersByFbId[fbUserMetadata.id] = addUser('facebook', fbUserMetadata));
    })
    .redirectPath('/');

everyauth 모듈은 npm을 통해 설치할 수 있다.

npm install everyauth

EveryAuth의 GitHub 사이트: https://github.com/bnoguchi/everyauth.

NPM을 통한 모듈 의존성 관리

원문 링크 – http://howtonode.org/managing-module-dependencies

모듈 의존성 관리하기

node.js 메일링 리스트에서 모듈 의존성 관리에 대한 논쟁 후에, 나는 이곳에 몇가지 사항을 공유하는 것이 의미있을 것이라 생각했다.

NPM을 통한 모듈 의존성 기술하기

당신이 여러개의 NPM 모듈들에 의존적인 애플리케이션을 개발중이라면, 다음과 같은 방식으로 package.json 파일을 작성해서 의존성을 기술할 수 있다.

"dependencies": {
  "express": "2.3.12",
  "jade": ">= 0.0.1",
  "redis":   "0.6.0"
}

이를 통해 당신이 프로젝트를 갱신할 때마다, 다음과 같은 npm 명령을 통해 의존성을 해결할 수 있다.

$ npm install

당신의 프로그램을 구동하기 위해, 특정 버전의 모듈이 필요로 하거나 버전 번호 앞에 >= 를 붙여 앱 실행을 위한 최소 버전을 요구할 수 있다.

개발 버전을 위한 의존성 관리

만약 실제 제품이 아닌 개발 버전에서만의 의존성 (예. 테스팅 프레임워크)을 제공하기 위해서는 devDependencies 속성을 사용하면 된다.

"devDependencies": {
  "vows": ">= 0.4.x"
}

실제 제품을 릴리즈 할 때는 npm install –production 명령을 실행하면, 개발 버전의 의존성을 가진 모듈의 설치는 제외된다.

private NPM 모듈 관리

당신이 private 모듈 상태로 개발한다면, package.json 파일에 “private”: true를 추가해서 당신이 작성한 모듈이잘못해서 NPM 레지스트리에 등록되는 것을 막을 수 있다.

git 저장소의 모듈 의존성 기술하기

마지막으로 여러분이 개발한 모듈을 private git 저장소에서 제공하면서, 이 모듈에 의존하는 어떤 프로젝트를 개발한다면 모듈 의존성을 다음과 같이 작성할 수 있다.

"dependencies": {
    "secret-module": "git+ssh://git@github.com:username/secret-repo.git#v2.3"
}

URL의 마지막 부분의 v2.3은 어떤 태그가 사용될지 기술한다. 커밋 해시나 브랜치명을 기술할 수도 있다.