2階層以上のプロトタイプチェーンについて

「prototypeオブジェクトにベースオブジェクトインスタンスを設定したら、多重継承ってどうやるんだ??ベースクラスを設定したらサブクラスのprototype書き換えられないじゃん???」
という考えから抜け出せられず、また継承使わずとも十分実装できることもあり、
prototypeとは深くかかわらないようにしてたのですが、今回ようやくスッキリすることができました。


確認用コード

var BaseHoge = (function(){
    function BaseHoge() {
      this.hoge = "ベースオブジェクト";
    }
  BaseHoge.prototype.getHoge = function(){
      return this.hoge;
  };
  BaseHoge.prototype.setHoge = function(val){
    this.hoge = val;
  };
  return BaseHoge;
})();

var SubFoo = (function(){
  function SubFoo() {
      this.foo = "サブオブジェクト"; 
  }
  SubFoo.prototype = new BaseHoge.prototype.constructor();
  SubFoo.prototype.constructor = SubFoo;
  SubFoo.prototype.getFoo = function(){
      return this.foo;
  };
  
  return SubFoo;
})();

var SubSubBar = (function(){
  function SubSubBar() {
      this.bar = "サブサブオブジェクト"; 
  }
  
  //BaseHogeインスタンスオブジェクトを
  //prototypeプロパティにセットする。下記構成になる。
  //※コードの通り、prototypeオブジェクトの中身が
  //SubFooインスタンスオブジェクトになるだけ。
  //
  //  prototype {
  //    foo : "サブオブジェクト",
  //    constructor : SubFoo,
  //    __proto__ : SubFoo.prototype
  //  }
  //
  SubSubBar.prototype = new SubFoo.prototype.constructor();
  
  //コンストラクタプロパティを時オブジェクトに書き換える
  //これを行わないと、外からオブジェクトタイプを調べたりすることができなくなってしまう。
  //変更後は、下記構成になる。
  //
  //  prototype {
  //    foo : "サブオブジェクト",
  //    constructor : SubSubBar,
  //    __proto__ : SubFoo.prototype
  //  }
  //
  SubSubBar.prototype.constructor = SubSubBar;
  
  //自オブジェクトに持たせる関数の定義
  SubSubBar.prototype.getBar = function(){
      return this.bar;
  };
  
  //最終的にはこんな感じ。
  //constructorが指す関数と、暗黙リンク(__proto__)が
  //指す関数が異なる構成だけど、問題なく動作する。
  //prototypeオブジェクトも他オブジェクトとなんら変わりない普通の
  //オブジェクトで、プロトタイプチェーンに必要なプロパティを持っていれば
  //どんなオブジェクトでも動作する。
  //
  //  prototype {
  //    foo : "サブオブジェクト",
  //    getFoo : function(){ ... },
  //    constructor : SubSubBar,
  //    __proto__ : SubFoo.prototype
  //  }
  //
  return SubSubBar;
})();

console.clear();

//インスタンスオブジェクトの中身はこんな感じ
//{
//  bar : "サブサブオブジェクト",
//  prototype : {
//    foo : "サブオブジェクト"
//    getFoo : function(){ ... },
//    constructor : SubSubBar,
//    __proto__ : SubFoo.prototype
//  },
//  constructor : SubSubBar,
//  __proto__ : SubSubBar.prototype
//}
//
var obj = new SubSubBar();
console.dir(obj);

//obj.__proto__.__proto__.__proto__.setHoge()を実行する。
//実行後のオブジェクトの中身は以下な感じ。
//
//{
//  bar : "サブサブオブジェクト",
//  hoge : "ほげ~",
//  prototype : { ... }
//  constructor : SubSubBar,
//  __proto__ : SubSubBar.prototype
//}
//
obj.setHoge("ほげ~");

//メソッド場所の検索、処理フローはsetHoge()と同様。
console.log("getHoge() : ", obj.getHoge());

Edit fiddle - JSFiddle

参考書籍

開眼!  JavaScript ―言語仕様から学ぶJavaScriptの本質

開眼! JavaScript ―言語仕様から学ぶJavaScriptの本質

これに全部載ってます。

関数デコレーターの動作

処理フロー

  1. 実行時、インタプリタがデコレーター割り付けを見つけると、直ちにそのデコレーター関数を呼び出す。関数の戻り値は関数オブジェクト(呼び出し可能:callable)であること。
  2. デコられた(被デコ)関数(例ではhello/hello2)を呼び出したときの動きとしては以下のようになる。
    1. 手順1の戻り値関数(例ではlambda/inner)を呼び出す。このときの第一パラメーターに被デコ関数オブジェクトを渡す。
    2. 手順2-2の戻り値関数(例ではhello/hello2)を呼び出す。

