シングルトンパターン

Javaだとこんな感じ…

//1. 拡張継承により, 複数のインスタンスが生成されてしまうのを防ぐ
final class Singleton {
    //2. クラス定義時にインスタンスを生成して, かつ, クラス(static) プロパティとして保持することで, マルチスレッドにおいてもインスタンスが1つしかないことを保証する
    private static Singleton instance = new Singleton();

    //3. 外部からコンストラクタを呼べないように, private
    private Singleton() {
    }

    //4. ただ1つのインスタンスを取得するクラス (static) メソッド (外部から直接インスタンスにはアクセスできないので)
    public static Singleton getInstance() {
        return Singleton.instance;
    }
}

expressでセッション管理の注意点

Node.jsに限らず, セッションを扱うWebアプリでは, セッションハイジャックなどの対策のために, ログイン後などにセッションIDを再生成する.

例えば, PHPであれば,

session_start();

$oldId = $_COOKIE['PHPSESSID'];

//ログイン成功 ...

session_regenerate_id(true);

$newId = $_COOKIE['PHPSESSID'];

//$oldId !== $newId のはず

Node.jsのフレームワークexpressでも同じことが可能.

ルーティングされているコントローラの引数に渡される, Http.ServerRequestオブジェクトのsessionプロパティのregenerateメソッドを利用する.

var sessionRegerateId = function(req, res) {
    req.session.regenerate(function(error) {
        if (error) {
            console.log(error);
            return;
        }

        //セッションID再生成後の処理
    });
};

ただし, PHPのsession_regenerate_idと違って, IDだけを再生成して, セッション変数に格納した値を保持しているという実装にはなっていない感じで, regenerateメソッドによって, 新しいsessionオブジェクトを生成するという実装になっているっぽいので, regenerateメソッドを実行すると, セッションに格納していた値は新しいセッションオブジェクトには引き継がれなくなる.

したがって, 以下のようにセッションオブジェクトを別変数に格納して, 参照を残していると, 古いセッションオブジェクトを参照してしまうことになる.

var sessionRegerateId = function(req, res) {
    var session = req.session;
    session.regenerate(function(error) {
        if (error) {
            console.log(error);
            return;
        }

        //古いセッションオブジェクトを参照したまま !!
        session['is_auth'] = true;
    });
};

まとめると, 以下のようにすればとりあえずOK (?) と思います.

var sessionRegerateId = function(req, res) {
    var sessions = {}

    //セッションに格納している値を取得しておく
    for (var key in req.session) {
        sessions[key] = req.session[key];
    }
    req.session.regenerate(function(error) {
        if (error) {
            console.log(error);
            return;
        }

       //セッションIDが再生成, すなわち, 新しいsessionオブジェクトが生成されたので, セッションに格納していた値を復元する
        for (var key in sessions) {
            req.session[key] = sessions[key];
        }
        //セッションID再生成後の処理
    });
};

自分はこれにかなりはまってしまったので, 大変でした…
ぜひ気をつけてください.

Jade

Jadeテンプレートに渡す変数は勝手にエスケープ (PHPでいうところの, htmlspecialchars) が適用される. これで, XSS対策のし忘れはないけれども, HTML文字列を出力したいって場合のためのオプションとかないのかな … ? と調べてみた限りではなさそう.

なので, その場合には元の文字列に戻す処理を自前で実装する…

example.

JSONデータをpostMessageする子iframeのJadeテンプレート

doctype html
html(lang='ja')
    head
    title postMessage
    meta(charset='UTF-8')
    script(type='text/javascript') window.onload = function()   {window.parent.postMessage('#{data}', '*');};
    body

postMessageを受ける親フレーム

