Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Branch: master
Fetching contributors…

Cannot retrieve contributors at this time

3501 lines (3098 sloc) 261.567 kB
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Nodeビギナーズブック » Node.jsチュートリアル » Node.js 教程</title>
<meta name="description" content="本書は、Node.jsでのアプリケーション開発を始めようとする皆さんに、”高度な”JavaScriptについて知るべきあらゆることを解説します。よくある”Hello World”チュートリアルの、はるか上をいくものです。" />
<link rel="icon" href="favicon.png" type="image/png" />
<link rel="stylesheet" type="text/css" href="default.css" />
<style type="text/css">
body {
font-family: "MS P明朝", "ヒラギノ明朝 ProN W3","Hiragino Mincho ProN", Georgia, serif;
}
</style>
<script type="text/javascript">
// Google Analytics
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-2127388-6']);
_gaq.push(['_trackPageview']);
(function() {
var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
// Disqus
var disqus_shortname = 'nodebeginner';
var disqus_identifier = 'nodebeginner-book';
var disqus_url = 'http://www.nodebeginner.org/';
(function() {
var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
dsq.src = 'http://' + disqus_shortname + '.disqus.com/embed.js';
(document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
})();
// CSS browser select
function css_browser_selector(u){var ua=u.toLowerCase(),is=function(t){return ua.indexOf(t)>-1},g='gecko',w='webkit',s='safari',o='opera',m='mobile',h=document.documentElement,b=[(!(/opera|webtv/i.test(ua))&&/msie\s(\d)/.test(ua))?('ie ie'+RegExp.$1):is('firefox/2')?g+' ff2':is('firefox/3.5')?g+' ff3 ff3_5':is('firefox/3.6')?g+' ff3 ff3_6':is('firefox/3')?g+' ff3':is('gecko/')?g:is('opera')?o+(/version\/(\d+)/.test(ua)?' '+o+RegExp.$1:(/opera(\s|\/)(\d+)/.test(ua)?' '+o+RegExp.$2:'')):is('konqueror')?'konqueror':is('blackberry')?m+' blackberry':is('android')?m+' android':is('chrome')?w+' chrome':is('iron')?w+' iron':is('applewebkit/')?w+' '+s+(/version\/(\d+)/.test(ua)?' '+s+RegExp.$1:''):is('mozilla/')?g:'',is('j2me')?m+' j2me':is('iphone')?m+' iphone':is('ipod')?m+' ipod':is('ipad')?m+' ipad':is('mac')?'mac':is('darwin')?'mac':is('webtv')?'webtv':is('win')?'win'+(is('windows nt 6.0')?' vista':''):is('freebsd')?'freebsd':(is('x11')||is('linux'))?'linux':'','js'];c=b.join(' ');h.className+=' '+c;return c;};css_browser_selector(navigator.userAgent);
</script>
</head>
<body>
<img style="display: none;" src="the_node_beginner_book_cover_medium.png" height="256" width="171" />
<div id="forkmeongithub">
<a href="https://github.com/ManuelKiessling/NodeBeginnerBook"><img src="fork_me_on_github.png" width="149" height="149" alt="Fork me on GitHub" /></a>
</div>
<div id="translations">
<table>
<tr>
<td>
<a href="./">
<div class="flag"><img src="us-flag.png" width="24" height="24" alt="usa flag" /></div>
<div class="text">Read this tutorial in english</div>
</a>
</td>
<td>
<a href="index-es.html">
<div class="flag"><img src="es-flag.png" width="24" height="24" alt="spanish flag" /></div>
<div class="text">Lee este tutorial en Español</div>
</a>
</td>
<td>
<a href="index-kr.html">
<div class="flag"><img src="kr-flag.png" width="24" height="24" alt="korean flag" /></div>
<div class="text">이 튜토리얼을 한글로 보세요</div>
</a>
</td>
</tr>
<tr>
<td>
<a href="index-zh-cn.html">
<div class="flag"><img src="cn-flag.png" width="24" height="24" alt="chinese flag" /></div>
<div class="text">阅读本书中文版</div>
</a>
</td>
<td>
<a href="index-zh-tw.html">
<div class="flag"><img src="cn-flag.png" width="24" height="24" alt="chinese flag" /></div>
<div class="text">阅读本书繁体中文版</div>
</a>
</td>
<td>
<a href="http://www.nodebeginner.ru">
<div class="flag"><img src="ru-flag.png" width="24" height="24" alt="russian flag" /></div>
<div class="text">Читать этот учебник на русском</div>
</a>
</td>
</tr>
</table>
</div>
<div class="buybox">
<div class="buy-the-bundle">
<div class="cover">
<p>
The perfect introduction plus the perfect reference in one bundle!
</p>
<a href="buy-bundle/index.html"><img src="the_node_beginner_book_cover_small.png" height="86" width="57" /></a>
<a href="buy-bundle/index.html"><img src="hands-on_node.js_cover.png" height="86" width="57" /></a>
</div>
<div class="description">
<p>
LeanBundle currently offers
the final version of
<br />
<strong>The Node Beginner Book</strong>
<br />
plus Pedro Teixeira's excellent
<br />
<strong>Hands-on Node.js</strong> for only
<br />
<br />
<strong class="price dollarsign">$</strong><strong class="price">9.99</strong>
<br />
(regular price <del>$21.98</del>)
</p>
</div>
<div class="buy">
<p>
226 pages in total
<br />
PDF, ePub & MOBI
<br />
Direct download
<br />
Free updates
</p>
<a class="buttonlink" href="buy-bundle/index.html">
<div class="button">Buy this<br />bundle now</div>
</a>
</div>
</div>
<div class="buy-kindle">
<div class="inner">
<a href="buy-kindle/index.html"><img class="cover" src="the_node_beginner_book_cover_small.png" height="86" width="57" /></a>
<br />
<a href="buy-kindle/reviews.html"><img class="stars" src="amazon_rating_stars.png" width="65" height="12" /></a>
<br />
(<a href="buy-kindle/reviews.html">Read customer reviews</a>)
<br />
<a href="buy-kindle/index.html"><img class="button" src="add_to_kindle_button.png" width="102" height="25"/></a>
</div>
</div>
</div>
<div id="book">
<h1>Nodeビギナーズブック</h1>
<div id="author">
A Node.js tutorial by <a href="http://twitter.com/manuelkiessling">Manuel Kiessling</a>
<br>
Translated by <a href="http://il-all.blogspot.com/">Yuki Kawashima</a>
</div>
<div id="social">
<a href="https://twitter.com/share" class="twitter-share-button" data-url="http://www.nodebeginner.org/index-jp.html" data-text="Nodeビギナーズブック » Node.jsチュートリアル:" data-via="manuelkiessling">Tweet</a>
<br />
<g:plusone size="medium"></g:plusone>
<br />
<div class="fb-like" data-href="http://www.nodebeginner.org/index-jp.html" data-send="false" data-layout="button_count" data-width="450" data-show-faces="false" data-action="recommend"></div>
</div>
<a name="about"></a>
<h2>本書について</h2>
<p>
本書は、Node.jsでのアプリケーション開発を始めようとする皆さんに、
”高度な”JavaScriptについて知るべきあらゆることを解説します。
よくある”Hello World”チュートリアルの、はるか上をいくものです。
</p>
<a name="status"></a>
<h3>ステータス</h3>
<p>
貴方が読んでいるのは、本書のいわゆる最終版となります。
つまり本書は、間違いが見つかった場合や、
Node.jsの新バージョンにおえる変更点を反映する時のみ、改訂されます。
最終更新日は2012年2月12日です。
</p>
<p>
本書内のコードのサンプルは、Node.jsのバージョン0.6.10でテストしています。
</p>
<a name="intended-audience"></a>
<h3>ターゲット読者</h3>
<p>
本書は、Ruby、Python、PHP、Javaのような、少なくともひとつのオブジェクト指向言語を理解しており、
JavaScriptについてはあまり経験がなく、Node.jsについては全く経験がないという、
著者と同じようなバックグラウンドをもつ読者にとって最もフィットする内容となっています。
</p>
<p>
何かしらのプログラミング言語を経験しているデベロッパー向けですので、
本書はデータ型や変数、制御構造などの本当に基本的な事柄はカバーしないということになります。
本書を理解するためには、そのような基本的な事柄は読み進める時点で理解している必要があります。
</p>
<p>
ただし、JavaScriptの関数やオブジェクトは他の言語のものと異なるので、より詳細に解説していきます。
</p>
<a name="structure"></a>
<h3>本書の構造</h3>
<p>
本書を読み終わる頃には、貴方はひとつのWebアプリケーションを完成させることになります。
そのWebアプリケーションでは、ユーザがWebページを閲覧し、ファイルをアップロードすることができます。
</p>
<p>
もちろん、世界を変えるようなものができるわけではないですが、
これらの機能を実現するために”必要最低限”のコードを書くだけではありません。
さらに一歩先へ進み、アプリケーションの持ついくつかの相違点をきれいに分離した、
シンプルだけれども完全なフレームワークを作りあげることができます。
私が言わんとしていることは、すぐにわかることでしょう。
</p>
<p>
まずはNode.jsを使ったJavaScriptでの開発は、
ブラウザ内のJavaScriptの開発と何が違うのかを見てみるところから始めます。
</p>
<p>
次に、古き良き伝統として”Hello World”アプリケーションを作ります。
これは確かに”何かをする”、最も基本的なNode.jsのアプリケーションです。
</p>
<p>
そして、私たちが構築したいと考えている”現実の”アプリケーションの種類について議論します。
このアプリケーションを組み立てるために実装が必要となるパーツを分解し、
それぞれのパーツをステップバイステップで作っていきます。
</p>
<p>
約束通り、これらを通してJavaScriptのより先進的なコンセプトを知り、どう活用し、
そしてなぜ他のプログラミング言語とは異なるコンセプトが意味をなすのかを学ぶことになります。
</p>
<p>
完成したアプリケーションのソースコードは
<a href="https://github.com/ManuelKiessling/NodeBeginnerBook/tree/master/code/application">NodeBeginnerBook Githubリポジトリ</a>
からダウンロードできます。
</p>
<div id="table-of-contents-headline">目次</div>
<div id="table-of-contents">
<ul>
<li><a href="#about">本書について</a>
<ul>
<li><a href="#status">ステータス</a></li>
<li><a href="#intended-audience">ターゲット読者</a></li>
<li><a href="#structure">本書の構造</a></li>
</ul>
</li>
<li><a href="#javascript-and-nodejs">JavaScriptとNode.js</a>
<ul>
<li><a href="#javascript-and-you">JavaScriptと貴方</a></li>
<li><a href="#a-word-of-warning">注意事項</a></li>
<li><a href="#server-side-javascript">サーバサイドJavaScript</a></li>
<li><a href="#hello-world">"Hello World"</a></li>
</ul>
</li>
<li><a href="#a-full-blown-web-application-with-nodejs">Node.jsでの本格的なWebアプリケーション</a>
<ul>
<li><a href="#the-use-cases">ユースケース</a></li>
<li><a href="#the-application-stack">アプリケーションスタック</a></li>
</ul>
</li>
<li><a href="#building-the-application-stack">アプリケーションスタックの構築</a>
<ul>
<li><a href="#a-basic-http-server">基本的なHTTPサーバ</a></li>
<li><a href="#analyzing-our-http-server">HTTPサーバの分析</a></li>
<li><a href="#passing-functions-around">関数をあちこちに渡す</a></li>
<li><a href="#how-function-passing-makes-our-http-server-work">関数渡しでHTTPサーバを動かす方法</a></li>
<li><a href="#event-driven-callbacks">イベント駆動コールバック</a></li>
<li><a href="#how-our-server-handles-requests">サーバによるリクエストの取り扱い方</a></li>
<li><a href="#finding-a-place-for-our-server-module">サーバモジュールの場所の確保</a>
</li>
<li><a href="#whats-needed-to-route-requests">リクエストを”ルーティング”するために必要なものは?</a></li>
<li><a href="#execution-in-the-kongdom-of-verbs">動詞王国での実行</a></li>
<li><a href="#routing-to-real-request-handlers">本当のリクエストハンドラへのルーティング</a></li>
<li><a href="#making-the-request-handlers-respond">リクエストハンドラによる応答</a>
<ul>
<li><a href="#how-to-not-do-it">うまくいかない方法</a></li>
<li><a href="#blocking-and-non-blocking">ブロッキングとノンブロッキング</a></li>
<li><a href="#responding-request-handlers-with-non-blocking-operations">ノンブロッキング操作で応答するリクエストハンドラ</a>
</li>
</ul>
</li>
<li><a href="#serving-something-useful">有益なものを提供する</a>
<ul>
<li><a href="#handling-post-requests">POSTリクエストの扱い</a></li>
<li><a href="#handling-file-uploads">ファイルアップロードの扱い</a></li>
</ul>
</li>
<li><a href="#conclusion-and-outlook">まとめと展望</a></li>
</ul>
</li>
</ul>
</div>
<a name="javascript-and-nodejs"></a>
<h2>JavaScriptとNode.js</h2>
<a name="javascript-and-you"></a>
<h3>JavaScriptと貴方</h3>
<p>
技術的な話に入る前に、貴方とJavaScriptの関係について話をしておきましょう。
本章は、この文書をさらに読み進めることが貴方にとって意味があるのか、判断してもらいたいのです。
</p>
<p>
もし貴方が私と同じであれば、はるか昔HTMLの文書を書いてHTMLでの”開発”を始めたはずです。
JavaScriptと呼ばれるなんだか面白そうなものに出会い、
つまり貴方のWebページに相互作用を加えるような、とても基本的な使い方だけで利用していたことでしょう。
</p>
<p>
貴方はもっと”本物”が欲しくなり、複雑なWebサイトを構築する方法を学びたくてPHP、
Ruby、Javaなどのプログラミング言語を勉強し、
”バックエンド”となるコードを書き始めたのではないでしょうか。
</p>
<p>
それにも関わらず、JavaScriptに注目してみるとjQueryやPrototypeなどが目に入ってきました。
JavaScriptの世界では物事が進化し、もはやこの言語は、<em>window.open()</em>では済まないのだ、
ということがわかってきたのです。
</p>
<p>
しかしそれは序章に過ぎません。jQueryを自由に扱えればWebページにスパイスを加えることができますが、
結局のところ貴方はまだ、JavaScriptの<em>ユーザ</em>でしかなく、
<em>デベロッパー</em>ではなかったのです。
</p>
<p>
そしていま、Node.jsにたどり着きました。サーバ側でのJavaScriptって、凄いの?
</p>
<p>
そろそろ、古くて新しいJavaScriptをチェックする時期がきたことを確信しました。
でもちょっと待って、Node.jsアプリケーションを書くというのはとても厄介。
なぜそうやって書く必要があるのか、理解しないといけない、
つまりJavaScriptを理解しなければいけないのです。今度こそ、真剣に。
</p>
<p>
ここで問題があります。JavaScriptには2つ、
もしかすると3つの時代があるため(90年代半ばの小さくて可愛らしいDHTMLの助っ人、
jQueryなどのもう少し真剣なフロントエンド、そして現在のサーバサイド)、
JavaScriptを利用するのではなくてJavaScriptで開発しているのだと感じさせてくれるような、
”正しい”使い方を示す情報を探すのは容易ではありません。
</p>
<p>
貴方は経験豊富なデベロッパーなのですから、
なんとなく時間を無駄に過ごしたり間違った使い方をしたりして新しいテクニックを身につけたい、
なんていうことはないでしょう。しっかりと正しい角度でアプローチしていきたいはずです。
</p>
<p>
もちろん素晴らしい文書はそこら中にあります。しかし一つの文書では十分でないこともあります。
必要なのはガイダンスなのです。
</p>
<p>
私の目的は、貴方にガイドを提供することなのです。
</p>
<a name="a-word-of-warning"></a>
<h3>注意事項</h3>
<p>
世の中には本当に素晴らしいJavaScriptエンジニアがいます。でも私は違います。
</p>
<p>
本当に、私は前段で述べたような男に過ぎません。
バックエンドのWebアプリケーションを1つや2つ知っているだけで、
まだまだ”本当の”JavaScriptは学び始めたばかりですし、Node.jsについても同じです。
JavaScriptのより先進的な側面を学んだのは最近で、経験も豊富ではありません。
</p>
<p>
ですので、本書は”初心者から上級者へ”なるためのではなくて、
どちらかというと”初心者から初級者へ”というものです。
</p>
<p>
私がNode.jsを学び始めた時にあったらよかった、そんな文書になっているはずです。
</p>
<a name="server-side-javascript"></a>
<h3>サーバサイドJavaScript</h3>
<p>
最初にJavaScriptが日の目を見たのは、ブラウザ上でした。
しかしこれは単なるコンテキストに過ぎません。
コンテキストによってその言語でできることは決まってきますが、
それはその言語自体ができることとイコールというわけではありません。
JavaScriptは”完全な”言語であり、様々なコンテキストで使えます。
他の言語でやっていることは、すべてJavaScriptでもできます。
</p>
<p>
Node.jsもまた、ひとつのコンテキストに過ぎません。
Node.jsによって、JavaScriptはバックエンド、ブラウザの外で動作できるのです。
</p>
<p>
バックエンドでJavaScriptが動作するには、インタープリターで変換され、
そして実行されなければなりません。これをNode.jsが行います。
内部ではGoogleのV8 VMが利用されています。
V8 VMはGoogle Chromeが使用しているJavaScriptの実行環境そのものと同じです。
</p>
<p>
それに加えて、Node.jsにはたくさんの便利なモジュールが同梱されています。
全てを1から作る必要はないのです。例えばコンソールに文字列を出力するモジュールなどがあります。
</p>
<p>
つまりNode.jsは、実行環境とライブラリの2つから成っているのです。
</p>
<p>
さて、これを使うためには、Node.jsをインストールする必要があります。
<a href="https://github.com/joyent/node/wiki/Installation" title="Building and Installing Node.js">公式サイトのインストール手順</a>を訪れて、またここに戻ってきて下さい。
</p>
<a name="hello-world"></a>
<h3>"Hello World"</h3>
<p>
OK、では冷水に飛び込んで、
はじめてのNode.jsアプリケーション”Hello World”を書いてみるとしましょう。
</p>
<p>
好きなエディタを選んだら、<em>helloworld.js</em>というファイル名で保存して下さい。
このアプリケーションでは”Hello World”を標準出力に書き出します。コードはこの通りです:
</p>
<pre class="prettyprint lang-js"><span class="pln">console</span><span class="pun">.</span><span
class="pln">log</span><span class="pun">(</span><span class="str">"Hello World"</span><span class="pun">);</span></pre>
<p>
ファイルを保存して、Node.jsから動かしてみます:
</p>
<pre>node helloworld.js</pre>
<p>
これで貴方の端末で、<em>Hello World</em>と出力されたはずです。
</p>
<p>
いやあ、退屈ですね。次はもっと本物っぽいものを書いてみましょう。
</p>
<a name="a-full-blown-web-application-with-nodejs"></a>
<h2>Node.jsでの本格的なWebアプリケーション</h2>
<a name="the-use-cases"></a>
<h3>ユースケース</h3>
<p>
シンプルに、しかし現実的に考えてみましょう:
</p>
<ul>
<li>
ユーザは、我々のアプリケーションをWebブラウザから使えるべき
</li>
<li>
ユーザは、http://<em>domain</em>/startにアクセスしたらウェルカムページを見ることができ、
そこではファイルアップロードフォームが表示される
</li>
<li>
アップロードする画像ファイルを選択して送信すると、
画像がhttp://<em>domain</em>/uploadにアップロードされ、
アップロードが一旦完了したらそこに表示される
</li>
</ul>
<p>
結構です。Googleで検索して、<em>何か</em>組み合わせていけばできてしまうでしょう。
でもここでやりたいのはそういうことではないのです。
</p>
<p>
目的を達成するためだけの最も基本的なコードだけを書く、それをやりたいだけではないのです。
綺麗で正確なコードを書きたいのです。
ここからは、もっと複雑なNode.jsアプリケーションを構築していく時の感覚を得るために、
わざと必要以上に抽象的にしていきます。
</p>
<a name="the-application-stack"></a>
<h3>アプリケーションスタック</h3>
<p>
それではアプリケーションを分解していきましょう。
やりたいことを満たすためには、一体どんなものを実装したら良いのでしょうか。
</p>
<ul>
<li>
Webページを提供したい。よって<strong>HTTPサーバ</strong>が必要
</li>
<li>
我々のサーバでは、リクエストが要求しているURLによってそれぞれ別に応答する必要がある。
よってリクエストをリクエストハンドラに結びつけるための<strong>ルータ</strong>のようなものが必要
</li>
<li>
サーバに届き、ルータによって渡されたリクエストの内容に応えるために、
<strong>リクエストハンドラ</strong>が必要
</li>
<li>
ルータはどんなPOSTデータでも取り扱えるべきで、
そのデータをリクエストハンドラに使い易い形式で渡す必要がある。
よって<strong>リクエストデータハンドリング</strong>が必要
</li>
<li>
URLへのリクエストだけを扱いたいのではなく、
それらのURLが要求された時に内容を表示したい。
つまり、コンテンツをユーザのブラウザへ送り返すための、
リクエストハンドラから利用できる<strong>ビューロジック</strong>が必要
</li>
<li>
ユーザは画像をアップロードできるようになるため、何らかの形で、
細かい部分の面倒を見る<strong>アップロードハンドリング</strong>が必要
</li>
</ul>
<p>
ちょっと考えてみて下さい。PHPでこのスタックを作り上げるとしたら、
どうやって実現するでしょうか。何も隠すことはないので言ってしまうと、
だいたいApache HTTPサーバとmod_php5をインストールするのでしょう。
<br>
そう考えると、”Webページを提供して、HTTPリクエストに応答する必要がある”
という部分については、PHPの中で起きているわけではないことに気づきます。
</p>
<p>
さて、nodeの場合、状況が少し異なります。Node.jsでは、
アプリケーションのみを実装するのではなく、HTTPサーバ全体を実装することになります。
我々がこれから作ろうとしているWebアプリケーションとWebサーバは基本的に同一のものなのです。
</p>
<p>
もしかすると大変そうな印象を持たれてしまったかもしれませんが、
Node.jsでは、そうでもないことがすぐにわかるはずです。
</p>
<p>
それではスタックの最初の部分である、HTTPサーバから始めてみましょう。
</p>
<a name="building-the-application-stack"></a>
<h2>アプリケーションスタックの構築</h2>
<a name="a-basic-http-server"></a>
<h3>基本的なHTTPサーバ</h3>
<p>
はじめて私が”本当の”Node.js”アプリケーションを作り始めようとした時は、
どうやってコードを書くのかわかりませんでしたし、コードをどう構成したら良いかもわかりませんでした。
<br>
1つのファイルに全てを書くので良いのでしょうか。Webで見つかるチュートリアルではほとんどの場合で、
Node.jsの基本的なHTTPサーバが、全てのロジックを1カ所で持っているようなものでした。
次の機能を実装しようとした時、コードの可読性を維持することは果たしてできるのでしょうか。
</p>
<p>
モジュールを使えば、やりたいこと別にコードを分離することは、
比較的簡単だということがわかりました。
</p>
<p>
そうすることで、Node.jsで実行するメイン部分のファイルを綺麗な状態に保つことができます。
また、メインファイルから使ったりモジュール同士で相互利用したりするモジュールも、
すっきりさせることができます。
</p>
<p>
アプリケーションを起動するためのメインファイルと、
HTTPサーバのコードから利用されるモジュールファイルを作りましょう。
</p>
<p>
私の印象では、メインファイルには<em>index.js</em>という名前にするのが標準的です。
そしてサーバモジュールには<em>server.js</em>というファイル名を付けます。
</p>
<p>
では、サーバモジュールから始めます。このプロジェクトのルートディレクトリに<em>server.js</em>
という名前のファイルを作成します。ファイルの内容は以下の通りにします:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br><br>http</span><span
class="pun">.</span><span class="pln">createServer</span><span class="pun">(</span><span class="kwd">function</span><span
class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; response</span><span
class="pun">.</span><span class="pln">writeHead</span><span class="pun">(</span><span
class="lit">200</span><span class="pun">,</span><span class="pln"> </span><span
class="pun">{</span><span class="str">"Content-Type"</span><span class="pun">:</span><span
class="pln"> </span><span class="str">"text/plain"</span><span class="pun">});</span><span
class="pln"><br>&nbsp; response</span><span class="pun">.</span><span class="pln">write</span><span
class="pun">(</span><span class="str">"Hello World"</span><span class="pun">);</span><span
class="pln"><br>&nbsp; response</span><span class="pun">.</span><span class="pln">end</span><span
class="pun">();</span><span class="pln"><br></span><span class="pun">}).</span><span
class="pln">listen</span><span class="pun">(</span><span class="lit">8888</span><span
class="pun">);</span></pre>
<p>
これだけです!これで実際に動くHTTPサーバが書けました。
実行して、ちゃんと動くかテストしてみましょう。
まず、このスクリプトをNode.jsから実行します:
</p>
<pre>node server.js</pre>
<p>
それでは、ブラウザを開いて
<a href="http://localhost:8888/" rel="nofollow">http://localhost:8888/</a>
にアクセスしてみましょう。
”Hello World”と表示しているWebページが表示されるはずです。
</p>
<p>
これは、とても気になるのではないでしょうか。我々のプロジェクトをどう構成していくのか、
というテーマを少し離れて、この部分について深堀してみませんか。すぐに本題に戻ることを約束します。
</p>
<a name="analyzing-our-http-server"></a>
<h3>HTTPサーバの分析</h3>
<p>
ではでは、何が起こっていたのか、分析していきます。
</p>
<p>
最初の行で、<em>http</em>モジュールを<em>require</em>(要求)しています。
これにより、Node.jsに同梱されているhttpモジュールへのアクセスが、
変数<em>http</em>を通して可能になります。
</p>
<p>
次に、httpモジュールが提供する関数のひとつである<em>createServer</em>を呼出しています。
この関数はオブジェクトを返しますが、このオブジェクトが<em>listen</em>というメソッドを持っています。
listenメソッドはポート番号を数値の引数として受け取り、HTTPサーバがそのポート番号で待ち受けします。
</p>
<p>
今のところは<em>http.createServer</em>の括弧内にある関数定義の箇所は無視しておいて下さい。
</p>
<p>
もし8888番ポートで待ち受けするサーバを開始したければ、こんなコードでも良かったはずです:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br><br></span><span class="kwd">var</span><span
class="pln"> server </span><span class="pun">=</span><span class="pln"> http</span><span
class="pun">.</span><span class="pln">createServer</span><span class="pun">();</span><span
class="pln"><br>server</span><span class="pun">.</span><span class="pln">listen</span><span class="pun">(</span><span
class="lit">8888</span><span class="pun">);</span></pre>
<p>
これでHTTPサーバが8888番ポートで待ち受けをして、その他、何もすることはありません。
(たとえリクエストがきても応答さえしません)
</p>
<p>
(もしPHPのような、より保守的な言語の経験者であれば特に)面白いのは、
<em>createServer()</em>の呼出しの第1引数に関数定義があることではないでしょうか。
</p>
<p>
この関数の定義は、<em>createServer()</em>を呼び出す際に渡す最初の (そして最後の)引数なのです。
JavaScriptでは、関数をその他の値と同様、あちこちに渡すことができるのです。
</p>
<a name="passing-functions-around"></a>
<h3>関数をあちこちに渡す</h3>
<p>
例えばこんなことができてしまいます:
</p>
<pre class="prettyprint lang-js"><span class="kwd">function</span><span class="pln"> say</span><span
class="pun">(</span><span class="pln">word</span><span class="pun">)</span><span
class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span
class="pln">word</span><span class="pun">);</span><span class="pln"><br></span><span
class="pun">}</span><span class="pln"><br><br></span><span class="kwd">function</span><span class="pln"> execute</span><span
class="pun">(</span><span class="pln">someFunction</span><span class="pun">,</span><span class="pln"> value</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; someFunction</span><span
class="pun">(</span><span class="pln">value</span><span class="pun">);</span><span
class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>execute</span><span
class="pun">(</span><span class="pln">say</span><span class="pun">,</span><span
class="pln"> </span><span class="str">"Hello"</span><span class="pun">);</span></pre>
<p>
よく読んでみて下さい!ここでは、関数<em>say</em>を<em>execute</em>関数の第1引数として渡しています。
<em>say</em>の戻り値ではなく、<em>say</em>自体を渡しているのです!
</p>
<p>
ここでは<em>say</em>が、関数<em>execute</em>の中にあるローカル変数
<em>someFunction</em>になっています。
そしてexecuteの中では(この変数に括弧をつけて)<em>someFunction()</em>
と書くことができるのです。
</p>
<p>
もちろん<em>say</em>は引数を1つとるので、
<em>execute</em>から<em>someFunction</em>を呼び出す時には、
さらに引数を渡すことができます。
</p>
<p>
このように、関数は、他の関数への引数として名前で渡すことができますが、
何も関数の定義を先にしたうえで、それを渡す、といった手順を踏まなくても良いのです。
関数は、関数を引数として渡す際、その場で定義しても良いのです:
</p>
<pre class="prettyprint lang-js"><span class="kwd">function</span><span class="pln"> execute</span><span
class="pun">(</span><span class="pln">someFunction</span><span class="pun">,</span><span class="pln"> value</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; someFunction</span><span
class="pun">(</span><span class="pln">value</span><span class="pun">);</span><span
class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>execute</span><span
class="pun">(</span><span class="kwd">function</span><span class="pun">(</span><span
class="pln">word</span><span class="pun">){</span><span class="pln"> console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span
class="pln">word</span><span class="pun">)</span><span class="pln"> </span><span
class="pun">},</span><span class="pln"> </span><span class="str">"Hello"</span><span
class="pun">);</span></pre>
<p>
まさに<em>execute</em>が第1引数を期待している場所で、<em>execute</em>
に渡したい関数を定義しています。
</p>
<p>
ここで見たやり方をすると、関数に名前をつけてやる必要がありません。
このような関数を<em>匿名関数(anonymous function)</em>と呼びます。
</p>
<p>
これは”上級の”JavaScriptと呼んでいるものの最たる例ですが、
ステップバイステップでやっていきましょう。
今のところ、JavaScriptでは関数を呼び出す時の引数として関数を渡すことができる、
ということを受け入れて下さい。
あらかじめ定義した関数を変数として割り当ててから渡すこともできますし、
その場で関数を定義して渡すこともできるのです。
</p>
<a name="how-function-passing-makes-our-http-server-work"></a>
<h3>関数渡しでHTTPサーバを動かす方法</h3>
<p>
今知った事を活用すれば、最低限のHTTPサーバをこんな風に書く事ができます:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br><br>http</span><span
class="pun">.</span><span class="pln">createServer</span><span class="pun">(</span><span class="kwd">function</span><span
class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; response</span><span
class="pun">.</span><span class="pln">writeHead</span><span class="pun">(</span><span
class="lit">200</span><span class="pun">,</span><span class="pln"> </span><span
class="pun">{</span><span class="str">"Content-Type"</span><span class="pun">:</span><span
class="pln"> </span><span class="str">"text/plain"</span><span class="pun">});</span><span
class="pln"><br>&nbsp; response</span><span class="pun">.</span><span class="pln">write</span><span
class="pun">(</span><span class="str">"Hello World"</span><span class="pun">);</span><span
class="pln"><br>&nbsp; response</span><span class="pun">.</span><span class="pln">end</span><span
class="pun">();</span><span class="pln"><br></span><span class="pun">}).</span><span
class="pln">listen</span><span class="pun">(</span><span class="lit">8888</span><span
class="pun">);</span></pre>
<p>
もうここで何をしているのかは明確なはずです:
<em>createServer</em>関数に匿名関数として渡しているのです。
</p>
<p>
先のコードをリファクタリングすると、こうすることができます:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br><br></span><span class="kwd">function</span><span
class="pln"> onRequest</span><span class="pun">(</span><span class="pln">request</span><span
class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span
class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; response</span><span
class="pun">.</span><span class="pln">writeHead</span><span class="pun">(</span><span
class="lit">200</span><span class="pun">,</span><span class="pln"> </span><span
class="pun">{</span><span class="str">"Content-Type"</span><span class="pun">:</span><span
class="pln"> </span><span class="str">"text/plain"</span><span class="pun">});</span><span
class="pln"><br>&nbsp; response</span><span class="pun">.</span><span class="pln">write</span><span
class="pun">(</span><span class="str">"Hello World"</span><span class="pun">);</span><span
class="pln"><br>&nbsp; response</span><span class="pun">.</span><span class="pln">end</span><span
class="pun">();</span><span class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>http</span><span
class="pun">.</span><span class="pln">createServer</span><span class="pun">(</span><span class="pln">onRequest</span><span
class="pun">).</span><span class="pln">listen</span><span class="pun">(</span><span
class="lit">8888</span><span class="pun">);</span></pre>
<p>
さて、次はこんな疑問が湧いてくるのではないでしょうか: なぜこんなことをしているの?
</p>
<a name="event-driven-callbacks"></a>
<h3>イベント駆動コールバック</h3>
<p>
先ほどの質問に対する答えは、a) (少なくとも私にとっては)それほど簡単な話ではなく、
b) Node.jsがどのように動作するかの原理に従っているものだから、ということになります。
それはつまり、イベント駆動型のことであり、高速に動作する理由でもあります。
</p>
<p>
もしこの背景について知りたければ、
Felix Geisendörferの素晴らしいエントリ
<a href="http://debuggable.com/posts/understanding-node-js:4bd98440-45e4-4a9a-8ef7-0f7ecbdd56cb">”Understanding node.js”</a>
は一読の価値があります。
</p>
<p>
つまるところ、Node.jsはイベント駆動で動作するということです。
えー、はい、私もです。何を言っているのかさっぱりわかんない。
しかし頑張って説明してみようと思います。
なぜWebベースのアプリケーションをNode.jsで書くことに意味があるのかを。
</p>
<p>
<em>http.createServer</em>メソッドを呼び出す時、
当然ながらサーバの待ち受けは同じポートでしないようにしたいですし、
サーバにHTTPリクエストが届いた時は、他にも何かしたいわけです。
</p>
<p>
問題は、これが非同期で起こるということです: いかなる時でも起こりますし、
サーバ内では動かせるのは単一のプロセスしかないのです。
</p>
<p>
PHPのアプリケーションを書く時は、このようなことに困る事はありません:
HTTPリクエストがあった時はいつでも、
(だいたいはApacheなどの)Webサーバがこのリクエストのために新しいプロセスをフォークし、
PHPスクリプトが最初から最後まで実行されることになります。
</p>
<p>
ですので、制御フローについていうとポート8888番に新しいリクエストが届いた時には、
Node.jsプログラムの中に入ってきており、
おかしなことにならないようそれをうまく取り扱わないといけません。
</p>
<p>
これに精通するためにはいくつか新しい概念を学ぶ必要がありますが、
まさにここが、Node.js/JavaScriptのイベント駆動設計が役に立つ部分なのです。
新しい概念をどのようにサーバのコードに適用するのかを見ていきましょう。
</p>
<p>
サーバは先ほど作りました。サーバを作る時には関数をメソッドに渡しました。
リクエストを受け付けた時は必ず、渡した関数が呼ばれます。
</p>
<p>
実際起こるかどうかはわかりませんが、入ってくるリクエストを取り扱うための場所を作りました。
そう、その渡した関数のことです。最初に定義したとか、匿名で渡したとかは置いておきましょう。
</p>
<p>
この概念を<em>コールバック</em>と呼びます。メソッドに関数を渡した時、
メソッドに紐づいたイベントが発生していた場合に、この関数を<em>コールバック</em>するのです。
</p>
<p>
少なくとも私にとっては、これを理解するのは大変でした。まだ理解が足りないと感じるのであれば、
是非Felixのブログエントリを読んでみて下さい。
</p>
<p>
それではこの新しい概念を使って遊んでみましょう。サーバを作成してから、
HTTPリクエストが発生せず、渡したコールバック関数が呼ばれていなくても、
我々のコードが実行を続けることを証明できるでしょうか。やってみましょう:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br><br></span><span class="kwd">function</span><span
class="pln"> onRequest</span><span class="pun">(</span><span class="pln">request</span><span
class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span
class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request received."</span><span
class="pun">);</span><span class="pln"><br>&nbsp; response</span><span class="pun">.</span><span
class="pln">writeHead</span><span class="pun">(</span><span class="lit">200</span><span
class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span
class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text/plain"</span><span
class="pun">});</span><span class="pln"><br>&nbsp; response</span><span class="pun">.</span><span
class="pln">write</span><span class="pun">(</span><span class="str">"Hello World"</span><span
class="pun">);</span><span class="pln"><br>&nbsp; response</span><span class="pun">.</span><span
class="pln">end</span><span class="pun">();</span><span class="pln"><br></span><span
class="pun">}</span><span class="pln"><br><br>http</span><span class="pun">.</span><span class="pln">createServer</span><span
class="pun">(</span><span class="pln">onRequest</span><span class="pun">).</span><span class="pln">listen</span><span
class="pun">(</span><span class="lit">8888</span><span class="pun">);</span><span class="pln"><br><br>console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Server has started."</span><span
class="pun">);</span></pre>
<p>
<em>onRequest</em>関数(コールバック関数)が呼ばれた時、
テキストを<em>console.log</em>で出力していることに注目して下さい。
その他のテキストはHTTPサーバ開始<em>直後に</em>出力しています。
</p>
<p>
これを起動した時(先ほどと同様、<em>node server.js</em>)、
すぐに”Server has started.”とコマンドライン上で出力されると思います。
(<a href="http://localhost:8888/" rel="nofollow">http://localhost:8888</a>を開いて)
サーバにリクエストを送った時、
”Request received.”というメッセージがコマンドライン上に出力されます。
</p>
<p>
イベント駆動の非同期サーバサイドJavaScriptでのコールバックはうまく動くことがわかりましたね :-)
</p>
<p>
(おそらくサーバは標準出力に、”Request received.”を2回出力したのではないでしょうか。
これはあなたがhttp://localhost:8888/ を開いた時、
ブラウザがfaviconをロードしようとしているためです。)
</p>
<a name="how-our-server-handles-requests"></a>
<h3>サーバによるリクエストの取り扱い方</h3>
<p>
OK、サーバのコードの残り部分をサクッと解析してしまいましょう。
残りはコールバック関数<em>onRequest()</em>の本体です。
</p>
<p>
コールバックによって<em>onRequest()</em>が呼ばれた時、2つの引数、
<em>request</em>と<em>response</em>が渡されます。
</p>
<p>
これらはオブジェクトです。このオブジェクトの持つメソッドを使うと、
発生したHTTPリクエストや、そのリクエストへの応答の詳細を取り扱うことができます
(例えば、サーバにリクエストを行ったブラウザに対して実際に何かを送り返す、というようなこと)。
</p>
<p>
我々のコードがやっているのはこれだけです:
リクエストを受信し、<em>response.writeHead()</em>関数を使って、
HTTPステータスコード200とcontent-typeをHTTPレスポンスヘッダとして、
さらに<em>response.write()</em>関数を使って、
テキスト”Hello World”をHTTPレスポンスのボディーとして送ります。
</p>
<p>
さいごに<em>response.end()</em>を呼び出してレスポンスを完了しています。 </p>
<p>
この時点では<em>request</em>オブジェクトは全く使っていないので、
リクエストの詳細についてはまだ触れません。
</p>
<a name="finding-a-place-for-our-server-module"></a>
<h3>サーバモジュールの場所の確保</h3>
<p>
OK、それでは約束通り、アプリケーションの構成の話に戻りましょう。
先ほどは<em>server.js</em>というファイルにとても基本的なHTTPサーバのコードを書きました。
そしてアプリケーションのブートストラップや他のアプリケーションモジュールを使用して
アプリケーションを起動する<em>index.js</em>というメインファイルを置くことが一般的だという話をしました。
(<em>server.js</em>にある他のHTTPサーバモジュールと同じです)。
</p>
<p>
それでは、server.jsをまだ書いていない<em>index.js</em>メインファイルから使えるよう、
本当のNode.jsモジュールにする方法について話していきましょう。
</p>
<p>
みなさんお気づきのように、コードの中で既に書いています:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br><br></span><span class="pun">...</span><span
class="pln"><br><br>http</span><span class="pun">.</span><span class="pln">createServer</span><span
class="pun">(...);</span></pre>
<p>
Node.jsのどこかに”http”と呼ばれるモジュールがあるので、
コード内でrequireを行い、その結果をローカル変数に割り当てることでそれを使うことができます。
</p>
<p>
これによりローカル変数にはオブジェクトが割り当てられ、
<em>http</em>モジュールが提供する全てのパブリックメソッドを使うことができます。
</p>
<p>
ローカル変数名にモジュール名をつけるのが慣習ですが、
このように好きな名前を付けることもできます:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> foo </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br><br></span><span class="pun">...</span><span
class="pln"><br><br>foo</span><span class="pun">.</span><span class="pln">createServer</span><span
class="pun">(...);</span></pre>
<p>
Node.js内部のモジュールの使い方はわかりました。では自分でモジュールを作り、
それを使うためにはどのようにしたら良いのでしょうか。
</p>
<p>
<em>server.js</em>スクリプトをモジュールとして作り直してみましょう。
</p>
<p>
そんなに変更する必要はありません。コードをモジュール化するためには、
それを必要とするモジュールのスクリプトに提供するように、
パーツが持っている機能を<em>export</em>する必要があります。
</p>
<p>
今のところ、exportしたいHTTPサーバの機能は単純なものです:
我々のサーバモジュールは単にサーバを起動するだけのスクリプトです。
</p>
<p>
これを実現するには、サーバのコードを<em>start</em>という関数の中に放り込み、
この関数をexportします:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br><br></span><span class="kwd">function</span><span
class="pln"> start</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span
class="pln"><br>&nbsp; </span><span class="kwd">function</span><span class="pln"> onRequest</span><span
class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; &nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request received."</span><span
class="pun">);</span><span class="pln"><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">writeHead</span><span class="pun">(</span><span class="lit">200</span><span
class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span
class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text/plain"</span><span
class="pun">});</span><span class="pln"><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">write</span><span class="pun">(</span><span class="str">"Hello World"</span><span
class="pun">);</span><span class="pln"><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">end</span><span class="pun">();</span><span class="pln"><br>&nbsp; </span><span class="pun">}</span><span
class="pln"><br><br>&nbsp; http</span><span class="pun">.</span><span
class="pln">createServer</span><span class="pun">(</span><span class="pln">onRequest</span><span
class="pun">).</span><span class="pln">listen</span><span class="pun">(</span><span
class="lit">8888</span><span class="pun">);</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Server has started."</span><span
class="pun">);</span><span class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>exports</span><span
class="pun">.</span><span class="pln">start </span><span class="pun">=</span><span
class="pln"> start</span><span class="pun">;</span></pre>
<p>
このようにすれば、サーバのコードが<em>server.js</em>ファイルの中にある状態で、
メインファイル<em>index.js</em>を作り、そこでHTTPサーバを起動することができます。
</p>
<p>
以下の内容の<em>index.js</em>ファイルを作成して下さい:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> server </span><span class="pun">=</span><span
class="pln"> require</span><span class="pun">(</span><span class="str">"./server"</span><span
class="pun">);</span><span class="pln"><br><br>server</span><span class="pun">.</span><span class="pln">start</span><span
class="pun">();</span></pre>
<p>
すぐおわかりだと思いますが、サーバモジュールを他の内部モジュールと同じように使うことができます:
そのファイルをrequireし、変数に割り当てれば、exportされた関数を使うことができるのです。
</p>
<p>
これだけです。では我々のアプリをメインスクリプトから起動してみましょう。
この部分は今までと同じです:
</p>
<pre>node index.js</pre>
<p>
素晴らしいですね。モジュールを作ることで、アプリケーションの特定の部分を別のファイルに移し、
再度つなぎ合わせることができました。
</p>
<p>
アプリケーションの最初の部分、HTTPリクエストを受け付ける、という部分しか残っていません。
ただ、もう少しだけやりたいことがあります –
ブラウザからリクエストのあったURLによって応答のしかたを変えたいですね。
</p>
<p>
単純なアプリケーションとしては、<em>onRequest()</em>
コールバック関数の中で直接やることもできます。
しかし先ほど述べた通り、もう少しだけ抽象レイヤーを追加して、
サンプルアプリケーションを面白くしてみましょう。
</p>
<p>
異なるHTTPリクエストに応じて行き先となるコードを変えることを”ルーティング”と呼びます –
では、<em>router</em>と名付けてモジュールを作っていきましょう。
</p>
<a name="whats-needed-to-route-requests"></a>
<h3>リクエストを”ルーティング”するために必要なものは?</h3>
<p>
リクエストURLに加えて、GETやPOSTパラメータをルータに渡してやる必要があり、
ルータはそれに基づいて実行するコードを決定します(この”実行するコード”とは、
我々のアプリケーションの第3の部分となるものです:
つまり、リクエストを受信した時に実際に働くリクエストハンドラ群です)。
</p>
<p>
HTTPリクエストの中を見て、リクエストURLやGET、POSTパラメータを抽出しなければいけません。
ルータの一部としてやるべきか、
サーバの中でやるべきか(もしくはモジュール自身か)という点は議論の余地があるかもしれませんが、
とりあえず現時点ではHTTPサーバの一部分としてやってみましょう。
</p>
<p>
必要な情報はすべて、コールバック関数<em>onRequest()</em>の第一引数として渡される<em>request</em>
オブジェクトから得ることができます。
ただ、内容を解釈するためには、Node.jsの追加のモジュール、
つまり<em>url</em>と<em>querystring</em>が必要となります。
</p>
<a name="head20"></a>
<p>
<em>url</em>モジュールが提供するのは、
URLの各部分(例えば、リクエストパスやクエリ文字列)を抽出するメソッドです。
抽出した後、<em>querystring</em>モジュールでリクエストパラメータのクエリ文字列をパースすることができます:
</p>
<pre> url.parse(string).query
|
url.parse(string).pathname |
| |
| |
------ -------------------
http://localhost:8888/start?foo=bar&amp;hello=world
--- -----
| |
| |
querystring(string)["foo"] |
|
querystring(string)["hello"]
</pre>
<p>
もちろん、<em>querystring</em>を使って
POSTリクエストのパラメータをパースすることもできるのですが、
それは後で見てみることにします。
</p>
<p>
では<em>onRequest()</em>関数に、
ブラウザがリクエストしてきたURLパスを見つけるためのロジックを追加してみましょう:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br></span><span
class="kwd">var</span><span class="pln"> url </span><span class="pun">=</span><span
class="pln"> require</span><span class="pun">(</span><span class="str">"url"</span><span
class="pun">);</span><span class="pln"><br><br></span><span class="kwd">function</span><span
class="pln"> start</span><span class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span
class="pln"><br>&nbsp; </span><span class="kwd">function</span><span class="pln"> onRequest</span><span
class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; &nbsp; </span><span
class="kwd">var</span><span class="pln"> pathname </span><span class="pun">=</span><span class="pln"> url</span><span
class="pun">.</span><span class="pln">parse</span><span class="pun">(</span><span
class="pln">request</span><span class="pun">.</span><span class="pln">url</span><span
class="pun">).</span><span class="pln">pathname</span><span class="pun">;</span><span class="pln"><br>&nbsp; &nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request for "</span><span
class="pln"> </span><span class="pun">+</span><span class="pln"> pathname </span><span
class="pun">+</span><span class="pln"> </span><span class="str">" received."</span><span
class="pun">);</span><span class="pln"><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">writeHead</span><span class="pun">(</span><span class="lit">200</span><span
class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span
class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text/plain"</span><span
class="pun">});</span><span class="pln"><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">write</span><span class="pun">(</span><span class="str">"Hello World"</span><span
class="pun">);</span><span class="pln"><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">end</span><span class="pun">();</span><span class="pln"><br>&nbsp; </span><span class="pun">}</span><span
class="pln"><br><br>&nbsp; http</span><span class="pun">.</span><span
class="pln">createServer</span><span class="pun">(</span><span class="pln">onRequest</span><span
class="pun">).</span><span class="pln">listen</span><span class="pun">(</span><span
class="lit">8888</span><span class="pun">);</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Server has started."</span><span
class="pun">);</span><span class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>exports</span><span
class="pun">.</span><span class="pln">start </span><span class="pun">=</span><span
class="pln"> start</span><span class="pun">;</span></pre>
<p>
はい。これで我々のアプリケーションはリクエストされたURLパスによって、
そのリクエストを区別することができます –
これでリクエストを、(これから書くことになる)ルータを使ってURLパスに基づく
リクエストハンドラにマッピングすることができます。
</p>
<p>
アプリケーションのコンテキストにおいて、
<em>/start</em>というURLに対するリクエストと、
<em>/upload</em>というURLに対するリクエストは、
それぞれ別のコードに渡すことができるわけです。
</p>
<p>
OK、実際のルータのコードを書いてみましょう。
<em>router.js</em>というファイルを作成し、
中身を以下の通り書いて下さい:
</p>
<pre class="prettyprint lang-js"><span class="kwd">function</span><span class="pln"> route</span><span
class="pun">(</span><span class="pln">pathname</span><span class="pun">)</span><span
class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"About to route a request for "</span><span
class="pln"> </span><span class="pun">+</span><span class="pln"> pathname</span><span
class="pun">);</span><span class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>exports</span><span
class="pun">.</span><span class="pln">route </span><span class="pun">=</span><span
class="pln"> route</span><span class="pun">;</span></pre>
<p>
もちろん、このコード自体は何もしませんが、
今のところそれでOKです。色々なロジックをルータの中に書いていく前に、
どうやってこのルータをサーバと連携させるのかを見ていきましょう。
</p>
<p>
我々のHTTPサーバは、ルータのことを知り、利用する必要があります。
依存関係をサーバから密に結合させることもできますが、
様々なプログラミング言語での苦い経験からもわかるとおり、
サーバとルータの相互依存性としては粗に結合させるべきでしょう
(Dependency Injection: 依存性の注入。
背景については
<a href="http://martinfowler.com/articles/injection.html">Martin Fowlerによる素晴らしいエントリ</a>
を読むと良いでしょう)
</p>
<p>
最初にサーバの<em>start()</em>関数を拡張して、
ルーティングのための関数を引数で指定して使えるようにしましょう:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br></span><span
class="kwd">var</span><span class="pln"> url </span><span class="pun">=</span><span
class="pln"> require</span><span class="pun">(</span><span class="str">"url"</span><span
class="pun">);</span><span class="pln"><br><br></span><span class="kwd">function</span><span
class="pln"> start</span><span class="pun">(</span><span class="pln">route</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span
class="pln"><br>&nbsp; </span><span class="kwd">function</span><span class="pln"> onRequest</span><span
class="pun">(</span><span class="pln">request</span><span class="pun">,</span><span class="pln"> response</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; &nbsp; </span><span
class="kwd">var</span><span class="pln"> pathname </span><span class="pun">=</span><span class="pln"> url</span><span
class="pun">.</span><span class="pln">parse</span><span class="pun">(</span><span
class="pln">request</span><span class="pun">.</span><span class="pln">url</span><span
class="pun">).</span><span class="pln">pathname</span><span class="pun">;</span><span class="pln"><br>&nbsp; &nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request for "</span><span
class="pln"> </span><span class="pun">+</span><span class="pln"> pathname </span><span
class="pun">+</span><span class="pln"> </span><span class="str">" received."</span><span
class="pun">);</span><span class="pln"><br><br>&nbsp; &nbsp; route</span><span class="pun">(</span><span
class="pln">pathname</span><span class="pun">);</span><span
class="pln"><br><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">writeHead</span><span class="pun">(</span><span class="lit">200</span><span
class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span
class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text/plain"</span><span
class="pun">});</span><span class="pln"><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">write</span><span class="pun">(</span><span class="str">"Hello World"</span><span
class="pun">);</span><span class="pln"><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">end</span><span class="pun">();</span><span class="pln"><br>&nbsp; </span><span class="pun">}</span><span
class="pln"><br><br>&nbsp; http</span><span class="pun">.</span><span
class="pln">createServer</span><span class="pun">(</span><span class="pln">onRequest</span><span
class="pun">).</span><span class="pln">listen</span><span class="pun">(</span><span
class="lit">8888</span><span class="pun">);</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Server has started."</span><span
class="pun">);</span><span class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>exports</span><span
class="pun">.</span><span class="pln">start </span><span class="pun">=</span><span
class="pln"> start</span><span class="pun">;</span></pre>
<p>
そして<em>index.js</em>も拡張しましょう。ルーティングのための関数をサーバに組み込みます:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> server </span><span class="pun">=</span><span
class="pln"> require</span><span class="pun">(</span><span class="str">"./server"</span><span
class="pun">);</span><span class="pln"><br></span><span class="kwd">var</span><span
class="pln"> router </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"./router"</span><span class="pun">);</span><span class="pln"><br><br>server</span><span
class="pun">.</span><span class="pln">start</span><span class="pun">(</span><span
class="pln">router</span><span class="pun">.</span><span class="pln">route</span><span
class="pun">);</span><span class="pln"><br></span></pre>
<p>
また出てきました。関数を渡していますね。これはもう目新しいことではないはずです。
</p>
<p>ここで(<em>node indes.js</em>として)アプリケーションを起動してURLへリクエストを送ったら、
HTTPサーバがルータを使用している様子、それからリクエストされたパス名が渡されている様子が、
アプリケーションの出力内容からわかるはずです:
</p>
<pre>bash$ node index.js
Request for /foo received.
About to route a request for /foo</pre>
<p>
(紛らわしいので/favicon.icoへのリクエストは省略しています)
</p>
<a name="execution-in-the-kongdom-of-verbs"></a>
<h3>動詞王国での実行</h3>
<p>
今一度、少しの間だけ機能的なプログラミングについての話をしても良いでしょうか。
</p>
<p>
関数を渡すのは、技術的な思慮によるものだけではありません。ソフトウェアデザインに関係していて、
哲学的とさえいえます。考えてみて下さい:
indexファイルは、<em>router</em>オブジェクトをサーバに渡します。
そしてサーバはこのオブジェクトの<em>route</em>関数を呼びます。
</p>
<p>
このように、<em>モノ</em>を渡して、サーバが何かを<em>スル</em>ためにこれを使うのです。
ねえルータさん、これをルーティングしてくれない?
</p>
<p>
しかしサーバは何かする必要はありません。何かを<em>やらせて</em>、それをやらせるために、
最終的に何か必要なものがあるわけではなく、
とにかく<em>アクション</em>を必要とします。必要なのは<em>名詞</em>ではなく、<em>動詞</em>なのです。
</p>
<p>
この考え方の核心となる根本的なマインドシフトが理解できてから、
私は機能的プログラミングをとても良く理解することができました。
</p>
<p>
私は、Steve Yeggeによる名作
<a href="http://steve-yegge.blogspot.com/2006/03/execution-in-kingdom-of-nouns.html">Execution in the Kingdom of Nouns</a>
を読んだ時にこの考え方を理解することができたのです。
是非今すぐアクセスしてこれを読んで下さい。
私が今まで読んだソフトウェアに関する書き物の中でも、出会えて嬉しいと思える、最高のものです。
</p>
<a name="routing-to-real-request-handlers"></a>
<h3>本当のリクエストハンドラへのルーティング</h3>
<p>
さて、仕事に戻るとしましょう。HTTPサーバとリクエストルータは、我々の意図した通り、
お互いに会話ができる仲良しの友達となりました。
</p>
<p>
もちろん、それだけでは不十分です。”ルーティング”とは、個々のリクエストを別々に取り扱いたい、
ということを意味しています。私たちが欲しいのは、
<em>/upload</em>へのリクエストや<em>/start</em>へのリクエストをそれぞれ取り扱う”ビジネスロジック”なのです。
</p>
<p>
今、ルーティングはルータの内部で”終了”しています。
これはアプリケーションがもっと複雑になった時にスケールできなくなるからで、
ルータはリクエストに対して実際に何かを”する”場所ではないわけです。
</p>
<p>
では、リクエストが引き渡される<em>リクエストハンドラ</em>を呼び出しましょう。
リクエストハンドラがないと、今までルータを使ってやろうとしていた事の意味がなくなってしまいます。
</p>
<p>
アプリケーションの新しい部分、新しいモジュールなど、 -
驚くような事はここでは一切ありません。
リクエストハンドラと呼ばれるモジュールを作って、
プレースホルダとなる関数を全てのリクエストハンドラに追加し、
モジュールのメソッドとしてエクスポートしましょう:
</p>
<pre class="prettyprint lang-js"><span class="kwd">function</span><span class="pln"> start</span><span
class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request handler 'start' was called."</span><span
class="pun">);</span><span class="pln"><br></span><span class="pun">}</span><span
class="pln"><br><br></span><span class="kwd">function</span><span class="pln"> upload</span><span
class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request handler 'upload' was called."</span><span
class="pun">);</span><span class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>exports</span><span
class="pun">.</span><span class="pln">start </span><span class="pun">=</span><span
class="pln"> start</span><span class="pun">;</span><span class="pln"><br>exports</span><span
class="pun">.</span><span class="pln">upload </span><span class="pun">=</span><span
class="pln"> upload</span><span class="pun">;</span></pre>
<p>
こうして、ルータにルーティングする先のものを渡すことで、
リクエストハンドラとルータを繋ぎ合わせることができます。
</p>
<p>
この時点で私たちは決断をする必要があります:
リクエストハンドラのモジュールはルータの中にハードコーディングするのか、
もしくは少しだけ依存性の注入を行うのか。
依存性の注入は、他のパターンも同様ですが、使いたいというだけで使うべきではありません。
ただしこのケースでは、
ルータとリクエストハンドラを粗に結合することでルータの再利用性を高められるという意味がしっかりあります。
つまり、リクエストハンドラはサーバからルータに渡す必要があることを意味していますが、
これはまだ間違っているような感じがします。ちゃんと説明するとしたら、
メインファイルからサーバに渡し、そこからさらにルータに渡す、ということになります。
</p>
<p>
つまり、リクエストハンドラはサーバからルータに渡す必要があることを意味していますが、
これはまだ間違っているような感じがします。ちゃんと説明するとしたら、
メインファイルからサーバに渡し、そこからさらにルータに渡す、ということになります。
</p>
<p>
ではどうやってそれを渡すのでしょうか。今現在、2つのハンドラがあります。
しかし実際のアプリケーションでは、この数はもっと増えたり減ったりします。
新しいURLやリクエストが追加される度に、
リクエストをハンドラにマッピングするというつまらない作業をしたくはありません。
かといって<em>if request == x then call handler y</em>
といったようなことをするのでは美しくありません。
</p>
<p>
要素の数が変わって、文字列(おそらくリクエストURL)にマッピングする?ということは、
なんだか連想配列がうまくはまりそうですね。
</p>
<p>
ただ、悪いニュースがあります。JavaScriptでは、連想配列が提供されていないのです。
でも大丈夫。連想配列が必要なら、オブジェクトを使えば良いのです!
</p>
<p>
これについては、次のURLに良い記事があります。
<a href="http://msdn.microsoft.com/en-us/magazine/cc163419.aspx">http://msdn.microsoft.com/en-us/magazine/cc163419.aspx</a>
関連する箇所を引用します:
</p>
<blockquote>
<p>
C++やC#において、オブジェクトについて話をする時は、
クラスや構造体のインスタンスについて触れます。
オブジェクトはそれぞれ異なるプロパティやメソッドを持ちますが、
それらはインスタンス化するためのテンプレート (つまり、クラスのことです)に依存します。
しかしJavaScriptのオブジェクトは、これに当てはまりません。
JavaScriptでは、オブジェクトは単なる名前と値のペアの集合なのです。
よってJavaScriptにおけるオブジェクトと呼ぶものは、
文字列をキーにしたディクショナリであると考えて下さい。
</p>
</blockquote>
<p>
JavaScriptのオブジェクトが単なる名前と値のペアの集合であるなら、
どうやってメソッドを持つのでしょうか?値は、文字列でも良いですし、
数字でも良い、 - そして関数でも良いのです!
</p>
<p>
OK、ではコードに戻ってみましょう。リクエストハンドラのリストをオブジェクトとして渡し、
粗結合にするためにこのオブジェクトを<em>route()</em>に入れてしまいます。
</p>
<p>
まずは、オブジェクトをメインファイルである<em>index.js</em>に配置してしまいましょう:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> server </span><span class="pun">=</span><span
class="pln"> require</span><span class="pun">(</span><span class="str">"./server"</span><span
class="pun">);</span><span class="pln"><br></span><span class="kwd">var</span><span
class="pln"> router </span><span class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"./router"</span><span class="pun">);</span><span class="pln"><br></span><span class="kwd">var</span><span
class="pln"> requestHandlers </span><span class="pun">=</span><span class="pln"> require</span><span
class="pun">(</span><span class="str">"./requestHandlers"</span><span class="pun">);</span><span
class="pln"><br><br></span><span class="kwd">var</span><span class="pln"> handle </span><span
class="pun">=</span><span class="pln"> </span><span class="pun">{}</span><span
class="pln"><br>handle</span><span class="pun">[</span><span class="str">"/"</span><span
class="pun">]</span><span class="pln"> </span><span class="pun">=</span><span class="pln"> requestHandlers</span><span
class="pun">.</span><span class="pln">start</span><span class="pun">;</span><span class="pln"><br>handle</span><span
class="pun">[</span><span class="str">"/start"</span><span class="pun">]</span><span
class="pln"> </span><span class="pun">=</span><span class="pln"> requestHandlers</span><span
class="pun">.</span><span class="pln">start</span><span class="pun">;</span><span class="pln"><br>handle</span><span
class="pun">[</span><span class="str">"/upload"</span><span class="pun">]</span><span
class="pln"> </span><span class="pun">=</span><span class="pln"> requestHandlers</span><span
class="pun">.</span><span class="pln">upload</span><span class="pun">;</span><span class="pln"><br><br>server</span><span
class="pun">.</span><span class="pln">start</span><span class="pun">(</span><span
class="pln">router</span><span class="pun">.</span><span class="pln">route</span><span
class="pun">,</span><span class="pln"> handle</span><span class="pun">);</span></pre>
<p>
<em>handle</em>は、いわば”モノ”(リクエストハンドラの集合)ではありますが、
動詞を使って命名したいと思います。次に出てくるルータのところで、
より自然な表現をすることができるからです。
</p>
<p>
おわかりの通り、
異なるURLを同じリクエストハンドラにマッピングする箇所がとてもシンプルになっています:
<em>“/”</em>と<em>requestHandlers.start</em>というキーと値のペアを追加することで、
<em>/start</em>へのリクエストだけでなく<em>/</em>へのアクセスも同じ<em>start</em>ハンドラに渡す処理が、
とても美しくあざやかに実現できていますね。
</p>
<p>
オブジェクトが定義できたら、
それを追加の引数としてサーバに渡します。
<em>server.js</em>を修正して実際に使えるようにしましょう:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br></span><span
class="kwd">var</span><span class="pln"> url </span><span class="pun">=</span><span
class="pln"> require</span><span class="pun">(</span><span class="str">"url"</span><span
class="pun">);</span><span class="pln"><br><br></span><span class="kwd">function</span><span
class="pln"> start</span><span class="pun">(</span><span class="pln">route</span><span
class="pun">,</span><span class="pln"> handle</span><span class="pun">)</span><span class="pln"> </span><span
class="pun">{</span><span class="pln"><br>&nbsp; </span><span class="kwd">function</span><span
class="pln"> onRequest</span><span class="pun">(</span><span class="pln">request</span><span
class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span
class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; &nbsp; </span><span
class="kwd">var</span><span class="pln"> pathname </span><span class="pun">=</span><span class="pln"> url</span><span
class="pun">.</span><span class="pln">parse</span><span class="pun">(</span><span
class="pln">request</span><span class="pun">.</span><span class="pln">url</span><span
class="pun">).</span><span class="pln">pathname</span><span class="pun">;</span><span class="pln"><br>&nbsp; &nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request for "</span><span
class="pln"> </span><span class="pun">+</span><span class="pln"> pathname </span><span
class="pun">+</span><span class="pln"> </span><span class="str">" received."</span><span
class="pun">);</span><span class="pln"><br><br>&nbsp; &nbsp; route</span><span class="pun">(</span><span
class="pln">handle</span><span class="pun">,</span><span class="pln"> pathname</span><span class="pun">);</span><span
class="pln"><br><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">writeHead</span><span class="pun">(</span><span class="lit">200</span><span
class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span
class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text/plain"</span><span
class="pun">});</span><span class="pln"><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">write</span><span class="pun">(</span><span class="str">"Hello World"</span><span
class="pun">);</span><span class="pln"><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">end</span><span class="pun">();</span><span class="pln"><br>&nbsp; </span><span class="pun">}</span><span
class="pln"><br><br>&nbsp; http</span><span class="pun">.</span><span
class="pln">createServer</span><span class="pun">(</span><span class="pln">onRequest</span><span
class="pun">).</span><span class="pln">listen</span><span class="pun">(</span><span
class="lit">8888</span><span class="pun">);</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Server has started."</span><span
class="pun">);</span><span class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>exports</span><span
class="pun">.</span><span class="pln">start </span><span class="pun">=</span><span
class="pln"> start</span><span class="pun">;</span></pre>
<p>
引数<em>handle</em>を<em>start()</em>関数に追加して、
ハンドルオブジェクトを<em>route()</em>コールバックの第一引数として渡すコードに修正しました。
</p>
<p>
<em>route()</em>関数も同様に変更しましょう。<em>router.js</em>ファイルは以下のよう修正します:
</p>
<pre class="prettyprint lang-js"><span class="kwd">function</span><span class="pln"> route</span><span
class="pun">(</span><span class="pln">handle</span><span class="pun">,</span><span
class="pln"> pathname</span><span class="pun">)</span><span class="pln"> </span><span
class="pun">{</span><span class="pln"><br>&nbsp; console</span><span class="pun">.</span><span
class="pln">log</span><span class="pun">(</span><span class="str">"About to route a request for "</span><span
class="pln"> </span><span class="pun">+</span><span class="pln"> pathname</span><span
class="pun">);</span><span class="pln"><br>&nbsp; </span><span class="kwd">if</span><span
class="pln"> </span><span class="pun">(</span><span class="kwd">typeof</span><span
class="pln"> handle</span><span class="pun">[</span><span class="pln">pathname</span><span
class="pun">]</span><span class="pln"> </span><span class="pun">===</span><span
class="pln"> </span><span class="str">'function'</span><span class="pun">)</span><span
class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; &nbsp; handle</span><span
class="pun">[</span><span class="pln">pathname</span><span class="pun">]();</span><span class="pln"><br>&nbsp; </span><span
class="pun">}</span><span class="pln"> </span><span class="kwd">else</span><span
class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; &nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"No request handler found for "</span><span
class="pln"> </span><span class="pun">+</span><span class="pln"> pathname</span><span
class="pun">);</span><span class="pln"><br>&nbsp; </span><span class="pun">}</span><span
class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>exports</span><span
class="pun">.</span><span class="pln">route </span><span class="pun">=</span><span
class="pln"> route</span><span class="pun">;</span></pre>
<p>
ここで何をしているかというと、パス名に対応するリクエストハンドラが存在するかどうかをチェックし、
もし存在する場合は、紐付けられた関数を単に呼び出しています。
連想配列の要素にアクセスすればオブジェクト経由でリクエストハンドラ関数にアクセスできるので、
先述のように実に自然な<em>handle[パス名]();</em>として、
あたかも”この<em>パス名</em>を<em>handle</em>して下さい”
と言っているように表現することができます。
</p>
<p>
OK、これでサーバ、ルータ、リクエストハンドラをすべて繋ぎ合わせることができました!
アプリケーションを起動し、
<a href="http://localhost:8888/start" rel="nofollow">http://localhost:8888/start</a>
にブラウザからリクエストを送れば、
このように正しくリクエストハンドラが呼び出されていることがわかります:
</p>
<pre>Server has started.
Request for /start received.
About to route a request for /start
Request handler 'start' was called.</pre>
<p>
そして
<a href="http://localhost:8888/" rel="nofollow">http://localhost:8888/</a>
をブラウザで開いた時、このリクエストはさらに、
<em>start</em>リクエストハンドラによって処理されていることもわかります:
</p>
<pre>Request for / received.
About to route a request for /
Request handler 'start' was called.</pre>
<a name="making-the-request-handlers-respond"></a>
<h3>リクエストハンドラによる応答</h3>
<p>
美しい。あとは実際にリクエストハンドラが何かブラウザに送り返せば良いわけですね。
</p>
<p>
ひとつ思い出してみて下さい。
ブラウザがページをリクエストした時に表示される”Hello World”は、
<em>server.js</em>ファイルの<em>onRequest()</em>関数が出しているものです。
</p>
<p>
“ハンドリングリクエスト”とは、
”リクエストに応答する”ということを意味していて、
この<em>onRequest</em>関数がしているように、
リクエストハンドラにブラウザと会話をしてもらう必要があります。
</p>
<a name="how-to-not-do-it"></a>
<h4>うまくいかない方法</h4>
<p>
PHPやRubyの経験を持つデベロッパーにとって、
私たちがやりたいと思うような単刀直入なアプローチは間違えやすい可能性があります:
魔法のように動いて、もっともらしくて、そして突然予期せず失敗に終わってしまうのです。
</p>
<p>
“単刀直入なアプローチ”というのはつまりこういうことです:
ユーザに対して表示したい内容をリクエストハンドラに<em>return()</em>させ、
このレスポンスデータを<em>onRequest</em>関数でユーザに戻す。
</p>
<p>
とりあえずやってみましょう。良いアイデアではないことがわかるはずです。
</p>
<p>
まずリクエストハンドラから始めましょう。
ブラウザに表示させたい内容を戻すようにします。
<em>requestHandlers.js</em>を下記のように修正します:
</p>
<pre class="prettyprint lang-js"><span class="kwd">function</span><span class="pln"> start</span><span
class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request handler 'start' was called."</span><span
class="pun">);</span><span class="pln"><br>&nbsp; </span><span class="kwd">return</span><span
class="pln"> </span><span class="str">"Hello Start"</span><span class="pun">;</span><span
class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br></span><span class="kwd">function</span><span
class="pln"> upload</span><span class="pun">()</span><span class="pln"> </span><span
class="pun">{</span><span class="pln"><br>&nbsp; console</span><span class="pun">.</span><span
class="pln">log</span><span class="pun">(</span><span
class="str">"Request handler 'upload' was called."</span><span class="pun">);</span><span
class="pln"><br>&nbsp; </span><span class="kwd">return</span><span class="pln"> </span><span
class="str">"Hello Upload"</span><span class="pun">;</span><span class="pln"><br></span><span
class="pun">}</span><span class="pln"><br><br>exports</span><span class="pun">.</span><span class="pln">start </span><span
class="pun">=</span><span class="pln"> start</span><span class="pun">;</span><span class="pln"><br>exports</span><span
class="pun">.</span><span class="pln">upload </span><span class="pun">=</span><span
class="pln"> upload</span><span class="pun">;</span></pre>
<p>
これでよし。
同じように、ルータはリクエストハンドラによって返されたものをサーバに渡してやる必要があります。
なので<em>router.js</em>はこのようにします:
</p>
<pre class="prettyprint lang-js"><span class="kwd">function</span><span class="pln"> route</span><span
class="pun">(</span><span class="pln">handle</span><span class="pun">,</span><span
class="pln"> pathname</span><span class="pun">)</span><span class="pln"> </span><span
class="pun">{</span><span class="pln"><br>&nbsp; console</span><span class="pun">.</span><span
class="pln">log</span><span class="pun">(</span><span class="str">"About to route a request for "</span><span
class="pln"> </span><span class="pun">+</span><span class="pln"> pathname</span><span
class="pun">);</span><span class="pln"><br>&nbsp; </span><span class="kwd">if</span><span
class="pln"> </span><span class="pun">(</span><span class="kwd">typeof</span><span
class="pln"> handle</span><span class="pun">[</span><span class="pln">pathname</span><span
class="pun">]</span><span class="pln"> </span><span class="pun">===</span><span
class="pln"> </span><span class="str">'function'</span><span class="pun">)</span><span
class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; &nbsp; </span><span
class="kwd">return</span><span class="pln"> handle</span><span class="pun">[</span><span class="pln">pathname</span><span
class="pun">]();</span><span class="pln"><br>&nbsp; </span><span class="pun">}</span><span
class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span
class="pun">{</span><span class="pln"><br>&nbsp; &nbsp; console</span><span class="pun">.</span><span
class="pln">log</span><span class="pun">(</span><span class="str">"No request handler found for "</span><span
class="pln"> </span><span class="pun">+</span><span class="pln"> pathname</span><span
class="pun">);</span><span class="pln"><br>&nbsp; &nbsp; </span><span class="kwd">return</span><span
class="pln"> </span><span class="str">"404 Not found"</span><span class="pun">;</span><span class="pln"><br>&nbsp; </span><span
class="pun">}</span><span class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>exports</span><span
class="pun">.</span><span class="pln">route </span><span class="pun">=</span><span
class="pln"> route</span><span class="pun">;</span></pre>
<p>
これを見ておわかりのとおり、リクエストがルーティングされなかった時にもいくらかのテキストを返すようにしています。
</p>
<p>
そして最後に、ルータ経由でリクエストハンドラが渡してきた内容をブラウザに返すよう、サーバを改修します。<em>server.js</em>は下記のようにしましょう:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br></span><span
class="kwd">var</span><span class="pln"> url </span><span class="pun">=</span><span
class="pln"> require</span><span class="pun">(</span><span class="str">"url"</span><span
class="pun">);</span><span class="pln"><br><br></span><span class="kwd">function</span><span
class="pln"> start</span><span class="pun">(</span><span class="pln">route</span><span
class="pun">,</span><span class="pln"> handle</span><span class="pun">)</span><span class="pln"> </span><span
class="pun">{</span><span class="pln"><br>&nbsp; </span><span class="kwd">function</span><span
class="pln"> onRequest</span><span class="pun">(</span><span class="pln">request</span><span
class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span
class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; &nbsp; </span><span
class="kwd">var</span><span class="pln"> pathname </span><span class="pun">=</span><span class="pln"> url</span><span
class="pun">.</span><span class="pln">parse</span><span class="pun">(</span><span
class="pln">request</span><span class="pun">.</span><span class="pln">url</span><span
class="pun">).</span><span class="pln">pathname</span><span class="pun">;</span><span class="pln"><br>&nbsp; &nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request for "</span><span
class="pln"> </span><span class="pun">+</span><span class="pln"> pathname </span><span
class="pun">+</span><span class="pln"> </span><span class="str">" received."</span><span
class="pun">);</span><span class="pln"><br><br>&nbsp; &nbsp; response</span><span
class="pun">.</span><span class="pln">writeHead</span><span class="pun">(</span><span
class="lit">200</span><span class="pun">,</span><span class="pln"> </span><span
class="pun">{</span><span class="str">"Content-Type"</span><span class="pun">:</span><span
class="pln"> </span><span class="str">"text/plain"</span><span class="pun">});</span><span
class="pln"><br>&nbsp; &nbsp; </span><span class="kwd">var</span><span class="pln"> content </span><span
class="pun">=</span><span class="pln"> route</span><span class="pun">(</span><span
class="pln">handle</span><span class="pun">,</span><span class="pln"> pathname</span><span
class="pun">)</span><span class="pln"><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">write</span><span class="pun">(</span><span class="pln">content</span><span
class="pun">);</span><span class="pln"><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">end</span><span class="pun">();</span><span class="pln"><br>&nbsp; </span><span class="pun">}</span><span
class="pln"><br><br>&nbsp; http</span><span class="pun">.</span><span
class="pln">createServer</span><span class="pun">(</span><span class="pln">onRequest</span><span
class="pun">).</span><span class="pln">listen</span><span class="pun">(</span><span
class="lit">8888</span><span class="pun">);</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Server has started."</span><span
class="pun">);</span><span class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>exports</span><span
class="pun">.</span><span class="pln">start </span><span class="pun">=</span><span
class="pln"> start</span><span class="pun">;</span></pre>
<p>
書き直したアプリケーションを起動すれば、すべては魔法のようにうまく行くことでしょう。つまり
<a href="http://localhost:8888/start" rel="nofollow">http://localhost:8888/start</a>
をリクエストすれば”Hello Start”がブラウザに表示され、
<a href="http://localhost:8888/upload" rel="nofollow">http://localhost:8888/upload</a>
をリクエストすれば”Hello Upload”と、
<a href="http://localhost:8888/foo" rel="nofollow">http://localhost:8888/foo</a>
などとすれば”404 Not found”となります。
</p>
<p>
OK、これで何か問題でも?端的に言うと、もしリクエストハンドラのどれかが、
後述のノンブロッキング操作を行いたいとなった場合に問題となってしまうのです。
</p>
<p>
それでは詳しい説明に入りたいと思います。
</p>
<a name="blocking-and-non-blocking"></a>
<h4>ブロッキングとノンブロッキング</h4>
<p>
先述のとおり、リクエストハンドラ内でノンブロッキングの操作をしようとした時、
問題が発生してしまいます。まずはブロッキングでの操作について、
そしてノンブロッキングの操作について説明していきます。
</p>
<p>
“ブロッキング”と”ノンブロッキング”の方法を説明するかわりに、
まずはいまのリクエストハンドラにブロッキングの操作を追加したら何が起こるのかを見てみましょう。
</p>
<p>
これをやるためには、リクエストハンドラ<em>start</em>を改造して、
”Hello Start”文字列を返すまで10秒間待つようにします。
<em>sleep()</em>の類いはJavaScriptにないので、
ちょっとしたうまい方法で工夫してやる必要があります。
</p>
<p>
では、<em>requestHandlers.js</em>を下記のように修正して下さい:
</p>
<pre class="prettyprint lang-js"><span class="kwd">function</span><span class="pln"> start</span><span
class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request handler 'start' was called."</span><span
class="pun">);</span><span class="pln"><br><br>&nbsp; </span><span class="kwd">function</span><span
class="pln"> sleep</span><span class="pun">(</span><span class="pln">milliSeconds</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; &nbsp; </span><span
class="kwd">var</span><span class="pln"> startTime </span><span class="pun">=</span><span
class="pln"> </span><span class="kwd">new</span><span class="pln"> </span><span
class="typ">Date</span><span class="pun">().</span><span class="pln">getTime</span><span
class="pun">();</span><span class="pln"><br>&nbsp; &nbsp; </span><span class="kwd">while</span><span
class="pln"> </span><span class="pun">(</span><span class="kwd">new</span><span
class="pln"> </span><span class="typ">Date</span><span class="pun">().</span><span
class="pln">getTime</span><span class="pun">()</span><span class="pln"> </span><span
class="pun">&lt;</span><span class="pln"> startTime </span><span class="pun">+</span><span class="pln"> milliSeconds</span><span
class="pun">);</span><span class="pln"><br>&nbsp; </span><span class="pun">}</span><span
class="pln"><br><br>&nbsp; sleep</span><span class="pun">(</span><span class="lit">10000</span><span
class="pun">);</span><span class="pln"><br>&nbsp; </span><span class="kwd">return</span><span
class="pln"> </span><span class="str">"Hello Start"</span><span class="pun">;</span><span
class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br></span><span class="kwd">function</span><span
class="pln"> upload</span><span class="pun">()</span><span class="pln"> </span><span
class="pun">{</span><span class="pln"><br>&nbsp; console</span><span class="pun">.</span><span
class="pln">log</span><span class="pun">(</span><span
class="str">"Request handler 'upload' was called."</span><span class="pun">);</span><span
class="pln"><br>&nbsp; </span><span class="kwd">return</span><span class="pln"> </span><span
class="str">"Hello Upload"</span><span class="pun">;</span><span class="pln"><br></span><span
class="pun">}</span><span class="pln"><br><br>exports</span><span class="pun">.</span><span class="pln">start </span><span
class="pun">=</span><span class="pln"> start</span><span class="pun">;</span><span class="pln"><br>exports</span><span
class="pun">.</span><span class="pln">upload </span><span class="pun">=</span><span
class="pln"> upload</span><span class="pun">;</span></pre>
<p>
何をしているのかというと: 関数<em>start()</em>が呼ばれたら、
Node.jsは10秒間待ち、”Hello Start”と返します。<em>upload()</em>を呼び出した時は、
待つことなくすぐに返します。
</p>
<p>
(<em>start()</em>内で10秒間スリープしている部分は、
実際にはブロッキング操作を行う部分で、
ある種もっと長い計算処理が行われるということを想像して下さい)
</p>
<p>
この変更で何が起こるのか見てみましょう。
</p>
<p>
いつも通り、サーバを再起動する必要があります。
今回は何が起こるのかを見るために、少し複雑な”手順”を追ってもらいます:
まず、2つのブラウザウィンドウもしくはタブを開き、
1つ目のブラウザウィンドウのアドレスバーに
<a href="http://localhost:8888/start" rel="nofollow">http://localhost:8888/start</a>
と入力して下さい。ただし、まだこのURLを開かないで下さい!
</p>
<p>
次に2つめのブラウザウィンドウのアドレスバーに、
<a href="http://localhost:8888/upload" rel="nofollow">http://localhost:8888/upload</a>
と入力し、1つ目と同様、まだenterを打たないで下さい。
</p>
<p>
そしてこのようにします: 1つ目のウィンドウ(“/start”)でenterキーを押し、
すぐに2つめのウィンドウ (“/upload”) に切り替えてenterを打ちます。
</p>
<p>
このような現象に気づくでしょう:
/startのURLの方はロードされるのに10秒間かかります。
これは期待通りの動きです。しかし、
/uploadのURLの方<em>も</em>ロードに10秒間かかってしまっています。
こっちのリクエストハンドラでは<em>sleep()</em>していないのに!
</p>
<p>
なぜでしょう?
それは、<em>start()</em>がブロッキング操作をしているからなのです。
”他のものが動くことをブロックしている”というわけです。
</p>
<p>
これは問題です。なぜなら有名な格言にもあるとおり:
<em>“nodeでは、コード以外のすべてが並列で動作する
(In node, everything runs in parallel, except your code)”</em>
べきだからです。
</p>
<p>
Node.jsはたくさんの処理を同時に行えるけれども、
全てをスレッドに割り振って行うわけではないのです –
実際、Node.jsはシングルスレッドです。そうではなく、
イベントループを走らせることでデベロッパーはこれを利用できるのです –
これらの理由から、ブロッキング操作は極力避け、
かわりにノンブロッキング操作を行うべきなのです。
</p>
<p>
しかしそれを実現するためには、時間のかかる処理になりそうな場合に、
関数を他の関数に渡してコールバック処理を行う必要があります
(例えば、10秒待つ、データベースに問い合わせる、大変な計算をするときなどです) 。
</p>
<p>
つまりこういうことです。
<em>“ねえprobablyExpensiveFunction()(時間がかかりそうな関数)さん、担当の仕事をして下さい。
でも私Node.jsはシングルスレッドだから、あなたの仕事が終わるのを待ってられないんだ。
あなたの次に並んでるコードを実行してしまいたいから、
このcallbackFunction()を持っていって、
その時間のかかりそうな仕事が終わったら呼び出してくれる?じゃあよろしく!”</em>
</p>
<p>
(もしこれについてさらに詳しく読みたいなら、Mixuのポスト
<a href="http://blog.mixu.net/2011/02/01/understanding-the-node-js-event-loop/">Understanding the node.js event loop</a>
を読んでみて下さい。)
</p>
<p>
では、なぜ我々のアプリケーションの”リクエストハンドラによるレスポンスのしかた”
ではノンブロック操作がうまくできないのでしょうか。
</p>
<p>
今一度、アプリケーションを修正して問題を直接再現してみましょう。
</p>
<p>
もう一度<em>start</em>リクエストハンドラを使います。
下記を反映するよう、修正して下さい(<em>requestHandlers.js</em>ファイル):
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> exec </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"child_process"</span><span
class="pun">).</span><span class="pln">exec</span><span class="pun">;</span><span
class="pln"><br><br></span><span class="kwd">function</span><span class="pln"> start</span><span
class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request handler 'start' was called."</span><span
class="pun">);</span><span class="pln"><br>&nbsp; </span><span class="kwd">var</span><span class="pln"> content </span><span
class="pun">=</span><span class="pln"> </span><span class="str">"empty"</span><span class="pun">;</span><span
class="pln"><br><br>&nbsp; exec</span><span class="pun">(</span><span class="str">"ls -lah"</span><span
class="pun">,</span><span class="pln"> </span><span class="kwd">function</span><span
class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span
class="pun">,</span><span class="pln"> stdout</span><span class="pun">,</span><span
class="pln"> stderr</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span
class="pln"><br>&nbsp; &nbsp; content </span><span class="pun">=</span><span
class="pln"> stdout</span><span class="pun">;</span><span class="pln"><br>&nbsp; </span><span
class="pun">});</span><span class="pln"><br><br>&nbsp; </span><span class="kwd">return</span><span
class="pln"> content</span><span class="pun">;</span><span class="pln"><br></span><span
class="pun">}</span><span class="pln"><br><br></span><span class="kwd">function</span><span class="pln"> upload</span><span
class="pun">()</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request handler 'upload' was called."</span><span
class="pun">);</span><span class="pln"><br>&nbsp; </span><span class="kwd">return</span><span
class="pln"> </span><span class="str">"Hello Upload"</span><span class="pun">;</span><span
class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>exports</span><span
class="pun">.</span><span class="pln">start </span><span class="pun">=</span><span
class="pln"> start</span><span class="pun">;</span><span class="pln"><br>exports</span><span
class="pun">.</span><span class="pln">upload </span><span class="pun">=</span><span
class="pln"> upload</span><span class="pun">;</span></pre>
<p>
おわかりのとおり、新しいNode.jsモジュール<em>child_process</em>を使っています。
これのおかげで、とてもシンプルで便利なノンブロッキング操作、<em>exec()</em>を活用できるのです。
</p>
<p>
<em>exec()</em>は、Node.jsの中からシェルコマンドを実行します。
この例においては、カレントディレクトリにあるすべてのファイルのリストを取得してみます(“ls -lah")。
ユーザがURL<em>/start</em>をリクエストするとこのリストがブラウザに表示されます。
</p>
<p>
このコードでやっていることは簡単なことです:
新しい変数<em>content</em>を(“empty”という文字列の初期値で)作成し、
”ls -lah"を実行してその結果を変数に格納し、返すだけです。
</p>
<p>
いつもどおり、アプリケーションを起動して、
<a href="http://localhost:8888/start" rel="nofollow">http://localhost:8888/start</a>にアクセスしてみましょう。
</p>
<p>
すっきりしたWebページがロードされ、文字列”empty”が表示されます。
おや、なにか間違っているのでしょうか?
</p>
<p>
ご想像の通りかもしれません。<em>exec()</em>はノンブロッキングなやりかたで魔法を使ったのです。
まあ良いでしょう、これでとても時間がかかるシェル操作を実行できるのですから
(例えば、巨大なファイルをコピーしまくるとか)。
<em>sleep</em>の時のように、ブロッキングされてアプリケーションが完全に止まってしまうことはないのです。
</p>
<p>
(もしこれを証明したければ、”ls -lah"をもっと時間のかかる操作、
たとえば”find /”みたいなものと入れ替えてみて下さい)
</p>
<p>
しかし、このエレガントなノンブロッキング操作、あまり嬉しくないですよね。
ブラウザが結果を表示してくれないわけですから。そうでしょう?
</p>
<p>
じゃあ、直していきましょう。直しながら、
なぜこのアーキテクチャがうまくいかないのか解明しましょう。
</p>
<p>
問題は<em>exec()</em>です。ノンブロッキングで動作するよう、
コールバック関数を活用します。
</p>
<p>
先ほどの例では、
<em>exec()</em>関数を呼び出す時の第2引数として渡されるのは匿名関数です。
</p>
<pre class="prettyprint lang-js"><span class="kwd">function</span><span class="pln"> </span><span
class="pun">(</span><span class="pln">error</span><span class="pun">,</span><span
class="pln"> stdout</span><span class="pun">,</span><span class="pln"> stderr</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; content </span><span
class="pun">=</span><span class="pln"> stdout</span><span class="pun">;</span><span
class="pln"><br></span><span class="pun">}</span></pre>
<p>
そしてここに我々が直面している問題の原因があります:
このコードは同期式に実行されているため、
<em>exec()</em>を呼び出した直後にNode.jsは<em>return content;</em>の実行へ進んでいます。
この時<em>exec()</em>は非同期式に動作するため、<em>exec()</em>に渡されたコールバック関数が呼び出されておらず、
<em>content</em>はまだ”empty”のままなのです。
</p>
<p>
いまのところ”ls -lah"は(ディレクトリ内にファイルが数百万もない限り)そ
れほど時間がかからず素早く実行されます。
それはコールバックが比較的早く呼び出されるからですが、それでも非同期に実行されます。
</p>
<p>
もっと時間のかかるコマンドで考えてみると明らかになります:
“find /”と実行すると、私のマシンではおよそ1分程度かかります。
しかしリクエストハンドラ内の”ls -lah"を”find /”に置き換えても、
やはり/start URLを開いたとき、すぐにHTTPレスポンスが返ってきます。
つまり<em>exec()</em>はバックグラウンドで実行され、
Node.jsはアプリケーションの実行を続けるということがわかります。
そして<em>exec()</em>に渡したコールバック関数は、
”find /”コマンドの実行が完了した時にだけ呼ばれるであろうことが想像できます。
</p>
<p>
しかし、これでどうやって目的を達成できるのでしょうか。
ユーザに対してカレントディレクトリのファイル一覧を表示したいのです。
</p>
<p>
どうしたらでき<em>ない</em>かを学んだところで、
次はどのようにリクエストハンドラがブラウザに正しく応答させられるかを考えていきましょう。
</p>
<a name="responding-request-handlers-with-non-blocking-operations"></a>
<h4>ノンブロッキング操作で応答するリクエストハンドラ</h4>
<p>
“正しく”というフレーズを使いました。これは危険ですね。ひとつの”正しい方法”なんていうものはないですから。
</p>
<p>
しかし良い方法ならあります。Node.jsでよく見かけるように、関数を渡し合う方法です。少し調べてみましょう。
</p>
<p>
今のところ、アプリケーションは(リクエストハンドラがユーザに表示したい)コンテンツを送ってやることはできます。
リクエストハンドラからHTTPサーバへ、
アプリケーションのレイヤーをまたがって(リクエストハンドラ-&gt;ルータ-&gt;サーバと)返していくわけです。
</p>
<p>
新しいアプローチ方法はこうです:
サーバにコンテンツを渡すのではなく、サーバをコンテンツに渡します。
もう少し正確にいうと、ルータを通して(サーバのコールバック関数<em>onRequest()</em>から受け取った)<em>response</em>オブジェクトを、
リクエストハンドラに渡してしまいます。
それにより、このオブジェクトの関数を使ってリクエストハンドラが自分でリクエストに応答することができます。
</p>
<p>
解説はもういいとして、アプリケーションをステップバイステップで修正していきましょう。
</p>
<p>
<em>server.js</em>からはじめます:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> http </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span
class="str">"http"</span><span class="pun">);</span><span class="pln"><br></span><span
class="kwd">var</span><span class="pln"> url </span><span class="pun">=</span><span
class="pln"> require</span><span class="pun">(</span><span class="str">"url"</span><span
class="pun">);</span><span class="pln"><br><br></span><span class="kwd">function</span><span
class="pln"> start</span><span class="pun">(</span><span class="pln">route</span><span
class="pun">,</span><span class="pln"> handle</span><span class="pun">)</span><span class="pln"> </span><span
class="pun">{</span><span class="pln"><br>&nbsp; </span><span class="kwd">function</span><span
class="pln"> onRequest</span><span class="pun">(</span><span class="pln">request</span><span
class="pun">,</span><span class="pln"> response</span><span class="pun">)</span><span
class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; &nbsp; </span><span
class="kwd">var</span><span class="pln"> pathname </span><span class="pun">=</span><span class="pln"> url</span><span
class="pun">.</span><span class="pln">parse</span><span class="pun">(</span><span
class="pln">request</span><span class="pun">.</span><span class="pln">url</span><span
class="pun">).</span><span class="pln">pathname</span><span class="pun">;</span><span class="pln"><br>&nbsp; &nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request for "</span><span
class="pln"> </span><span class="pun">+</span><span class="pln"> pathname </span><span
class="pun">+</span><span class="pln"> </span><span class="str">" received."</span><span
class="pun">);</span><span class="pln"><br><br>&nbsp; &nbsp; route</span><span class="pun">(</span><span
class="pln">handle</span><span class="pun">,</span><span class="pln"> pathname</span><span
class="pun">,</span><span class="pln"> response</span><span class="pun">);</span><span class="pln"><br>&nbsp; </span><span
class="pun">}</span><span class="pln"><br><br>&nbsp; http</span><span class="pun">.</span><span
class="pln">createServer</span><span class="pun">(</span><span class="pln">onRequest</span><span
class="pun">).</span><span class="pln">listen</span><span class="pun">(</span><span
class="lit">8888</span><span class="pun">);</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Server has started."</span><span
class="pun">);</span><span class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>exports</span><span
class="pun">.</span><span class="pln">start </span><span class="pun">=</span><span
class="pln"> start</span><span class="pun">;</span></pre>
<p>
<em>route()</em>関数からの戻り値は受け取らずに、
第3引数として<em>response</em>オブジェクトを渡しています。
さらに、<em>response</em>オブジェクトのメソッドは一切呼び出していません。
これは<em>route</em>にすべて任せようと考えているからです。
</p>
<p>
次に<em>router.js</em>です:
</p>
<pre class="prettyprint lang-js"><span class="kwd">function</span><span class="pln"> route</span><span
class="pun">(</span><span class="pln">handle</span><span class="pun">,</span><span
class="pln"> pathname</span><span class="pun">,</span><span class="pln"> response</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"About to route a request for "</span><span
class="pln"> </span><span class="pun">+</span><span class="pln"> pathname</span><span
class="pun">);</span><span class="pln"><br>&nbsp; </span><span class="kwd">if</span><span
class="pln"> </span><span class="pun">(</span><span class="kwd">typeof</span><span
class="pln"> handle</span><span class="pun">[</span><span class="pln">pathname</span><span
class="pun">]</span><span class="pln"> </span><span class="pun">===</span><span
class="pln"> </span><span class="str">'function'</span><span class="pun">)</span><span
class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; &nbsp; handle</span><span
class="pun">[</span><span class="pln">pathname</span><span class="pun">](</span><span class="pln">response</span><span
class="pun">);</span><span class="pln"><br>&nbsp; </span><span class="pun">}</span><span
class="pln"> </span><span class="kwd">else</span><span class="pln"> </span><span
class="pun">{</span><span class="pln"><br>&nbsp; &nbsp; console</span><span class="pun">.</span><span
class="pln">log</span><span class="pun">(</span><span class="str">"No request handler found for "</span><span
class="pln"> </span><span class="pun">+</span><span class="pln"> pathname</span><span
class="pun">);</span><span class="pln"><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">writeHead</span><span class="pun">(</span><span class="lit">404</span><span
class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span
class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text/plain"</span><span
class="pun">});</span><span class="pln"><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">write</span><span class="pun">(</span><span class="str">"404 Not found"</span><span
class="pun">);</span><span class="pln"><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">end</span><span class="pun">();</span><span class="pln"><br>&nbsp; </span><span class="pun">}</span><span
class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br>exports</span><span
class="pun">.</span><span class="pln">route </span><span class="pun">=</span><span
class="pln"> route</span><span class="pun">;</span></pre>
<p>
同じパターンです: リクエストハンドラからの戻り値を受け取らず、<em>response</em>オブジェクトを渡します。
</p>
<p>
もしリクエストハンドラがない場合、適切に”404”ヘッダとボディーを自分で返します。
</p>
<p>
そして最後に、<em>requestHandlers.js</em>を修正します:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> exec </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"child_process"</span><span
class="pun">).</span><span class="pln">exec</span><span class="pun">;</span><span
class="pln"><br><br></span><span class="kwd">function</span><span class="pln"> start</span><span
class="pun">(</span><span class="pln">response</span><span class="pun">)</span><span
class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request handler 'start' was called."</span><span
class="pun">);</span><span class="pln"><br><br>&nbsp; exec</span><span class="pun">(</span><span
class="str">"ls -lah"</span><span class="pun">,</span><span class="pln"> </span><span class="kwd">function</span><span
class="pln"> </span><span class="pun">(</span><span class="pln">error</span><span
class="pun">,</span><span class="pln"> stdout</span><span class="pun">,</span><span
class="pln"> stderr</span><span class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span
class="pln"><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">writeHead</span><span class="pun">(</span><span class="lit">200</span><span
class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span
class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text/plain"</span><span
class="pun">});</span><span class="pln"><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">write</span><span class="pun">(</span><span class="pln">stdout</span><span
class="pun">);</span><span class="pln"><br>&nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">end</span><span class="pun">();</span><span class="pln"><br>&nbsp; </span><span class="pun">});</span><span
class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br></span><span class="kwd">function</span><span
class="pln"> upload</span><span class="pun">(</span><span class="pln">response</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request handler 'upload' was called."</span><span
class="pun">);</span><span class="pln"><br>&nbsp; response</span><span class="pun">.</span><span
class="pln">writeHead</span><span class="pun">(</span><span class="lit">200</span><span
class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span
class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text/plain"</span><span
class="pun">});</span><span class="pln"><br>&nbsp; response</span><span class="pun">.</span><span
class="pln">write</span><span class="pun">(</span><span class="str">"Hello Upload"</span><span
class="pun">);</span><span class="pln"><br>&nbsp; response</span><span class="pun">.</span><span
class="pln">end</span><span class="pun">();</span><span class="pln"><br></span><span
class="pun">}</span><span class="pln"><br><br>exports</span><span class="pun">.</span><span class="pln">start </span><span
class="pun">=</span><span class="pln"> start</span><span class="pun">;</span><span class="pln"><br>exports</span><span
class="pun">.</span><span class="pln">upload </span><span class="pun">=</span><span
class="pln"> upload</span><span class="pun">;</span></pre>
<p>
リクエストハンドラ関数は、
リクエストに直接応答するためにレスポンスのパラメータを受け取って使用する必要があります。
</p>
<p>
<em>start</em>ハンドラでは、<em>exec()</em>の匿名コールバック関数の中から応答しています。
また<em>upload</em>ハンドラは、”Hello Upload”と応答すること自体は同じですが、
<em>response</em>オブジェクトを使って応答するようにしました。
</p>
<p>
同じように(<em>node index.js</em>として)アプリケーションを起動しましょう。期待通り動くはずです。
</p>
<p>
もし<em>/start</em>で実行される時間のかかる処理のせいで、
<em>/upload</em>へのリクエストがブロックされて応答が返らない、
ということがないか確認したかったら、<em>requestHandlers.js</em>を以下のようにしてみると良いでしょう:
</p>
<pre class="prettyprint lang-js"><span class="kwd">var</span><span class="pln"> exec </span><span
class="pun">=</span><span class="pln"> require</span><span class="pun">(</span><span class="str">"child_process"</span><span
class="pun">).</span><span class="pln">exec</span><span class="pun">;</span><span
class="pln"><br><br></span><span class="kwd">function</span><span class="pln"> start</span><span
class="pun">(</span><span class="pln">response</span><span class="pun">)</span><span
class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request handler 'start' was called."</span><span
class="pun">);</span><span class="pln"><br><br>&nbsp; exec</span><span class="pun">(</span><span
class="str">"find /"</span><span class="pun">,</span><span class="pln"><br>&nbsp; &nbsp; </span><span
class="pun">{</span><span class="pln"> timeout</span><span class="pun">:</span><span
class="pln"> </span><span class="lit">10000</span><span class="pun">,</span><span
class="pln"> maxBuffer</span><span class="pun">:</span><span class="pln"> </span><span
class="lit">20000</span><span class="pun">*</span><span class="lit">1024</span><span
class="pln"> </span><span class="pun">},</span><span class="pln"><br>&nbsp; &nbsp; </span><span
class="kwd">function</span><span class="pln"> </span><span class="pun">(</span><span
class="pln">error</span><span class="pun">,</span><span class="pln"> stdout</span><span
class="pun">,</span><span class="pln"> stderr</span><span class="pun">)</span><span class="pln"> </span><span
class="pun">{</span><span class="pln"><br>&nbsp; &nbsp; &nbsp; response</span><span class="pun">.</span><span
class="pln">writeHead</span><span class="pun">(</span><span class="lit">200</span><span
class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span
class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text/plain"</span><span
class="pun">});</span><span class="pln"><br>&nbsp; &nbsp; &nbsp; response</span><span
class="pun">.</span><span class="pln">write</span><span class="pun">(</span><span
class="pln">stdout</span><span class="pun">);</span><span class="pln"><br>&nbsp; &nbsp; &nbsp; response</span><span
class="pun">.</span><span class="pln">end</span><span class="pun">();</span><span class="pln"><br>&nbsp; &nbsp; </span><span
class="pun">});</span><span class="pln"><br></span><span class="pun">}</span><span class="pln"><br><br></span><span
class="kwd">function</span><span class="pln"> upload</span><span class="pun">(</span><span class="pln">response</span><span
class="pun">)</span><span class="pln"> </span><span class="pun">{</span><span class="pln"><br>&nbsp; console</span><span
class="pun">.</span><span class="pln">log</span><span class="pun">(</span><span class="str">"Request handler 'upload' was called."</span><span
class="pun">);</span><span class="pln"><br>&nbsp; response</span><span class="pun">.</span><span
class="pln">writeHead</span><span class="pun">(</span><span class="lit">200</span><span
class="pun">,</span><span class="pln"> </span><span class="pun">{</span><span
class="str">"Content-Type"</span><span class="pun">:</span><span class="pln"> </span><span class="str">"text/plain"</span><span
class="pun">});</span><span class="pln"><br>&nbsp; response</span><span class="pun">.</span><span
class="pln">write</span><span class="pun">(</span><span class="str">"Hello Upload"</span><span
class="pun">);</span><span class="pln"><br>&nbsp; response</span><span class="pun">.</span><span
class="pln">end</span><span class="pun">();</span><span class="pln"><br></span><span
class="pun">}</span><span class="pln"><br><br>exports</span><span class="pun">.</span><span class="pln">start </span><span
class="pun">=</span><span class="pln"> start</span><span class="pun">;</span><span class="pln"><br>exports</span><span
class="pun">.</span><span class="pln">upload </span><span class="pun">=</span><span
class="pln"> upload</span><span class="pun">;</span></pre>
<p>
これで<a href="http://localhost:8888/start" rel="nofollow">http://localhost:8888/start</a>
へのHTTPリクエストは最低でも10秒間かかることになりますが、
/startがまだ終わっていなくても、
<a href="http://localhost:8888/upload" rel="nofollow">http://localhost:8888/upload</a>
へのリクエストに対する応答はすぐに返ってくるはずです。
</p>
<a name="serving-something-useful"></a>
<h3>有益なものを提供する</h3>
<p>
ここまでのところ、まことに結構な感じですが、
まだお客さんにとって価値のある賞を獲得するようなものは何も作っていません。
</p>
<p>
サーバ、ルータ、リクエストハンドラはここにあります。
やっとコンテンツをサイトに追加して、ユーザがファイルを選択、アップロードし、
アップロード済みのファイルをブラウザで表示できるようにしていきましょう。
ここではシンプルにするため、画像ファイルだけアップロードおよび表示できるようにしたいと思います。