割り付けが見つかった時点でデコレーター関数が呼び出される。
「被デコ関数呼び出し直前にデコレーターを呼びだしたい!」という場合は
デコレーター割り付けを内包する関数を定義するのがよろしいかと思われる。

コード例

def mydecorator():
	print("this is decorator!!")
	return lambda f : f

def mydecorator2(num):
	def inner(func):
		for i in range(int(num)):
			print("this is innerdeco!!!!")
		return func
	return inner

#パラメーターなしデコレーター
@mydecorator()
def hello(name):
	print("hello, ",name)

#パラメーター付きデコレーター
@mydecorator2(3)
def hello2(name):
	print("hello, ",name)

#デコレーター割り付けを内包する関数
def call_hello(name):
	@mydecorator()
	def hello3(name):
		print("hello, ", name)
	hello3(name)

if __name__ == "__main__":
	hello2("john")
	call_hello("tom")

Ubuntu12.4.1 + Python3 + Bottle + Jinja2 の組み合わせ

少しハマったのでメモ。
Ubuntuのapt-getでインストールできるPython3系は3.2なのですが、
これだとJinja2が動かないようです。

Jinja 2.7 brings experimental support for Python >=3.3.
Introduction — Jinja2 2.8-dev documentation

ソースをビルドしてインストールする必要があります。
次のページを参照しました。
Ubuntu12.04にPython3.3をインストール - Qiita [キータ]

カスタムオブジェクトの定義について

JavaScriptのprototype周りを勉強しながら、過去・現在の自分で書いたコードを見直したところ、
ちょっとまずい構成をしていたことが判明しました。例として以下のコードで考えます。

//ストップウオッチ
var StopWatch = function(){
	var _st = undefined;
	var _ed = undefined;

	var _start = function(){
		_st = new Date();
	}
	var _stop = function(){
		_ed = new Date();
	}
	var _clear = function(){
		_st = undefined;
		_ed = undefined;
	}
	var _getElapsedMilliseconds = function(){
		if(_st === undefined || _st === undefined)
			return -1;
		return _ed.getTime() - _st.getTime();
	}
	
	return {
		start : _start,
		stop : _stop,
		clear : _clear,
		getElapsedMilliseconds : _getElapsedMilliseconds
	}
}

1) まずい点 - new演算子

まずい点というよりは、理解不足による無駄な処理を行っていた点です。
StopWatchコンストラクタ関数の戻り値が存在します。
この時の「return { ... }」 は return new Object();」 と等価です。
ということは、関数を(new関係なく)呼び出すだけでオブジェクト(インスタンス)が生成できてしまうのです。*1
あと、これはコンストラクタ関数じゃないので、createStopWatch()みたいな感じに名称変更も行うことも必要になるでしょう。

//等価
var sw1 = new StopWatch();
var sw2 = StopWatch();

2) まずい点 - オブジェクト(インスタンス)のサイズ

各オブジェクト(インスタンス)が全メンバとメソッドを持っているので、大量にオブジェクト(インスタンス)を生成するとメモリ量が圧迫される場合があります。
プロトタイプ拡張する形式でカスタムオブジェクト定義すれば、オブジェクト(インスタンス)では各自の状態だけを持たせるようにできるため、使用メモリ量が改善が期待できます。

3) まずい点 - オブジェクト(インスタンス)がカスタムオブジェクトでは無い

コンストラクタ関数の戻り値で、new Object()して、そこにプロパティをいろいろ設定したものをインスタンスとして使用しています。
これだとObjectとして認識されていまうようです。(StopWatchオブジェクトであることを期待していました。)

var sw = new StopWatch();
console.log(sw);

★出力結果
f:id:ham007:20131010163216p

4) プロトタイプを意識したオブジェクト

定義例として以下のような記述が最も理解しやすいんじゃないかなと思います。
(ここを参考にしました)

//ストップウオッチ
var StopWatch2 = (function(){

	function StopWatch2(){
		this._st = undefined;
		this._ed = undefined;
	}

	function _start(){
		this._st = new Date();
	}
	
	function _stop(){
		this._ed = new Date();
	}
	
	var _clear = function(){
		this._st = undefined;
		this._ed = undefined;
	}
	var _getElapsedMilliseconds = function(){
		if(this._st === undefined || this._ed === undefined)
			return -1;
		return this._ed.getTime() - this._st.getTime();
	}
	
	StopWatch2.prototype = {
		constructor : StopWatch2,
		start : _start,
		stop : _stop,
		getElapsedMilliseconds : _getElapsedMilliseconds
	}
	
	return StopWatch2;
})();

