2012年12月9日日曜日

Node.jsが面白い件⑤ MongoDBで遊ぶ 〜Mongoose編その②:バリデーション〜

Node.jsが面白い件④ MongoDBで遊ぶ 〜Mongoose編その①〜ではMongooseの基本的な使い方を説明しましたが、今回から数回に分けてMongooseの便利な機能について紹介します。

1. Validator

Mongooseではバリデーション機能として次のようなビルトインバリデータを提供します。
  1. required
  2. 最大/最小(数値)
  3. enum/マッチング(文字列)
前回のスキーマに対して上記のビルトインバリデータを試してみます。まずは前回のスキーマの復習ですが、

   var userSchema  = mongoose.Schema({
        uid : { type: String, required:true, index: {unique:true} },
        name: { first: String, last: String },
        age : { type: Number, min: 1 },
        gender : { type: String, enum: genders }
   });

です。これに、以下のドキュメントを作成します。

   User.create(
      { name: {first: 'KAT', last: 'INTINK'},
        age: 100,
        gender: 'male'
      },(以下省略)

uidを指定していないため、requiredにひっかかり以下のようなエラーとなります。
{ message: 'Validation failed',
  name: 'ValidationError',
  errors: 
   { uid: 
      { message: 'Validator "required" failed for path uid',
        name: 'ValidatorError',
        path: 'uid',
        type: 'required' } } }

次に、ageを-1にしてみます。

    User.create(
      { uid: 'kat',
        name: {first: 'KAT', last: 'INTINK'},
        age: -1,
        gender: 'male'
      }, (以下省略)

minにひっかかり以下のようなエラーとなります。
{ message: 'Validation failed',
  name: 'ValidationError',
  errors: 
   { age: 
      { message: 'Validator "min" failed for path age',
        name: 'ValidatorError',
        path: 'age',
        type: 'min' } } }

genderにenum以外の値、manを指定してみます。

    User.create(
      { uid: 'kat',
        name: {first: 'KAT', last: 'INTINK'},
        age: 100,
        gender: 'man'
      }, (以下省略)

同様に以下のエラーとなります。
{ message: 'Validation failed',
  name: 'ValidationError',
  errors: 
   { gender: 
      { message: 'Validator "enum" failed for path gender',
        name: 'ValidatorError',
        path: 'gender',
        type: 'enum' } } }

また、カスタムバリデータとして以下のように定義することも可能です。

    var userSchema  = mongoose.Schema({
        uid : { type: String, required:true, index: {unique:true} },
        name: { first:
                    {type: String,
                     validate: [function(fname) {
                                   return fname.length < 5;
                               }, 'MaxNameLength']
                    },
                last: String },
        age : { type: Number, min: 1 },
        gender : { type: String, enum: genders }
    });

firstnameが5以上だとMaxNameLengthにひっかかります。試しに以下のドキュメントを作成します。

    User.create(
      { uid: 'kat',
        name: {first: 'KATXX', last: 'INTINK'},
        age: 100,
        gender: 'male'
      }, (以下省略)


以下のようにエラーが発生しました。
{ message: 'Validation failed',
  name: 'ValidationError',
  errors: 
   { 'name.first': 
      { message: 'Validator "MaxNameLength" failed for path name.first',
        name: 'ValidatorError',
        path: 'name.first',
        type: 'MaxNameLength' } } }

バリデーション機能は便利というよりほぼ必須機能になってますが、上記のように簡単に提供された機能を使って実現することができます。

2012年12月8日土曜日

Node.jsが面白い件④ MongoDBで遊ぶ 〜Mongoose編その①〜

今回はNode.js+MongoDB環境でよく使われるMongooseを使ってみたいと思います。MongooseはODM(Object document mapper)の1つで、validationやmiddlewareなどのより高度な機能を提供します。まずはいつものようにインストールから。

 > npm install mongoose


Mongooseでは最初にSchemaを定義してdocumentを生成します。以下、サンプルプログラムです。

var mongoose = require('mongoose');

// enum
var genders = 'male,female,neuter'.split(',');

// DBクローズ処理。
var closeDB = function() {
  db.close(function(err, result) {
    console.log("close!");
  });
};

// コネクション。dbオプションとしてsafe:true, serverオプションとしてauto_reconnect:trueを設定。
var opts = {server:{auto_reconnect:true}, db:{safe:true}};
//var db = mongoose.createConnection('mongodb://localhost/exampleDB2', opts);
var db = mongoose.createConnection('localhost', 'exampleDB2', 27017, opts);

// 接続エラーの場合
db.on('error', console.error.bind(console, 'connection open error.'));

// 接続オープン
db.once('open', function() {
    // サンプルスキーマ
    var userSchema  = mongoose.Schema({
        uid : { type: String, required:true, index: {unique:true} },
        name: { first: String, last: String },
        age : { type: Number, min: 1 },
        gender : { type: String, enum: genders }
    });

    // collectionの定義。
    userSchema.set('collection', 'users');

    // モデルの作成。
    var User = db.model('User', userSchema);

    // インスタンス(document)作成。
    User.create(
      { uid: 'kat',
        name: {first: 'KAT', last:'INTINK'},
        age: 100,
        gender: 'male'
      }, function(err, user) {
        if (err) {
          console.log('[error] create');
          console.log(err);
          closeDB();
        } else {
          console.log('create done.');

          // ドキュメントの検索。
          User.find().exec(function(err, user) {
            if (err) {
              console.log('[error] query');
              console.log(err);
              closeDB();
            } else {
              console.log('query done.');
              console.log(user);

              // ドキュメントの削除。
              User.remove(function(err) {
                if (err) {
                  console.log('[error] remove');
                  console.log(err);
                  closeDB();
                } else {
                  console.log('remove done.');
                  closeDB();
                }
              });
            }
          });
        }
    });
});


Node.jsが面白い件③ MongoDBで遊ぶ 〜基本編〜で説明したNative Driverを使った方法と違い、Modelを通して処理をしていることがわかります。簡単/単純な処理を記述する場合はNative Driverでも十分ですが、世の中のODMと同じようにMongooseにも便利な機能が実装されています。次はそれらの機能について説明してみます。

2012年11月29日木曜日

テンプレート・エンジン {{ mustache }} を使ってみる

{{ mustache }} というテンプレートエンジンを知っていますか?
http://mustache.github.com/

Google Trendで、Javaで有名なテンプレート・エンジンである Apache Velocity と比較したところ、最近では人気度が上回っているようです。



mustache はHTMLやコンフィグファイル・ソースコードを生成するためのシンプルなテンプレートエンジンで、以下のようないろんな言語をサポートしているようです。
Ruby, JavaScript, Python, Erlang, PHP, Perl, Objective-C, Java, .NET, Android, C++, Go, Lua, ooc, ActionScript, ColdFusion, Scala, Clojure, Fantom, CoffeeScript, D, node.js.

ちなみに、「mustache」とは「口ひげ」という意味で、「{」をタグとして利用したテンプレートということから、「{」を横にしたらひげに見えるから「mustache」と名前が付いたようです。



変数定義
早速どういったもの見てみましょう。以下はマニュアルに載っていたサンプルです。

・テンプレート
Hello {{name}}
You have just won ${{value}}!
{{#in_ca}}
Well, ${{taxed_value}}, after taxes.
{{/in_ca}}

・上記のテンプレートに渡すデータ(hash)をJSONもしくはYAMLで定義します。
{
    "name": "Chris",
    "value": 10000,
    "taxed_value": 10000 - (10000 * 0.4),
    "in_ca": true
}

・結果は以下のように表示されます。
Hello Chris
You have just won $10000!
Well, $6000.0, after taxes.

このテンプレートの「{{」と「}}」で囲まれたタグが、渡したデータ(hash)により変更され、結果として出力されます。上記の例では、{{name}}というタグ部分が、データの"name"の値「Chris」に変更されます。同様に{{value}}タグは、"value"の10000になります。


ちなみに、デフォルトで結果はエスケープされます。(例:「<」や「>」は、「&lt;」や「&gt;」に。)これをエスケープしないためには、ひげを3つにするか(例.{{{tag}}})、「{{&」と「}}」で囲みます。

・テンプレート
{{name}}
{{age}}
{{company}}
{{{company}}}

・データ(hash)
{
  "name": "Chris",
  "company": "<b>GitHub</b>"
}

・結果
Chris
&lt;b&gt;GitHub&lt;/b&gt;
<b>GitHub</b><


条件式やループ
mustacheが、「logic-less templates」というように呼ばれている意味は、どうやらif文やfor/while文などのステートメントが含まれないことからロジックレスという名称が付いているようです。ただし、テンプレート・エンジンでステートメント的なものは必要なので、条件文やループなどがどのように表現されているか説明しますと、「{{#」と「}}」で囲むことで実現できるようです。(ちなみに、ブロックの閉じるタグは「{{/」と「}}」で囲む。)

上記の例ですと、{{#in_ca}}と{{/in_ca}}にあたります。この場合、データ"in_ca"はboolean型なので、if文のように動きます。"in_ca"がtrueなら「Well, ....」が表示され、falseならこの部分が表示されません。(もしくは値が無い場合も表示されません。)

では、ループはというと、これも同様に「{{#」と「}}」で囲みます。ただし、この囲まれたタグの値の型がリストの場合に繰り返されます。以下はサンプルですが、"repo"の値がリストですので、以下のようにリスト分、繰り返されます。

・テンプレート
{{#repo}}
  <b>{{name}}</b>
{{/repo}}

・データ(hash)
{
  "repo": [
    { "name": "resque" },
    { "name": "hub" },
    { "name": "rip" },
  ]
}

・結果
<b>resque</b>
<b>hub</b>
<b>rip</b>

さらに、関数を渡すこともできるようです。これは面白いですね。

・テンプレート
{{#wrapped}}
  {{name}} is awesome.
{{/wrapped}}

・データ(hash)
{
  "name": "Willy",
  "wrapped": function() {
    return function(text) {
      return "<b>" + render(text) + "</b>"
    }
  }
}

・結果
<b>Willy is awesome.</b>

このタグがbooleanでもなく、リストでもない場合に、tagに相当するキー値が存在すれば、以下のようにtrueと同様の動きをするみたいです。

・テンプレート
{{#person?}}
  Hi {{name}}!
{{/person?}}

・データ(hash)
{
  "person?": { "name": "Jon" }
}

・結果
Hi Jon!

また、if文の逆(not if)は以下のように「{{^」と「}}」で囲んで書きます。

・テンプレート
{{#repo}}
  <b>{{name}}</b>
{{/repo}}
{{^repo}}
  No repos
{{/repo}}
Hash:

・データ(hash)
{
  "repo": []
}

・結果
No repos

その他のタグ
あと、コメントは「{{!」と「}}」で囲みます。

別のテンプレートファイルとして定義して読み込みたい場合は、例えばother.mustacheファイルを読み込みたいのであれば、{{>other}}のように「{{>」と「}}」で囲みます。

最後に、「{{」と「}}」以外でテンプレートを作りたい場合、以下のようにテンプレート中で記載することで変更ができます。(この例は、「{{」を「<%」に、「}}」を「%>」に変更)
{{=<% %>=}}

元に戻すには、
<%={{ }}=%>

これをmustache.jsで使うには以下のようなコードで動きます。
var template = 'I'm {{name}}.';
var v = Mustache.to_html(
          template,
          {
            name:"hitocie"
          }
);
console(v);

うーん、結構良くできていますし、Apache Velocityなどと比べると非常にシンプルですね。

今回、ほぼマニュアルの翻訳的な感じになってしまいましたが、ある程度の機能は紹介できたかと思います・・・。

ちなみに、mustacheを拡張したもので、Twitterからオープンソースとして提供されているhogan.jsというものがあります。これは、mustacheの記法で書いたテンプレートをAOT(Ahead Of Time)コンパイルして、JavaScriptに変換するというもので、レンダリングのたびにmustache記法のテンプレートを解析する必要がなく高速に動作するため、WebシステムでJSPなどのように利用できます。


2012年11月24日土曜日

Node.jsが面白い件③ MongoDBで遊ぶ 〜基本編〜

今回はMongoDBの使い方についてです。MongoDBは有名なNoSQLの1つで、Redisと同じようにNode.jsでも簡単に使えます。MongoDB Native NodeJS Driverをダウンロードし、こちらのマニュアルに沿って記述すれば基本的な使い方はO.K.です! ・・・で終わると書いている意味もないので、ここでは基本的な使い方を記述して行きます。 まずはいつも通りnpmでインストール!
 > npm install mongodb

続いて、データベースに接続してコレクションを作成した後ドキュメントを挿入し、コレクションを削除するという簡単なサンプルプログラム。
var mongo = require('mongodb');

// ドキュメントサンプル。
var doc1 = {'key1':'val1'};
var doc2 = {'key2-1':'val2-1', 'key2-2':'val2-2'};
var docs = [doc1, doc2];

// serverの設定。
var server = new mongo.Server('localhost', 27017, {auto_reconnect: true});

// dbの設定。
var db = new mongo.Db('exampleDb', server, {safe:true});
//var db = new mongo.Db('exampleDb', server);

var closeDB = function() {
    db.close(true, function(err, result) {
        console.log("close!");
    });
}

// コネクションオープン。
db.open(function(err, db) {
    if (!err) {
        console.log("open!");
        // コレクションの作成。既に存在する場合はエラー。
        db.createCollection('collection1', {strict:true}, function(cerr, collection) {
            if (!cerr) {
                console.log("create!");

                // ドキュメントの格納。
                collection.insert(docs, function(ierr, res) {
                    if (!ierr) {
                        console.log("insert doc1!");

                        // ドキュメントの検索。
                        collection.find().toArray(function(ferr, items) {
                            if (!ferr) {
                                console.log(items);

                                // コレクションの削除。
                                collection.drop();
                                closeDB();
                            } else {
                                console.log(ferr);

                                collection.drop();
                                closeDB();
                            }
                        });
                    } else {
                        console.log(ierr);

                        collection.drop();
                        closeDB();
                    }
                });
            } else {
                console.log(cerr);

                closeDB();
            }
        });
    } else {
        console.log(err);
    }
});

MongoDBでは、今のところデフォルトで書き込みの結果を待たずにリターンを返します(safe=false)。つまり、書き込みが成功したか失敗したかがすぐにわかりません。これは、データベースを作成する際にsafe=trueを設定することで変更できます。また、書き込みの挙動は以下のように設定することができます。
  1. j:ジャーナル(redoログのようなもの)のコミットを待つ。ジャーナルが設定されていなければそのまま返す。
  2. w:レプリカに指定数反映されるのを待つ。タイムアウト指定も可能。
  3. fsync:ジャーナリングされてない場合は全てのファイルをfsyncするのを待つ。ジャーナリングしている場合は次のグループのコミットを待つ。非推奨なのでjを使うべき。

MongoDBでは動的にcollectionを作成するケースも多いですが、既に作成しようとするcollection存在する場合はエラーを受け取りたい、という場合もあります。その場合はcreateCollectionでstrict:trueを指定します。
  1. createCollectionでstrict:false(デフォルト)の場合、既に存在する場合は何もしない(作成しない)
  2. createCollectionでstrict:trueの場合、既に存在する場合はエラーを返す。
  3. collectionでstrict:false(デフォルト)の場合、コレクションが存在しない場合は最初のinsertで作成する。
  4. collectionでstrict:trueの場合、コレクションが存在しない場合はエラーを返す。
サンプルプログラム内ではinsert/findを実施しています。この辺りはupdateやdeleteも含めて多くの処理がありますので、ドキュメントを見ながら実装していけばよいと思います。
Node.jsというよりMongoDBの話になりましたが、他の言語でMongoDBを使っている開発者ならかなり違和感なく使えそうですね。また、MongoDBはWeb上にもドキュメントが揃ってきていますので、NoSQL初心者でも比較的情報は得やすいですね。

2012年11月22日木曜日

BaaS(Backend as a Service)のプッシュ通知サービス

最近、BaaS(Backend as a Service)っていう言葉をよく聞きます。これは、スマートデバイス(モバイル)アプリケーション向けに、サーバサイド実装の手間を省くため、データベースへの保存・参照、SNSとの連携、プッシュ通知、認証、ファイル保存などの機能を簡単に呼び出すことができるサービスです。(実際はモバイルのみが対象ではないものもあり、本当にモバイル特化の場合は、MBaaSという呼び方をする場合もあるそうです。)これらのサービスにはSDK(開発キット)が提供されておりAPIをコールするだけで呼び出すことができたり、RESTでアクセスする機能が提供されています。
今回は、そのBaaSの中の1つのサービス「プッシュ通知」機能について、各ベンダーごとにどのようなサービスを提供しているのかを見ていきたいと思います。

その前に、プッシュ配信の仕組みについては、ほとんどのベンダーがApple、Google、Microsoftが提供するサービス「APNS(Apple Push Notification Service)」、「GCM(Google Cloud Messaging for Android)」、もしくはMicrosoftの「WNS(Windows Push Notification Service)」をベースに作っています。簡単に言えばラップしているだけのサービスなので、以下のようにBaaSベンダーはプッシュされたメッセージを各ベンダーごとに振り分けるというようなサーバを用意しているというイメージになります。


このことから、以下のページを参考にして、自分で実装することもできます。

ただ、簡単になっていますので、BaaSを使った方が便利ではありますね。

では、各BaaSベンダーのサービスです。

Microsoft Mobile Services
上記でも軽く触れました名前の通りのMicrosoftがAzure上で提供する「Windows Push Notification Service (WNS)」というサービスです。そのため以下のようにWindowsがサポート対象ですが、将来的にはAndroidやiOSにも対応するようです。
・プラットフォーム: Windows Store, Windows Phone
・言語: C#など

ACS(Appcelerator Cloud Services)
モバイル開発ツールで有名な「Titanium Mobile」を作っている会社「Appcelerator」がCocoafishという会社を買収して、ACSという名称で提供しています。
・プラットフォーム: Android or iOS
・言語: JavaScript, REST, AS3, Java for Android

Buddy
ここのサービスは、他とは違いAndroidとiOSに加え、Windows系もサポートしています。内部的には上記のWindows Push Notification Service(WNS)を使っているようです。
・プラットフォーム: Android, iOS, Windows Store, Windows Phone 
・言語: Java, Objective-C, C#

appiaries(国産)
国産のBaaSのようです。ただ、プッシュ配信は、まだ未提供のようです。将来的にはAndroid/iOS対応予定とのこと。

Parse
・プラットフォーム: Android, iOS
・言語: REST, JavaScript, Java for Android, Objective-C for iOS

StackMob

・プラットフォーム: Android, iOS
・言語: Java for Android, Objective-C for iOS, REST
   
Kinvey
http://www.kinvey.com/
・プラットフォーム: Android, iOS
・言語: Java for Android, Objective-C for iOS

その他、ApplicasaQuickbloxSencha.ioなどの有名なBaaSがありますが、あまり変わらなそうなので、とりあえずこれらは未調査。


上記サービスは基本的に、APNS、GCM、WNSをベースに作られていますが、プッシュ配信と言えば、HTML5のWebSocketが有名です。そこで、WebSocketをベースにプッシュ配信サービスを提供しているベンダーを少し挙げてみます。

pusher
PHP, Ruby, JavaScriptなどWebSocket ClientのAPIがあればアクセス可能

Kaazing
http://kaazing.com/
プッシュ通知サービスは、Kaazing WebSocket Gateway (HTML5 Edition)と呼ばれるようです。こちらもWebSocket Clientのライブラリからアクセスできますが、あらかじめ、Java, JavaScript, .Net, SilverLight, AIR, Flex, Flashなどのライブラリが用意されているようです。また、WebSocket以外にも、JMS, AMQP, XMPPもサポートしています。(ただし、WebSocketほどメジャーではないですが。)

以上がプッシュ通知のサービスですが、今後はBuddyなどのようにAPNS、GCM、WNSを連携するのみでなく、さらにWebSocketなどもサポートされていくのではないかと思います。

2012年11月20日火曜日

iOS デバイスへのプッシュ通知と node.js (プロバイダ編)

iOS デバイスへのプッシュ通知は、APNs (Apple Push Notification service) を使って実装できます。
加えて、配信するコンテンツを提供するサーバーも必要になります。
今回は、ちょうど使ってみたかった node.js で動かしてみます。

APNs の概要


全容を把握したい場合は、やっぱり公式のガイドがいいと思います。

 Local NotificationおよびPush Notificationプログラミングガイド

現時点では日本語版の方が古いということもなく、英語版の Local and Push Notification Programming Guide と同じ内容みたいですので、安心して日本語版を参照できますね。

APNs を利用したプッシュ通知を単純化すると以下のようになります。
(上記、公式ドキュメントからの引用です)


今回 node.js で作るのは、上図のプロバイダに相当するものです。


まずは iOS Provisioning Portal


APNs は実機でしか使えないので、Provisioning Portal で Provisioning Profile を用意します。手順は基本的に通常の iOS アプリ開発時と同じなので割愛。
ただし、App ID については2点ほど注意が必要です。

1. ワイルドカードを使っていない ID を作る
通常 AppID にはワイルドカードの指定が許されていますが、APNs を利用するアプリではワイルドカードは使用できません。

2. 1 の ID で APNs を有効にする
ID 作成後に、Configure から設定に入り、
Enable Push Notification service にチェックを入れて APNs を有効にした後に
証明書(Push SSL Certificate)を取得する必要があります。
証明書には、Development / Production の2つがありますが、今回は Development にします。


プロバイダを node.js で


簡単に APNs を利用できるモジュールがありますので、それを使います。

node-apn
 https://github.com/argon/node-apn

README がしっかりと用意されているので、そちらを読めば使い方は基本的な使い方は把握出来そうです。
READMEからポイントをいくつか転載。

インストールとコード内での利用
これはいつも通りです。
$ npm install apn
var apns = require('apn');

apns.Connection へ渡すオプションについて
デフォルトは以下のようになっています。
var options = {
    cert: 'cert.pem',                 /* Certificate file path */
    certData: null,                   /* String or Buffer containing certificate data, if supplied uses this instead of cert file path */
    key:  'key.pem',                  /* Key file path */
    keyData: null,                    /* String or Buffer containing key data, as certData */
    passphrase: null,                 /* A passphrase for the Key file */
    ca: null,                         /* String or Buffer of CA data to use for the TLS connection */
    gateway: 'gateway.push.apple.com',/* gateway address */
    port: 2195,                       /* gateway port */
    enhanced: true,                   /* enable enhanced format */
    errorCallback: undefined,         /* Callback when error occurs function(err,notification) */
    cacheLength: 100                  /* Number of notifications to cache for error purposes */
};

var apnsConnection = new apns.Connection(options);

  • cert, key には前節 App ID のところで取得した証明書を使う。
  • Development の Push SSL Certificate を使用する場合は、gateway の値を gateway.sandbox.push.apple.com に!
  • enhanced が true の場合、errorCallback が使用できます。(後述)

APNs の証明書
cert.pem, key.pem は以下のコマンドで作れます。

$ openssl x509 -in cert.cer -inform DER -outform PEM -out cert.pem
$ openssl pkcs12 -in key.p12 -out key.pem -nodes
コマンド中の2つのインプットは次のようにして用意します。
cert.cer ... Provisioning Portal の App ID のところで取得したもの(aps_development.cerとか)
key.p12 ... aps_development.cer をダブルクリック等でインストール後、
  Keychain Access.app を起動 -> My Certificates -> Apple Development iOS Push Services を選択 -> File メニューから Export Items を選択
 で取得できます。



errorCallback について
APNs のエラー応答については、前述の公式ドキュメントの「バイナリインターフェイスの形式と通知形式(p.50)」あたりに詳しいです。
node-apn では、オプションの enhanced が true の状態で
errorCallback に function(err, notification) {} を指定することで APNs からのエラーメッセージが拾えます。




feedback について
フィードバックの概要は、公式ドキュメントの「フィードバックサービス(p.55)」で。
var feedback = new apns.Feedback(options);
で簡単に使用できます。

var options = {
    cert: 'cert.pem',                   /* Certificate file */
    certData: null,                     /* Certificate file contents (String|Buffer) */
    key:  'key.pem',                    /* Key file */
    keyData: null,                      /* Key file contents (String|Buffer) */
    passphrase: null,                   /* A passphrase for the Key file */
    ca: null,                           /* Certificate authority data to pass to the TLS connection */
    address: 'feedback.push.apple.com', /* feedback address */
    port: 2196,                         /* feedback port */
    feedback: false,                    /* enable feedback service, set to callback */
    interval: 3600                      /* interval in seconds to connect to feedback service */
};

options も Connection と同じような感じですね。
address は、開発用を使っている場合は Connection と同じように feedback.sandbox.push.apple.com を使用します。
feedback にコールバックをセットするとインターバルの間隔で呼び出されます。この際、コールバックがとる引数は2つ、サーバーが返す time と デバイストークンを含む Buffer です。

debug モジュール
debug モジュール ( https://github.com/visionmedia/debug ) を導入するとデバッグメッセージを有効にできます。
npm install debug.

$ DEBUG=apn node apns.js
のようにすると APNs 接続に関するデバッグメッセージがコンソールに表示されます。
DEBUG=apnfb では、フィードバックに関するメッセージが出力されます。

token
デバイストークン。
長くなってきたので、これについては iOS 側の実装とともにまた次回の記事で。


2012年11月19日月曜日

Node.jsが面白い件② redisを使う

Node.jsが面白い件①の続きです。今回はredisの使い方について書いていきます。redisはオープンソースのインメモリKVSとして有名で、ディスクへの書き込みやトランザクション機能も提供する優れものです。クラスタに関してはまだ安定版が出ていませんが、単体ではとても簡単に利用できます。以下、Macでのインストール手順です。詳しくはredisのページを参照してください。
  1. redisのダウンロードページからダウンロードする。
  2. 展開したディレクトリに移動しmakeを実行する。
  3. 同ディレクトリでmake testを実行する。
node.js側では以下のようにライブラリredis、hiredisをインストールします。例えば作業ディレクトリをRedisSampleとすると、以下のようにします。
 > mkdir RedisSample
 > cd RedisSample
 > npm install hiredis redis

RedisSampleディレクトリにサンプルプログラムapp.jsを作成し、以下の内容を記述します。

//redisの読み込み。
var redis = require('redis')

try {
    // コネクションの作成。defaultで127.0.0.1:6379を使う。
    console.log("-- connection open.");
    var client = redis.createClient();

    // onメソッドによるerrorイベント時の処理。
    client.on("error", function(err) {
        console.log("Error:" + err);
    });

    // password認証。redis.confでrequirepassにredispassを設定。
    console.log("-- auth");
    client.auth("redispass");

    // 全てのkeyを削除。
    console.log("-- flushdb");
    client.flushdb();

    // 文字列のkey-valueとして値を格納。key:key1、value:val1
    client.set("key1", "val1");

    // 格納した値を取得。 key: key1
    client.get("key1", function(err, obj) {
        console.log("get key1: " + obj);
    });

    // リストの保持。key: lkey、value: lval1, lval2
    client.lpush("lkey1", "lval1");
    client.lpush("lkey1", "lval2");

    // 格納したリスト値を取得。key:lkey1
    client.lrange("lkey1", 0, -1, function(err, obj) {
        console.log("get list lkey1: " + obj);
    });

    // ハッシュの保持。key: hkey1、value: hfld1:hval1, hfld2:hval2
    client.hset("hkey1", "hfld1", "hval1");
    client.hset("hkey1", "hfld2", "hval2");

    // 格納したハッシュ値を取得。 key:hkey1
    client.hgetall("hkey1", function(err, obj) {
        console.log("get hash hkey1");
        console.log(obj);
    });


    // トランザクションその1。成功。
    multi = client.multi();
    multi.set("mkey1", "mval1");
    multi.set("mkey2", "mval2");
    multi.exec(function(err, obj) {
        client.get("mkey1", function(err, rep1){
            console.log("get mkey1: "+rep1);
        });
        client.get("mkey2", function(err, rep2) {
            console.log("get mkey2: "+rep2);
        });

        // トランザクションその2。watchの値が更新されて失敗。
        client.set("wkey1", "wval1");
        client.watch("wkey1");
        client.set("wkey1", "wval-upd");
        multi = client.multi();
        multi.set("mkey1", "mval3");
        multi.set("mkey2", "mval4");
        multi.exec(function(err, obj) {
            client.get("mkey1", function(err, rep1){
                console.log("get mkey1: "+rep1);
            });
            client.get("mkey2", function(err, rep2) {
                console.log("get mkey2: "+rep2);
                console.log("-- connection close.");
                client.quit();
            });
        });
    });
} catch(err) {
    console.err(util.inspect(err));
}

結果は以下のようになります。
 > -- connection open.
 > -- auth
 > -- flushdb
 > get key1: val1
 > get list lkey1: lval2,lval1
 > get hash hkey1
 > { hfld1: 'hval1', hfld2: 'hval2' }
 > get mkey1: mval1
 > get mkey2: mval2
 > get mkey1: mval1
 > get mkey2: mval2
 > -- connection close.

2行目で最初にインストールしたredisを読み込みます。6行目でコネクションを作成して、あとはredisのコマンドにならって処理を実行しています。redisはトランザクションをサポートしていますが、51行目のトランザクション①は成功し、63行目のトランザクション②は失敗します。これはwatchを使って楽観的ロックを実現しており、この例の場合はwkey1をwatchしており、watch実行後からexec実行前までにwkey1が変更されているとトランザクションが失敗するため、結果としてはmkey1、mkey2はmval1、mval2のままとなります。
この例の場合、トランザクション①の結果表示でコールバックを使っているので、コールバック内にトランザクション②の処理を記述しています。Node.jsではコールバック処理をよく使うので処理結果の順序を保持したい場合はこのように意識して記述するか、簡素化のためにstepなどのモジュールを使って処理を記述することができます。
redisはNode.jsでは今のところよく使われる組み合わせなのですが、ここで書いたように基本的な使い方はとても簡単です。軽いプロトタイプなら、Node.jsとredisだけですぐに作れてしまいそうですね。