Node.js | Node.jsスクリプトの基本
基本スクリプトとhttpオブジェクト
前に作成したスクリプトを見ながら、Node.jsスクリプトの基本を説明する。
var http = require('http');
var server = http.createServer();
server.on('request', doRequest);
server.listen(process.env.PORT, process.env.IP);
console.log('Server running!');
// リクエスト処理
function doRequest(req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.write('Hello World\n');
res.end();
}
httpオブジェクトの読み込み
var http = require('http');
最初に行うのは、必要なライブラリの読み込みである。ここではrequire(..)関数を使う。引数に取り込むオブジェクト名を指定すると、そのオブジェクトが読み込まれて返される。これを変数に代入して利用する。
ここではhttpというオブジェクトを読み込んでいる。これはHTTPの各種機能をまとめたもので、ここからサーバーオブジェクトを作る。
Serverオブジェクトの生成
var server = http.createServer();
httpオブジェクトのcreateServer()関数を呼び出し、http.Serverオブジェクトを作る。これがNode.jsのサーバーになる部分である。このオブジェクトを準備し、必要な設定をしてサーバーとして実行する。
多くの解説では次のような書き方をしている。
http.createServer(function(x){
...必要な処理...
}).listen(x);
createServerの引数に、サーバーがリクエストを受け取ったときの処理を書く。そして、その後に待機するためのlistenメソッドを続けて書く。この書き方も問題ないが、ひとまとまりで書かれているため、慣れるまでは少しわかりにくい。ここでは、createServerには引数がない場合もあり、必要な処理は後から設定できると覚えておこう。
リクエスト処理の設定
server.on('request', doRequest);
http.Serverオブジェクトにはさまざまなイベントが用意されており、その処理を登録する方法もある。onメソッドは指定したイベント処理を登録するもので、第1引数にイベント名、第2引数に処理、つまり関数を指定する。
ここではrequestイベントにdoRequest関数を割り当てている。requestはhttp.Serverオブジェクトがクライアントからのリクエストを受け取ったときに発生するイベントであり、ブラウザーからサーバーへアクセスしたときのサーバー応答処理をここに含める。
待機開始
server.listen(1234);
http.Serverオブジェクトの準備ができたら、listenメソッドを実行する。するとサーバーは待機状態になり、クライアントからリクエストがあればそれを受け取って処理できる。引数にはポート番号を指定する。ほかにもホスト名、バックログ、コールバック関数を指定できるが、まずは第1引数がポート番号だと覚えれば十分である。
コンソール出力
console.log('Server running!');
listenで待機を開始したところで、メッセージをコンソールへ出力している。consoleはコンソール操作用のオブジェクトで、logでログ出力できる。簡単なデバッグにはログ出力が便利である。
リクエスト処理用の関数
これでhttp.Serverオブジェクトの作成、準備、待機開始までの流れは大体わかった。残るのは、リクエストを受け取ったときの処理である。
前の例ではdoRequestという関数を定義し、onメソッドでrequestイベントに組み合わせた。このrequestイベントで実行する処理こそ、クライアントのリクエストを処理するためのものである。
この関数は次の形で定義する。
function 関数名(req, res) {
...... 必要な処理 ......
}
引数には2つのオブジェクトが渡される。
- request: 第1引数にはrequestオブジェクトが渡される。これは
http.IncomingMessageオブジェクトで、クライアントのリクエストに関する機能をまとめている。 - response: 第2引数にはresponseオブジェクトが渡される。これは
http.ServerResponseオブジェクトで、サーバーからクライアントへ返す応答に関する機能をまとめている。
このrequestとresponseを使って、リクエストを受け取ったときの処理を作る。
ヘッダー情報を送る
res.writeHead(200, {'Content-Type': 'text/plain'});
writeHeadはresponseオブジェクトのメソッドで、ヘッダー情報を応答に書き込んで送る。第1引数にはステータスコードを指定し、第2引数にはヘッダー情報を連想配列としてまとめる。
ここではContent-Typeヘッダーにtext/plainを設定している。これにより、返すコンテンツが通常のテキストであることをクライアントへ伝える。
コンテンツを送る
res.write('Hello World\n');
HTTPではヘッダー情報の後にボディ部分となるコンテンツを書く。responseオブジェクトのwriteは、その内容を送るためのものである。引数に指定した値がボディのコンテンツとして書き込まれる。
writeは複数回呼び出せる。呼び出してもまだコンテンツは終了しないため、続けてwriteで追記できる。
コンテンツ出力の完了
res.end();
内容の送信が完了したら、最後にresponseのendを呼び出してコンテンツ出力を完了する。引数に出力する内容を指定することもでき、その場合はその値を書き込んでから終了する。
このendにより応答処理は終了し、そのリクエストの処理が完了する。writeHead、write、endの3つがあれば、クライアントへ返す内容は一通り書ける。
ファイルを読み込むfsオブジェクト
responseのwriteで出力できるとはいえ、HTMLコードをすべてwriteで書かなければならないのかと思うかもしれない。表示するWebページの内容をスクリプト内の文字列として用意するのは現実的ではない。
表示するページの内容はHTMLファイルとして用意し、それを読み込んで表示できなければWebらしくない。そこでfsオブジェクトを使う。
fsオブジェクトの取得
var fs = require('fs');
httpオブジェクトと同じように、fsオブジェクトも読み込んで変数に代入する。
ファイルの読み込み
fs.readFile(ファイルのパス, エンコーディング, コールバック関数);
ファイルを読み込む。これは読み込んだデータを直接返すものではない。非同期に実行される処理だからである。
ファイルの読み込みは時間がかかる処理である。読み込みが完了するまで待ってから次に進む設計にすると、ほかのアクセスも待たされてしまう。そこで、読み込みを開始したらすぐ次の処理へ進むようになっている。読み込みはバックグラウンドで行われ、完了するとあらかじめ設定したコールバック関数が呼ばれる。このように、処理が終わった後で呼び出される関数をコールバック関数と呼ぶ。
readFileの第1引数には読み込むファイルのパス、第2引数にはエンコーディング名、第3引数には読み込み完了後のコールバック関数を指定する。
HTMLファイルの表示
Node.jsスクリプトと同じ場所にhello.htmlを作成する。
hello.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>sample</title>
</head>
<body>
<header>
<h1 id="h1">Sample Page</h1>
</header>
<div role="main">
<p>これはNode.jsの例です。</p>
</div>
</body>
</html>
続いてスクリプトを書く。
sampleapp.js
var http = require('http');
var fs = require('fs');
var server = http.createServer();
server.on('request', doRequest);
server.listen(1234);
console.log('Server running!');
// リクエスト処理
function doRequest(req, res) {
fs.readFile('./hello.html', 'UTF-8',
function(err, data) {
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(data);
res.end();
});
}
hello.htmlの内容が表示される。ここではリクエスト時に実行されるdoRequest関数でhello.htmlを読み込み、表示している。コールバック関数の第1引数にはエラー、第2引数には読み込んだテキストが渡される。読み込み失敗時の処理を用意するなら、if (err) {...}のように書けばよい。
重要なのは、writeHead、write、endをすべてコールバック関数の中で実行している点である。readFileは非同期なので、コールバックの外でres.end()を呼ぶと、ファイルの読み込みが終わる前に応答が終了してしまう。
HTMLの一部をスクリプトで変更する
HTMLに置換用の特別な値を入れておき、それを読み込んで必要な内容に置き換えて出力すれば、簡単なテンプレートのように使える。
hello.html
<!DOCTYPE html>
<html lang="ko">
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>@title@</title>
</head>
<body>
<header>
<h1 id="h1">@title@</h1>
</header>
<div role="main">
<p>@content@</p>
</div>
</body>
</html>
@title@と@content@が置換用の値である。
sampleapp.js
var http = require('http');
var fs = require('fs');
var server = http.createServer();
server.on('request', doRequest);
server.listen(1234);
console.log('Server running!');
// リクエスト処理
function doRequest(req, res) {
var number = Math.floor(Math.random() * 3);
fs.readFile('./hello.html', 'UTF-8',
function(err, data) {
var title = ["ページA", "ページB", "ページC"];
var content = ["これは例として作成したものです。",
"もう1つのコンテンツです。",
"最後に利用したコンテンツです。"];
var data2 = data.
replace(/@title@/g, title[number]).
replace(/@content@/g, content[number]);
res.writeHead(200, {'Content-Type': 'text/html'});
res.write(data2);
res.end();
});
}
ここではテキストを読み込んだ後、replaceで文字列を置き換えている。@title@と@content@を、それぞれtitle配列とcontent配列からランダムに選んだ値に置き換えて出力する。ページを更新するたびに、ランダムに選ばれたテキストが表示されることを確認できる。
これで、スクリプトからHTML内へ小さな値を埋め込めるようになった。ただし、これだけではまだ柔軟性が足りない。より汎用的に表示を生成、操作するには、次にテンプレートエンジンを使う。