5) プロトタイプを意識したオブジェクト - メリット

  • オブジェクト(インスタンス)サイズ量が小さくなる。オブジェクト共通で使うものはprototype経由で参照できる。

6) プロトタイプを意識したオブジェクト - デメリット

  • メンバの隠蔽が出来ない。JavaScriptにアクセス修飾の概念が存在しない為。やろうと思うとクロージャを使う必要あり。

7) 定義方法の使い分け

一長一短ありますので、状況に応じて使い分けするのもよいかと思いますが、
常に「プロトタイプを意識したオブジェクト」の定義方法を採用するのでも問題ないかと思います。
メンバの隠蔽の件については、コーディング規約など、運用でカバーできますしね。。

*1:コンストラクタ関数に戻り値が無い場合は、生成したthisに相当するオブジェクトが返されます。

NancyでSessionを扱う

ナンシーはデフォルトではSessionが無効になっています。
有効化するにはBootstrapperの初期化時に以下を呼び出します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Nancy;
using Nancy.Session;

namespace NancySessionSample
{
    public class MyBootstrapper : DefaultNancyBootstrapper
    {
        protected override void ApplicationStartup(Nancy.TinyIoc.TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines)
        {
            base.ApplicationStartup(container, pipelines);
            CookieBasedSessions.Enable(pipelines);
        }
    }
}


使い方は今まで通り。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Nancy;

namespace NancySessionSample
{
    public class MyModule : NancyModule
    {
        public MyModule()
            : base()
        {
            Get["/"] = _ =>
            {
                return "main";
            };

            Get["/aaa/{val}"] = p =>
            {
                string val = p.val;
                Session["aaa"] = val;
                return "update session aaa";
            };

            Get["/bbb/{val}"] = p =>
            {
                string val = p.val;
                Session["bbb"] = val;
                return "update session bbb";
            };

            Get["/dump"] = _ =>
            {
                string aaa = Convert.ToString(Session["aaa"]);
                string bbb = Convert.ToString(Session["bbb"]);
                return string.Format("aaa:{0}, bbb:{1}", aaa,bbb);
            };
        }
    }
}

注意事項

  1. ASP.NETのセッション機構ではなく、ナンシー独自のセッション機構を使用。
  2. セッション情報本体もクッキーで管理している。セッションにセットできるのはNancy.DefaultObjectSerializerでシリアライズ/デシリアライズできるオブジェクト。(BinaryFormatterでシリアライズ可であれば大丈夫)

Nancyでのセキュリティー (CSRF)

ナンシーでのセキュリティ対策として、CSRFについて調査しました。
パッケージは Nancy.Hosting.Aspnet と Nancy.Viewengines.Razor を使いました。


ビュー - 登録フォーム (Views/form.cshtml)

ASP.NET MVCと同様に、チェック対象フォームにトークンを組み込みます。
このHTMLヘルパーはNancy.ViewEngines.Razor.HtmlHelpersクラスで定義されています。

<html>
<head>
    <title>登録フォーム</title>
</head>
<body>
    <h1>個人情報を入力してください</h1>
    <form action="~/" method="post">
        <p>名前:<input type="text" name="Namae" /></p>
        <p>年齢:<input type="text" name="Age" /></p>
        <p>カード番号:<input type="text" name="CardNo" /></p>
        <p><input type="submit" value="確認" /></p>
        @Html.AntiForgeryToken()
    </form>
</body>
</html>

ビュー - 登録完了ページ (Views/thanks.cshtml)

<html>
<head>
    <title>登録しました</title>
</head>
<body>
    <h1>ありがとうございました</h1>
</body>
</html>

CSRFチェックを有効にする1

初期化時にNancy.Security.Csrf.Enable()します。
これでリクエスト毎のAfterPipeline処理でトークン作成が行われます。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Nancy;
using Nancy.Security;

namespace NancyCsrfForm
{
    public class MyBootstrapper : DefaultNancyBootstrapper
    {
        protected override void ApplicationStartup(Nancy.TinyIoc.TinyIoCContainer container, Nancy.Bootstrapper.IPipelines pipelines)
        {
            base.ApplicationStartup(container, pipelines);
            Csrf.Enable(pipelines);            
            pipelines.OnError.AddItemToEndOfPipeline((ctx, exc) =>
            {
                return exc.Message;
            });
        }
    }
}