window.onmessage = function(event){
    //エスケープされたままJSON.parse()するとエラーなので…
    //もとに戻す
    var json = event.data.replace(/"/g, '"')
                                           .replace(/&lt;/g, '<')
                                           .replace(/&gt;/g, '>')
                                           .replace(/&amp;/g, '&');
    var datas = JSON.parse(json);
};

まあ, Jadeはいわゆるフェールセーフな設計になっているということでしょう (危険な場合でも安全な方向に働くような設計).

 

Node.js express

expressのresponseオブジェクトのrenderメソッドにコールバック関数を指定すると, ブラウザの出力やAjaxのレスポンスとして返さずに, テンプレート出力したHTMLの文字列が格納される.

function(req, res){
    //Not output
    res.render('template', {title : 'sample'}, function(error, html) {
        /* do something ...*/
}
}

これを利用すれば, window.postMessageを利用したクロスオリジン対応の (擬似的な) Ajaxにも利用できる.

function(req, res){
    //まずは, レスポンスとして返したい, HTMLを生成
    res.render('template', {title : 'sample'}, function(error, html) {
        //Output to iframe -> postMessage -> 'onmessage' event in parent frame
        res('iframe', {data : html});
}
}

expressでセッション管理

セッションストアとして, MongoStoreを使うとして…

まずは, インストール

$ sudo npm install -g connect-mongo
var express        = require('express');
var MongoStore = require('connect-mongo')(express);

var app = express();

app.configure(function(){
    app.use(express.cookieParser());  //Cookieを使うので必要
    app.use(express.session({
        /*オプションの詳細はこちらを参照*/
        key      : 'session',  //Session IDのキー
        secret : 'secret',     //署名つきCookieのパスフレーズ
        store   : new MongoStore({
             /*その他のオプションはこちらを参照*/
             db     : 'database-session'  //データベース名
        }),
        cookie : {
            /*その他のオプションはこちらを参照*/
            maxAge : new Date(Date.now() + 3600000)  //1 hours  (Dateインスタンスを指定するので注意)
        }
 }));
});

これで, セッション管理をする準備は完了です.

(connect-mongodbを使う場合はまた異なるので, くれぐれも注意してください…)

セッションストアには, MongoStore以外にも, KVSなら, redisやmemcached, RDBなら, MySQLやPostgreなどが使えます.

JavaScript copy / cut, pasteイベント

使いどころは少ないかもしれませんが, JavaScriptでWebページのコンテンツが, コピー / 切り取り, ペーストされたときのイベントを検知することが可能です.

//コピー
document.addEventListener('copy', function(event) {
}, false);

//切り取り
document.addEventListener('cut', function(event) {
}, false);

//ペースト
document.addEventListener('paste', function(event) {
}, false);

また, それぞれ直前に発生する, beforecopy, beforecut, beforepasteイベントもあります.

これらすべてのイベントオブジェクトには, clipboardDataプロパティが設定されます. clipboardDataは, HTML5 APIのDrag & Dropのdropイベントオブジェクトで定義されているData Transfer APIと同じです. ファイルがcopy / cut, pasteされたときにitemsやfilesにFileオブジェクトが設定されるようになっているみたいです.

参考

Node.js + MongoDBでサウンドパッチシステム 3

セッション管理をexpressフレームワークで実装しようという方針にしたので, 今日はスケルトンコードの作成まで.

その備忘録を書いときます.

  • npm ( Node Package Manager) はインストール済とします
  • root権限が必要なときは, sudoをつけてください
  • グローバルインストールの場合, gオブションをつけてください

1. expressをインストール

$ (sudo) npm (-g) install express

2. テンプレートエンジンをインストール

$ (sudo) npm (-g) install jade

テンプレートエンジンはいくつかありますが, 今回はJadeを利用することにしました.

3. アプリ作成のディレクトリを作成して移動

$ mkdir express && cd express

4. スケルトンコードの生成 (package.jsonでカスタマイズできます)

$ express

スケルトンコード (app.js) は以下のように生成されます.

/**
 * Module dependencies.
 */

var express = require('express');
var routes  = require('./routes');
var user    = require('./routes/user');
var http    = require('http');
var path    = require('path');

var app = express();

// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));

// development only
if ('development' == app.get('env')) {
  app.use(express.errorHandler());
}

app.get('/', routes.index);
app.get('/users', user.list);

http.createServer(app).listen(app.get('port'), function(){
  console.log('Express server listening on port ' + app.get('port'));
});

5. リクエストを待つ

$ node app.js

デフォルトでは, ポート番号3000で待つので, http://localhost:3000/にアクセスすると,

Express

Welcome to Express

と表示されます.