Node 를 사용하여 Javascript 언어로 어플리케이션을 개발하기위한 기초를 다지기에 좋은 교재가 있습니다.

사이트 : http://www.nodebeginner.org/index-kr.html



구글 크롬 웹브라우저에서 사용하고 있는 V8 Javascript Engine을 사용합니다.

웹어플리케이션의 유스케이스는 아래와 같습니다.

1. 사용자는 웹브라우저로 우리의 웹어플리케이션을 이용할수 있다.

2. 사용자가 http://domain/start 를 요청하면 파일 업로드 폼이 들어있는 웰컴 페이지를 볼수 있어야 한다.

3. 업로드할 이미지 파일을 선택해서 폼으로 전송하면 해당이미지는 http://domain/upload 로 업로드 되어야 하며 업로드가 끝나면 해당페이지에 표시된다.


사용자가 웹브라우저를 통해서 REQUEST를 보내면 서버가 이 REQUEST 를 받아서 작업을 한 다음에 RESPONSE 를 보냅니다.

이 유스케이스를 구현하기 위해 필요한 기술은?

1. 웹페이지를 전송할수 있는 웹서버   => NODE.js 사용

2. REQUEST 내용에 따라 응답을 다르게 하는 방법 => 라우터(ROUTER) 구축

3. 라우팅된 REQUEST 를 처리하기 위한 로직 => 요청 핸들러(request handlers)

4. REQUEST 와 함께 넘어오는 POST 데이타를 분리해서 넘겨주는 기능 => 요청데이타핸들링(request data handling )

5. RESPONSE로 보내기 위한 웹화면 생성 => 뷰로직( view logic )

6. 사용자가 이미지를 업로드 할수 있는 기능 => 업로드 핸들링( upload handling )


main 파일을 작성하는데 파일명은 index.js 로 합니다.

그리고 서버모듈을 가지고 있는 파일명은 server.js 로 합니다.

서버모듈의 프로세스는

구현되어져 있는 http 모듈을 가져와서 http server 를 생성합니다. 포트는 8888 로 설정하는 거지요.

var http = require("http");

http.createServer(function(request, response) {

response.writeHead(200, {"Content-Type": "text/plain"});

response.write("Hello World");

response.end();

}).listen(8888);

서버실행 : node server.js

클라이언트 접속 : http://localhost:8888/


함수의 인자로 함수를 전달하기

event-driven callbacks

: 이벤트에 의해 구동되는 함수가 있고 이 함수에 인자로 넘겨진 호출되어야 할 함수가 있다면 이벤트에 의해 구동되는 함수가 인자로 넘어온 함수를 호출하도록 하는 겁니다.

즉 우리는 메소드에 함수를 넘기고, 메소드는 관련된 이벤트가 발생하면 이 함수를 거꾸로 호출(call back) 합니다.

위의 소스에서 콜백 함수는 function(request, response) 입니다.

var http = require("http");

Node.js 내부 어딘가에 http 라는 모듈이 있으며 이를 require 하고 지역변수에 할당해서 사용할수 있습니다.

이렇게 하면 지역변수가 http모듈이 제공하는 모든 public 메소드를 사용할수 있는 객체가 됩니다.

모듈의 생성과 사용하기

server.js를 모듈로 생성해할수 있습니다.

코드를 모듈로 만든다는 것은 모듈을 필요로 하는 스크립트에 제공할 기능의 일부를 export 하는 것입니다.

우리 서버모듈을 require 하는 스크립트는 단순히 서버를 시작하는것이 필요할 것입니다.

start 하는 기능 즉 함수를 만들고  이 함수를 export.start = start 형식으로 지정하면 됩니다.

내장모듈(http)을 사용하는 것처럼 외장모듈(server.js) 도 똑같이 사용하면 됩니다.

파일을 require 하고 지역변수에 할당하면  export 된 함수를 쓸수 있게 됩니다.

요청을 라우터 하기

웹브라우저의 페이지에서 요청을 보내면 url 과 정보( get방식, post방식)가 함께 전송됩니다.

우리가 필요한 정보 : request 객체를 통해 접근가능, url 모듈querystring 모듈이 필요


url.parse(string).query
                                           |
           url.parse(string).pathname      |
                       |                   |
                       |                   |
                     ------ -------------------
http://localhost:8888/start?foo=bar&hello=world
                                ---       -----
                                 |          |
                                 |          |
              querystring(string)["foo"]    |
                                            |
                         querystring(string)["hello"]

querystring 모듈을 POST 요청의 body를 파싱하는데에도 사용할 수 있습니다.

var pathname = url.parse(request.url).pathname;

여기서 /start, /upload 를 pathname 으로 구분할수 있을것입니다.

router 모듈에서 pathname 을 받아서 처리할수 있도록 합니다.

정리하면 server.js 모듈이 start 할때 router 모듈을 인자로 받아서 실행을 하면서 콜백함수 onRequest에서 router 를 호출하는것으로 흘러갑니다.

동사들의(verbs) 나라에서의 실행(execution)

functional programming 에 대한 이야기 - 무엇인가를 해야 할때 명사보다는 동사의 행동이 필요하다.

실제 request handler 로 라우팅(routing) 하기

라우터를 통해 라우팅된 실제 비지니스로직을 수행하는 requestHandlers 모듈을 만들 필요가 있습니다.

두개의 기능이 모듈에서 export 되어야 합니다.

exports.start = start;

exports.upload = upload;

request handler는 지금은 두개 있지만 실제 어플리케이션에서는 handler의 수가 늘어나고 다양해 질것입니다.

라우터에서 request 와 handler를 매핑하거나 if code 문을 쓰는것은 추악함 이상이 될것입니다.