CSRFチェックを有効にする2

チェックを行いたいルート処理内で、ValidateCsrfToken()します。
チェック不合格で、CsrfValidationExceptionがthrowされます。

using System.Web;
using Nancy;
using Nancy.ModelBinding;
using Nancy.Extensions;
using Nancy.Security;
using System.IO;

namespace NancyCsrfForm
{
    public class MyModule : NancyModule
    {
        public MyModule()
            : base()
        {

            Get["/"] = _ =>
            {
                return View["form.cshtml"].WithContentType("text/html; charset=UTF-8");
            };

            Post["/"] = _ =>
            {
                this.ValidateCsrfToken();
                var model = this.Bind<FormModel>();
                ViewBag.model = model;
                //todo: 入力チェック
                return Response.AsRedirect("~/regist");
            };

            Get["/regist"] = _ =>
            {
                //todo: 登録処理
                return View["thanks.cshtml"].WithContentType("text/html; charset=UTF-8");
            };
        }
    }

    public class FormModel
    {
        public string Namae { get; set; }
        public int Age { get; set; }
        public string CardNo { get; set; }
        public FormModel() { }
    }
}

動作確認用ページ1

実際の悪さをするページ

<html>
<head><title>CSRFページ</title></head>
<body onload="document.attackform.submit()">
<form name="attackform" action="<登録フォームURL>" method="post">
	<input type="text" name="Namae" value="攻撃太郎" />
	<input type="text" name="Age" value="99" />
	<input type="text" name="CardNo" value="9999-9999-9999-9999" />
	<input type="submit" value="確認" />
</form>
</body>
</html>

動作確認用ページ2

本当は1*1なiframeを使ったりするのでしょうが、今回はわかるように大きくしています。

<html>
<head><title>ダミーページ</title></head>
<h1>ようこそいらっしゃいました!!</h1>
<body>
<iframe src="./attackform.html" width="500px" height="500px" />
</body>
</html>

動作結果

エラー時は、チェック不合格種類を表示しています。
フォーム側のトークンが取得できなかったので、"TokenMissing"が出力されています。
f:id:ham007:20130731185828p:plain

Nancyメモ

ASP.NETアプリケーションとしてホストした場合の処理フローメモ

Nancy.Hosting.Aspnet.NancyHttpRequestHandler

  • 静的オブジェクト。
  • コンストラクタ内で呼び出すGetBootstrapper()で、
    1. web.configにnancyFxセクションが存在する場合は、そこからオブジェクトを生成する。 ⇒ Nancy-master\src\Nancy.Demo.Hosting.Aspnet\Web.config 参照
    2. NancyBootstrapperLocator経由でBootstrapperオブジェクトを取得する。

Nancy.Bootstrapper.NancyBootstrapperLocator

  • Bootstrapperオブジェクトを生成する。
  • Bootstrapperオブジェクトは静的オブジェクトとして保持している。
  • NancyHttpRequestHandlerの静的コンストラクタで呼び出されている -> 一回だけ呼び出される。
  • DefaultNancyBootstrapper以外でINancyBootstrapperを実装したクラスが存在する場合は、それを使用する。(存在しなければDefaultNancyBootstrapper)

