前回は、サーバサイドJavaScriptの動向と共通APIのCommonJS、そしてNode.jsについてまとめた。ここでは、非同期処理実例、Express、MongoDBなど実践Node.jsプログラミングについて解説する。
1 HTTPサーバ処理
HTTPサーバ処理の基本
http.ServerクラスがHTTPサーバ処理の中心となるクラスである。直接newしてhttp.Serverインスタンス生成もできるが、http.createServer関数で生成するのが流儀である。http.Serverを使うHTTPサーバ処理の基本構造は次の流れになる。
- http.Serverオブジェクトの生成
- http.Serverオブジェクトに必要なイベントハンドラを追加(最低でもrequestイベントで何かしないと何もしないHTTPサーバになる)
- http.Serverオブジェクトにlistenメソッド呼び出し
- requestイベントのコールバック関数で、引数のrequestオブジェクトからリクエスト情報を取り出し、引数のresponseオブジェクトにレスポンス処理をする
リクエスト処理
http.ServerRequestクラスでリクエスト処理を行う。読み込みストリームを継承したクラスである。HTTPサーバ処理を入力と出力の視点で見ると、入力に当たるのはリクエストURLとヘッダ値とボディ値である。リクエストヘッダはheadersプロパティの値で得られる。
- URLパース処理:主にGETメソッドを想定してURLパスとクエリパラメータの値を取得する方法
レスポンス処理
http.ServerResponseクラスでレスポンス処理を行う。書き込みストリームを継承したクラスである。
POSTリクエスト処理
POSTリクエストのフォームデータを受け取るには、HTTPのリクエストボディのデータを読む必要がある。イベントハンドラに渡るボディデータはパースされていない文字列もしくはバイト列である。
2 HTTPクライアント処理
HTTPクライアント処理はhttp.ClientRequestクラスとhttp.ClientResponseクラスで行う。HTTPクライアント処理の基本構造は以下の4つ。
- http.ClientRequestオブジェクトの生成
- http.ClientRequestオブジェクトのresponseイベントにイベントハンドラを追加
- http.ClientRequestオブジェクトに対してリクエストを書き込む(endメソッドの呼び出しは必須)
- responseイベントのイベントハンドラに渡るhttp.ClientResponseオブジェクトからレスポンス情報を取り出す
3 HTTP処理
ここでは自己証明書を使い簡易なHTTPSサーバを立てる方法を説明する。
opensslコマンドを使う自己証明書の発行方法
以下のようなコードを実行する。
$ openssl req -new -x509 -keyout key.pem -out cert.pem
HTTPSサーバ
生成した秘密鍵と証明書を使うHTTPSサーバのコードを実行する。
4 Socket.IOとWebSocket
Socket.IOはWebSocket限定のパッケージではなく、WebSocketを要素技術の1つに使うより広範囲のWebリアルタイム通信機能を提供するパッケージである。Socket.IOパッケージは、Node.jsを使うサーバサイドJavaScript向けライブラリとWebブラウザ上で動くクライアントサイドJavaScript向けライブラリがセットになっている。
サーバサイド(Node.js)のコードの基本構造は以下の通り。
- Socketオブジェクトのlistenメソッドを呼ぶ
- io.socketsのconnectionイベントでクライアントと接続確立したio.Socketオブジェクトを取得
- io.Socketのemitメソッドでメッセージ送信(任意のイベント名を指定)
- io.Socketのイベントでメッセージ受信
クライアント側(Webブラウザ上のJavaScript)のコードは<script src=”socket.io.js”></script>でSocket.IO付属のクライアント用ファイルを読み込む。コードの基本構造は以下の通り。
- io.connect(‘サーバのURL’)でio.Socketオブジェクトを生成
- io.Socketのemitメソッドでメッセージ送信(任意のイベント名を指定)
- io.Socketのイベントでメッセージ受信
5 低レイヤのネットワークプログラミング
低レイヤネットワーク処理
低レイヤネットワーク処理を行うモジュールはソケットプログラミングを行うものである。HTTPやSMTPなどの決まった通信プロトコルを扱う場合、専用モジュールを使うのが便利である。
ソケットとは
伝統的にネットワークのデータ送受信にはソケットと呼ぶ抽象化層を使う。ソケットは通信先の相手と1対1につながる仮想的な線の端点のことである。低レベルネットワークプログラミングの基本はソケットストリームに対するデータの読み書きである。
- ソケットの種別:受動的ソケット(接続を待ち受けるソケット)と能動的ソケット(自分から接続しにいくソケット)の2種。通常、サーバは受動的ソケットを使い、クライアントは能動的ソケットを使う
- ソケットの動作:能動的ソケットから待ち受けソケットに接続を開始する。接続を確立すると通信の両端でお互いにソケットにデータを読み書きできる。名称や生成経緯は異なるが、受け入れソケットと能動的ソケットの動作には対称性がある
ソケットプログラミングの基本構造
サーバ側のコードの基本構造は以下の7つ。
- net.createServer()を呼んで、返り値でnet.Serverオブジェクトを得る(createServerの引数にconnectionイベントハンドラを渡せる)
- net.Serverオブジェクトのlistenメソッドを呼んで接続待ち状態に入る
- net.Serverオブジェクトのconnectionイベントハンドラの引数でクライアントと接続した受け入れソケット(net.Socketオブジェクト)を得る
- net.Socketオブジェクトにwriteメソッドで書き込み、dataイベントで読み取りを行う(ここのクライアントごとのソケット操作)
- net.Socketオブジェクトのcloseイベントやerrorイベントで個々のクライアントとの接続終了を扱う
- net.Socketオブジェクトのcloseメソッドを呼んでクライアントとの接続を切る
- net.Serverオブジェクトのcloseメソッドを呼んでサーバ側を終了
クライアント側のコードの基本構造は以下の6つ。
- net.createConnection()を呼んでnet.Socketオブジェクトを得る(能動的ソケット)
- net.Socketオブジェクトのconnectメソッドを呼んで、引数で指定したサーバに接続する
- net.Socketオブジェクトのconnectイベントで接続成功を扱う
- net.Socketオブジェクトにwriteメソッドで書き込み、dataイベントで読み取りを行う
- net.Socketオブジェクトのcloseイベントやerrorイベントで個々のサーバとの接続終了を扱う
- net.Socketオブジェクトにcloseメソッドを呼んでサーバとの接続を切る
ソケットプログラミングの具体例
サーバ側のコードはポート9000番でクライアントからの接続を待つ。クライアントからの接続があるとconnectionイベントが発生する。クライアント側のコードはlocalhostのポート9000番に接続する。クライアント側はtelnetやncコマンドでも動作確認できる。
6 ファイル処理
Node.jsのファイル処理にはfsモジュールを使い、ファイルパス関係の処理にはpathモジュールを使う。ファイル処理の関数には同期版と非同期版が存在する。非同期版は関数の引数にコールバック関数を渡す(最後の引数で渡す)。コールバック関数は処理の完了時に呼ばれる。コールバック関数の第1引数はエラー引数である。Node.jsのネットワーク関数はすべてノンブロッキングで非同期である。
本節のサンプルコード
var fs = require(‘fs’);
var fpath = process.argv[2]; //コマンドライン引数
if (!fpath) {
process.exit();
}
ファイルの非同期処理
ファイルのstat関数を非同期に呼び出すコード例は以下の通り。
fs.stat(fpath, function(err, stats) { //引数でコールバック関数を指定
if (err) throw err;
console.log(‘stats: ‘, stats); //コールバック関数の引数statsでファイル情報を得る
});
ファイルの同期処理
同期API版は以下の通り。
var stats = fs.statSync(fpath);
console.log(‘stats: ‘, stats);
ファイル操作系の関数
同期関数の引数は非同期版からコールバック関数を除いたもの。非同期版のコールバック関数に渡る引数はエラーステータスを示す引数の1つ。
ファイル読み込み
Node.jsの汎用的なファイルの読み込みコードはオープンから読み込みまですべて非同期で処理するコード。非同期処理にはコードの可読性が落ちる欠点があるため、ファイル読み込みに関しては同期処理を組み合わせるのも1つの選択である。
ファイル書き込み
一般的にストリームに対するwriteメソッド呼び出しは例外を発生しうるので注意が必要。
ディレクトリ操作
ディレクトリ系APIは以下の3つ。
- mkdir(path, mode, callback):mkdirSync。ディレクトリ作成
- rmdir(path, callback):rmdirSync。ディレクトリ削除
- readdir(path, callback):readdirSync。ディレクトリ一覧の読み込み
ファイルの変更監視
ファイルの変更を監視するには、watchFile関数で監視を開始して、unwatchFile関数で監視を停止する。watchFileの最後の引数にコールバック関数を渡す。
ファイルパス
ファイルパスを扱うAPIがpathモジュールにある。exists系APIを除くとただの文字列処理でI/O処理ではないので非同期APIではない。
7 タイマー
Node.jsは以下の4つのタイマー関数を使える。
- setTimeout(callback, delay, [arg,…]):delayミリ秒後にコールバック関数を引数argで呼ぶ
- clearTimeout(timerld):指定したtimerIDのタイマーコールバック関数を解除
- setInterval(callback, delay, [arg,…]):delayミリ秒ごとにコールバック関数を引数argで呼ぶ
- clearInterval(intervalId):指定したintervalIDのインターバルコールバック関数を解除
8 Express
Expressは同じ作者が開発したConnectをベースにしたWebアプリ作成のためのMVCフレームワークである。
URLルーティング
ExpressのURLルーティングはHTTPのメソッド名に対応したメソッドにURLパスを渡す。app.get、app.post、app.put、app.delなどがある。URLパスには:fooの形式を記載できる。
リクエスト処理
Webアプリのリクエスト処理の主な対象は、主にGETリクエストの場合のクエリパラメータ、主にPOSTリクエストの場合のフォームデータである。クエリパラメータはreq.query[‘foo’]のように値を取得できる。HTMLフォームの入力値をPOSTメソッドで送信すると、WebアプリにはHTTPのボディで値が届く。
レスポンス処理
Expressのレスポンス処理の代表的なメソッドは以下の5つ。
- res.header(key, [val]):レスポンスヘッダをセット
- res.sendfile(path[, options[, callback]]):指定したファイルパスの中身をレスポンスのボディで返す
- res.send(body[, headers, status]):指定した文字列をレスポンスのボディで返す
- res.redirect(url[, status]):指定したURLへリダイレクトするレスポンスを返す
- res.render(view[, options[, fn]]):指定したビューにレスポンス処理を委譲する
scaffold作成機能
Expressにはexpressコマンドが付属している。expressコマンドはscaffold作成機能を提供する。scaffold作成機能とはアプリケーションの骨格となるファイルをコマンド1つで自動作成する機能である。
MVCアーキテクチャ
scaffold作成機能で生成されたapp.jsファイルの中には以下のコードがある。このコードの動作がExpressのMVCアーキテクチャの要になる。
// app.jsから抜粋
app.get(‘/’, function(req, res)){
res.render(‘index’, {
title: ‘Express’
});
});
- MVCの役割分担:MVCのコントローラの役割は、コンテキストオブジェクトを生成してビューに渡すこと、URLルーティング機能、データバインディング処理
テンプレート言語Jade
テンプレート言語は、最終出力のHTMLの不変部分をベースに実行時の可変部分を埋め込むように記述する。Expressのテンプレート言語のデフォルトはJadeである。Jadeの簡単な規則の例は以下の通り。
- 行の先頭にタグ名を書くとHTML要素になる
- タグに続けて書いた文字列はHTML要素の内容になる。複数行にまたがる場合は[|]で連結する
- インデントをつけてタグを書くと入れ子のタグになる
- コロンをつけるとインデントを省略できる
- CSSセレクタ風の表記でid属性とclass属性を指定できる(divの場合はdivを省略可能)
MongoDB(データベース)
ある規模のWebアプリの多くはバックエンドにデータベースが必要である。現時点はMySQLなどのRDBMSを使うのが一般的。Node.jsからRDBMSを扱うパッケージとしてはnodejs-dbがある。ここでは、NoSQLの1つMongoDBをNode.jsから扱う方法を説明する。
- MongoDBの概念:直感的にはJavaScriptのオブジェクトをMongoDBで永続化できるということ。オブジェクトのプロパティはフィールドに対応する。ドキュメントはIDで識別され、IDをキーにしてドキュメントの取得(検索)、更新、削除ができる
Mongooseの実例
MongoDBはスキーマフリーのドキュメント指向のデータベースである。ただしMongooseはスキーマ定義を書く流儀である(詳細は省略)。MongooseでMongoDBに新規ドキュメントを作成するコードは以下の通り。
var mongoose = require(‘mongoose’);
// mydbの部分はデータベース名
mongoose.connect(‘mongodb://localhost/mydb’);
var Schema = mongoose.Schema;
// スキーマ定義
// ‘articles’はコレクション名
var Article = mongoose.model(‘articles’, new Schema({
title : String,
body : String,
date : { type: Date, default: Date.now }
})
);
// 新規ドキュメント
var obj = new Article();
obj.title = ‘hello';
obj.body = ‘hello body';
// 保存
obj.save(function (err) {
if (err) throw err;
});
ExpressとMongooseを使うWebアプリ
- 文書管理アプリ
- モデル作成
- 文書一覧のコントローラ
- 文書一覧のビュー
- 文書表示機能
- 文書作成機能
最後に
『パーフェクトJavaScript』を22回にわたってまとめ、その技術を要約した。最近ではJavaScriptによっていろいろな種類のアプリケーションが書けるようになっている。Webブラウザの拡張、サーバサイドアプリ(Node.js)、LinuxのGUI(Gjs, Gnome Seed)、iPhoneアプリやAndroidアプリ(Titanium Mobile)などである。「日本語、英語に続く第3の言語がJavaScriptです」と著者の1人である土江拓郎氏が言うように、JavaScriptを学んでおくことで将来何かいいことがあるのだろう。
![]() |