javascript 의 객체는 키 값이 문자열인 사전(dictionary) 이라고 생각하세요. 이름 값의 컬렉션. 이 값에는 문자일수 도 숫자일수 도 함수일수도 있다는 것입니다.

requestHandler 의 리스트를 객체로 넘기고 느슨한 연결을 위해 이 객체를 router 로 주사하려고 합니다.

index.js 에서 

var handle = {};

handle["/"] = requestHandlers.start;

handle["/start"] = requestHandlers.start;

handle["/upload"] = requestHandlers.upload;

여기서의 handle 을 server 가 시작할때 라우터 와 함께 handle 을 넘겨줍니다.

라우터는 pathname 과 handle 을 인자로 받아서 pathname 에 해당하는 handle 키에 해당하는 값의 함수를 콜백합니다.

if (typeof handle[pathname] === 'function') {

   handle[pathname]();

  } else {

    console.log("No request handler found for " + pathname);

  }

request handler 가 응답하게 만들기

request 처리란 request 에 응답하는 것입니다.

해서는 안되는 것 : 직관적인 접근방식 => 반환값으로 응답데이타를 유저에게 보내는 방식

Blocking 과 non-Blocking

start request handler 에서 10초 동안 기다리도록 수정합니다. ( start time  과 현재 시간을 비교하여 구현 )

/**

function start() {

  console.log("Request handler 'start' was called.");

  function sleep(milliSeconds) {

    var startTime = new Date().getTime();

    while (new Date().getTime() < startTime + milliSeconds);

  }

  sleep(10000);

  return "Hello Start";

}

**/

여기서 10초 동안 sleep 하거나 오랜시간 걸리는 계산작업을 하는 경우에 operation이 blocking 되어 있다고 합니다.

 Node.js는 다수의 동시작업을 처리할 수 있지만 thread를 나누는 방식으로 하지 않습니다. 

 사실 Node.js는 단일 thread입니다. 

 대신, Node.js는 동시작업을 event loop을 실행해서 처리하며 개발자들은 이것을 사용할 수 있습니다. 

 우리는 blocking 동작을 피하고 non-blocking 동작을 사용해야만 합니다.

그러기 위해서는 callback 으로 함수를 다른 함수에게 넘겨야 합니다.

/**

var exec = require("child_process").exec;

function start() {

  console.log("Request handler 'start' was called.");

  var content = "empty";

  exec("ls -lah", function (error, stdout, stderr) {

    content = stdout;

  });

  return content;

}

**/

위 start 함수의 실행결과는 empty 가 바로 출력이 됩니다.

우리는 새로운 Node.js 모듈인 child_process를 사용했습니다. 

매우 단순하지만 쓸모가 많은 non-blocking 동작인 exec()을 사용하기 위해서 입니다.

그렇지만 그 이유는 exec()을 호출하자 마자 Node.js는 이어서 return content;를 실행합니다. 

바로 이 시점에 content는 여전히 "empty" 입니다. 

왜냐하면 exec()은 비동기적으로 동작하기 때문에 exec()에 전달된 callback 함수는 아직 호출되지 않았던 것이죠.


request handler 가 non-blocking 방식으로 동작하면서 응답하기

현재 우리 애플리케이션은 사용자에게 보여주고 싶은 content를 

request handler에서 HTTP server로 전달할 수 있습니다. 

다음과 같은 여러 애플리케이션 레이어들을 거쳐 넘기는 식으로 식으로 말입니다. 

(request handler -> router -> server).


새로운 접근 방법은 다음과 같습니다: 

content를 server로 보내는 대신 server를 content로 보낼겁니다. 

좀 더 자세히 이야기 하면, response 객체 (server의 callback 함수인 onRequest() 에서 얻은)를 

router를 통해 request handler에게 주사(inject) 합니다. 

이제 handler는 이 객체가 가진 함수들을 이용해서 스스로 요청에 응답할 수 있게 되었습니다.


POST 요청 처리하기

textarea 를 하나 제공해서 사용자가 내용을 채우고 submit 해서 post 요청을 서버로 보내는 시나리오 입니다

requestHandler.js 의 start 함수에 testarea를 포함하는 html을 response 하도록 수정합니다.

function start(response) {

  console.log("Request handler 'start' was called.");

  var body = '<html>'+

    '<head>'+

    '<meta http-equiv="Content-Type" content="text/html; '+

    'charset=UTF-8" />'+

    '</head>'+

    '<body>'+

    '<form action="/upload" method="post">'+

    '<textarea name="text" rows="20" cols="60"></textarea>'+

    '<input type="submit" value="Submit text" />'+

    '</form>'+

    '</body>'+

    '</html>';

    response.writeHead(200, {"Content-Type": "text/html"});

    response.write(body);

    response.end();

}

POST 요청은 상당히 클겁니다. 

누구도 사용자가 몇 메가바이트의 텍스트를 입력하는 것을 막을 수 없겠죠. 

전체 데이터 블록을 하나로 처리하는 것은 blocking operation 방법이 될 것입니다.

전체 프로세스를 non-blocking 으로 만들려면, 

POST 데이터를 작은 청크로 나누고 특정 이벤트 때마다 

callback을 호출하는 방식으로 만들어야 합니다.



참고사이트

김종령의 생각 : http://jongryong.wordpress.com/tag/node-js/

screenr.com 웹기반동영상 제작편집 : http://ktk350.blog.me/100144575612

Node.js란? : http://www.ibm.com/developerworks/kr/library/os-nodejs/index.html

CoffeeScript 의 첫경험 : http://www.ibm.com/developerworks/kr/library/os-nodejs/index.html

html5로 flac파일 재생하기 : aurora.js, ofmlabs 의 github

The Node Beginner Book Korean Edition : http://www.nodebeginner.org/index-kr.html


블로그 이미지

희망잡이

,