Nancy.DefaultNancyBootstrapper

  • 継承関係: INancyBootstrapper,INancyModuleCatalog -> NancyBootstrapperBase<T> -> NancyBootstrapperWithRequestContainerBase<T> -> DefaultNancyBootstrapper ※<T>にはTinyIoCContainerを指定
  • Initialiseメソッド:
    • NancyHttpRequestHandlerの静的コンストラクタで呼び出されている -> 一回だけ呼び出される。
    • INancyBootstrapperで定義。
    • Bootstrapper生成直後に呼び出しされる。 -> Nancy.Hosting.Aspnet.NancyHttpRequestHandlerのコンストラクタ
    • やってること
      1. アプリケーションコンテナオブジェクトの取得
      2. 対応型情報の取得1? -> DIするときの割り付け情報1?
      3. Conventionsの設定?
      4. 対応型情報の取得2? -> DIするときの割り付け情報2?
      5. 初期化 - GetApplicationRegistrationTasks()使用 -> なにやってるかはよくわからん。
      6. 初期化 - GetApplicationStartupTasks()使用 -> おそらくアプリケーション初期化処理。
      7. 初期化 - ApplicationStartup()呼び出し -> サブクラスでオーバーライドするやつ ※6と7の初期化の違いとして、DIできるかできないかの違いじゃないかなと思う。
      8. FavIconあり時に、アプリケーションパイプライン処理を追加する。
      9. 診断オブジェクトの初期化? -> IDiagnostics実装クラスの初期化処理。なにやってるかはよくわからん。
  • ApplicationStartupメソッド:
    • NancyBootstrapperBaseクラスで定義&ここでは空実装。
    • NancyBootstrapperBase.Initialise()で呼び出ししている。
  • RequestStartupメソッド:
    • NancyBootstrapperBaseクラスで定義
    • 呼び出し階層は GetEngine() -> InitializeRequestPipelines() -> RequestStartup() ※すべてブートストラッパークラス内のメソッド
  • InitializeRequestPipelinesメソッド:
    • パイプラインオブジェクトはは初期化時のものをベースに、リクエスト毎にクローンコピーしたものが使用される。
    • 後述のRequestStartupで設定したパイプラインはそのリクエスト処理時のみ有効となる。
    • また、コンテナもパイプラインと同様。
  • ConfigureRequestContainerメソッド:
    • NancyBootstrapperWithRequestContainerBaseクラスで定義&ここでは空実装。
    • NancyBootstrapperWithRequestContainerBase.GetConfiguredRequestContainer()で呼び出している。-> GetConfiguredRequestContainer()呼び出しは、 GetAllModules(), GetModule(), InitializeRequestPipelines() で行われる。
    • オーバーライドして何を実装することを想定されているのかが、よくわからない。
  • ConfigureApplicationContainerメソッド:
    • NancyBootstrapperBaseクラスで定義&ここでは空実装。
    • NancyBootstrapperBase.Initialise()で呼び出ししている。
    • オーバーライドして、INancyModuleCatalogとIRouteResolverの割り付けを行うことを想定しているようだ。

オーバーライドできるメソッドの呼び出され順は以下の通り

[初回アクセス時のみ呼び出し]

  1. ConfigureApplicationContainer(TContainer existingContainer)
  2. ConfigureConventions(NancyConventions nancyConventions)
  3. ApplicationStartup(TContainer container, IPipelines pipelines)

[リクエスト毎呼び出し]

  1. ConfigureRequestContainer(TContainer container, NancyContext context)
    • RequestStartup()と同様に、InitializeRequestPipelines()内で呼び出されている処理なので、リクエスト毎に呼ばれる。
  2. RequestStartup(TContainer container, IPipelines pipelines, NancyContext context)
    • NancyBootstrapperBase.GetEngine()で行っているRequestPipelinesFactoryの割り付け処理。ここでInitializeRequestPipelines()への参照(という表現で妥当かはわからんけど。)を渡している。RequestPipelinesFactoryはリクエスト毎に呼び出されている -> Nancy.NancyEngineのHandleRequestInternal()での this.RequestPipelinesFactory.Invoke(context);

NancyEngine

  • リクエスト発生時にあらかじめ生成してあるNancyEngineオブジェクトを使ってリクエストをさばく -> NancyHttpRequestHandler.BeginProcessRequest()内
  • NancyEngineはIoCコンテナによってコンストラクタパラメーターが生成されているっぽい。 -> この挙動を設定するには、BootstrapperのConfigureApplicationContainer()をオーバーライドし、コンテナの割り付け情報を変更する。
  • HandleRequestメソッド:
    1. NancyContext生成
    2. プレリクエスト処理 -> 詳細はよくわからん
    3. 静的コンテンツ処理 -> 静的コンテンツへのアクセス時はここで処理して終了
    4. リクエストPipelines生成
    5. リクエストライフサイクル処理*1 -> InvokeRequestLifeCycle()
      1. BeforePipeLineによる処理 -> PipelinesのBeforeを使用
      2. Dispatcherによる処理 -> リクエストのルートに対する処理を行っていると思われる。
      3. AfterPipeLineによる処理 -> PipelinesのAfterを使用
      • 各処理でエラー発生時には、PipelinesのOnErrorを使用したエラー処理が行われる。

ルート解決、ルート処理、レスポンス生成

  • IRequestDispatcherのデフォルト実装であるDefaultRequestDispatcherによって行われる。
  • クラスメンバ構成から想像するに、ルート解決 -> ルート処理 -> レスポンス生成 な流れでリクエスト処理を行っていると想像。




これ以降は次回へつづく?

*1:ここではタスクを組み立てるだけで、実際の処理は行っていない?