Moai+Easter 上級マニュアル

Moai Manual Advanced Manual Annoucement FAQ
ご案内 Moaiエンジン CustomBoyエンジン HowToコンパイル Moai CGI Developers
エンジンの詳細 フィルターの詳細 ローカルプロキシとしての使い方 プラグイン開発のためのヒント

はじめに

この記事ではMoaiのplugin開発について、そのヒントを説明します.


目次



開発言語について

Moaiでは開発言語としてCを使います.
そして基本的に実行効率より確実性を最優先します.
C++を使っていない理由も簡単に述べておきます.
  • 多くの場合Cの方が高速である.
    一般的によく知られていることだと思います.
    ただし一部例外もあり、最適化におけるインライン展開によって C++ の方が高速になるケースというのもあります.
    このような例として例えば qsort などが知られています.
    とはいえ C 言語においても最近では殆どのベンダーで最適化におけるインライン展開はサポートされてはいるため
    この点に関しては実質的には両者にさほど差異はないと思われます.

  • C++ はデバッガでも追跡困難なバグを誘発しやすい.
    一般に C よりも C++ の方が記述は簡潔になるケースが多いです.
    つまり C だと多くの複雑な処理を行う形になるものを、C++ならそれを見かけ上隠蔽して単純に書けます.
    一見するとこれはメリットしかないように思えるかもしれませんが、デバッグの際にはむしろ逆です.

    現在の OS およびデバッガは( 一部 C++ を解釈できる部分もあるが )基本的には C の文を基本単位とします.
    従ってもし抽象度の高い C++ 記述にバグを入れると、デバッガによるシーケンシャルな追跡が困難になります.
    例えばコンストラクタやデストラクタの発動の順序のタイミングなどは複雑で、
    場合によってはその発動を捕捉することすら困難な状況が起こりえます.

    関数やマクロを適切に使えば C でも記述を簡潔化することはできます.
    さらに大規模になればこれまた適切にライブラリ化すればよいでしょう.
    本プロジェクトでも基本的にはその戦略をとります.

  • C++ はコンパイラ依存の差異や癖などに起因する問題を誘発しやすい.
    C/C++は様々なコンパイラメーカーが様々なコンパイラを提供する言語です.
    それゆえ、このような問題の発生を避けて通れません.
    しかし C の場合、言語仕様が小さいためか経験上この問題はあまり発生しないように思われます.
    一方、C++ の場合は逆に仕様が複雑過ぎるゆえか、我々が様々なコンパイラを実際に試した経験上、
    このような問題が頻繁に発生するように思われます.
というわけで我々はどうでもいい開発にはC++を使うこともありますがそうではない場合はC++は基本的に使いません.
しかし、基本ライブラリであるlibZnkはC/C++いずれからでも使用可能です.

その他メジャーなスクリプト言語(rubyやpython、goなど)は大抵の場合良い選択です.
言語処理系を提供者は単一であるため、上述した「様々なコンパイラメーカーの差異」に起因する問題も起こりえません.

しかしこのような言語は長い目で見た場合雲行きが怪しくなってきます.
インタプリタや付属ライブラリのバージョンアップなどで、今まで問題なく動作していたものが突然エラーになるのはよくある話です.
(一方、逆にC/C++では、大量にあるレガシーな資産への配慮があるためか、この種の問題があまり発生しません. )

また万一原因不明のバグが発生した場合、それがその言語に存在する未知のバグに起因するものなのか
それとも自作したコード内に起因するものなのかが判断し難いという問題があります.
(一方、逆にC/C++では、極めてプリミティブというかあまり余計なことをしない言語仕様であるためか、この種の問題があまり発生しません. )

また、バグとまではいかないまでも、アンドキュメントなある種の癖に由来した挙動が発生した場合、
言語処理系に付属した膨大なライブラリから原因を分析する必要が生じるかもしれません.
こんなのやってらんないので現時点の Moai では不採用としました.

目次に戻る

改造に挑戦される方へ

あなたが開発したプラグインは公開してもらっても勿論構いません.
我々がPolipoを参考に新しくMoaiを開発したように、今度はあなたがMoaiのpluginを作られてみるのもまた一興ではないでしょうか?
MoaiのライセンスはNYSLであり、某ライセンスのようなソースコード開示の義務はありません.

目次に戻る

ライブラリの依存関係について

まずMoaiシステム全体において、各ツールおよびライブラリの依存関係を以下に簡単に示しておきます.
  • libZnk
    依存ライブラリはなし(zlibを使用しているがソース内に同梱されてある).

  • libRano
    libZnk
    libresslを動的モジュールとしてロード(万一libresslが存在しなければそれをスルーする)

  • moai
    libZnk
    libRano

  • easter
    libZnk
    libRano

  • custom_boy
    libZnk
    libRano

  • proxy_finder
    libZnk
    libRano

  • birdman
    libZnk
    libRano

  • plugin_futaba
    libZnk

  • plugin_5ch
    libZnk

目次に戻る

moai本体のソースコードの構成について

このセクションではMoaiエンジンのソースを分析したい方のために、そのソースを構成するファイルの概要を示します.


Moai_connection
MoaiConnection型とその寿命を管理する.
MoaiConnectionとは、ProxyにおけるI側とO側のソケットをペアに持ち、その他それに関係する情報を保持する構造体である.
ここで I側とはHTTPリクエストの発行元であり、O側とはそのリクエストをそのまま(あるいは加工して)送信する先である.
またO側からそのリクエストに対する応答(レスポンス)が返され、I側へそれをそのまま(あるいは加工して)送信する形になる.
TCPコネクション処理に関する管理やコールバックなどもここで保持する.



Moai_info
MoaiInfo型とその寿命を管理する.
MoaiInfoとはHTTPに関する必要な情報を保持する型である.
また、MoaiInfoIDという64bit整数により、このMoaiInfo型本体へアクセスすることができるような機構を備える(一種の連想配列である).



Moai_io_base
ZnkSocketのクローズやそれらに関する報告用メッセージの作成補助、textデータのsend、パターンやサイズを加味したrecvを行う.



Moai_post
MoaiにおけるPOST処理一般に関する処理.
POST変数やCookie変数のフィルタリングやpost_confirm用メッセージの構築などを行う.



Moai_context
Moai_server内におけるscanHttpFirstから始まる一連の処理に使われる変数群を格納したもの.



Moai_http
HTTPに関する下請け処理などを纏めてある.



Moai_server
サーバ処理のコア部分であり、selectメインループが存在する.
main関数から呼ばれたMoaiServer_mainは、config.myfやmoduleのロードなどの初期化処理を終えた後、ポート8124をリッスンし、selectメインループに入る.
また同時にXhrDMZとしてポート8125をリッスンする.

このループ内では MoaiFdSet_select において、データの入力発生(read)や接続の成功(write)、タイムアウト(timeout)といったイベントの発生を待ち、これらのうちのいずれかが発生した場合に制御を返す.
特にデータ入力発生(read)が発生した場合においては、MoaiFdSet_process関数を介してrecv_and_sendがコールバックとして呼ばれ、入力されたデータの状態をまずscanHttpFirstで解析する.

この解析の結果、それがHTTPリクエストの開始であるのか、あるいはその応答の開始であるのか、あるいはそれらの途中の部分であるのかが明らかになる.
またこの解析では中継先の取得も行われ、その結果によってWebサーバとして動作するのかローカルプロキシとして動作するのかの判断も行われる.



Moai_server_info
サーバとしての基本情報を保持する.



Moai_web_server
Webサーバ処理のコア部分である.
HTMLなどのファイルの中身をクライアントに送信したり、Moai CGIのエントリ処理などを行う.
XhrDMZオリジンへの振り分け等も行う.



Moai_cgi Moai_cgi_manager
Moai CGIコアエンジンである.
CGI用の子プロセスを立ち上げ、標準入力と標準出力に関するパイプを作成する.
このパイプにおいてクライアントからのSocket受信を子プロセスの標準入力へ繋ぎ替える.
また一方、子プロセスの標準出力をクライアントへのSocket送信へと繋ぎ替える.
この二つを同時に制御することにより、CGIの動作を実現する.



Moai_fdset
selectイベントに関するsocketの登録、観察(Observe)用.



Rano_module
これはlibRanoに存在する.
filterファイル(myf)やplugin(dll/so)などの管理やロード、関数の実行などを行う.



main
mainが存在するエントリー部である.


目次に戻る

libZnkについて

基本ライブラリ libZnk はこのプロジェクトに存在する全てのツールの開発において使われています.
よってこれの解説を抜きにするわけにはいかないのですが、このライブラリの仕様を詳しく解説していたらそれこそ紙面と時間がいくらあっても足らないので、 とりあえずこちら猛烈に手抜きな概要を書いておきます.

あとはlibZnkのソースコードやコメントなどを適宜参照して下さい.
時間があればそのうち詳細なドキュメントも用意したいところですが、いつになるかは未定です.

目次に戻る

プラグインインターフェース

Moaiプラグインでは以下のダイナミックロード可能なグローバル関数をC言語により実装します.
このバージョンにおいてサポートされる関数は以下です.


bool initiate( ZnkMyf ftr_send, const char* parent_proxy, ZnkStr result_msg );
Moai Serverが起動した際の初期化処理の一環としてこの関数が呼び出される.
フィルター処理に特別な初期化が必要な場合などに利用できる.

@param ftr_send:
初期化対象となるフィルタへの参照である.
あなたのpluginはこの関数内でこの値を参照および変更してかまわない.
これにより、ヘッダ、ポスト変数、Cookie変数の特別な初期化(仮想化やランダマイズ化)が可能となるだろう.

@param parent_proxy:
Moaiが現在使用中の外部プロキシがhostname:portの形式で設定されている.
(外部プロキシを使用していない場合はこの値が空値であるかまたはNONEであるかまたは:0が指定されている)
あなたのpluginはこの関数内でこの値の参照は可能だが変更することはできない.

@param result_msg:
ここにはこの関数の処理をおこなった結果のメッセージを格納しなければならない.
これは処理が成功した場合はそれをリポートメッセージであり、エラーが発生した場合はそれを示すエラーメッセージとなる.
このメッセージはMoaiエンジンがエラー報告用として使用する場合がある.



bool on_post( ZnkMyf ftr_send, ZnkVarAry hdr_vars, ZnkVarAry post_vars );
target固有のPOST直前時の処理を行う.
POST直前に毎回呼び出される.

@param ftr_send:
処理対象となるフィルタへの参照である.
あなたのpluginはこの関数内でこの値を参照および変更してかまわない.
これにより、ヘッダ、ポスト変数、Cookie変数のさらなる柔軟なフィルタリングが可能となるだろう.

@param hdr_vars:
処理対象となるリクエスト HTTP Headerの参照である.
あなたのpluginはこの関数内でこの値を参照および変更してかまわない.

@param post_vars:
処理対象となるPOST変数の参照である.
あなたのpluginはこの関数内でこの値を参照および変更してかまわない.



bool on_response( ZnkMyf ftr_send, ZnkVarAry hdr_vars, ZnkStr text, const char* req_urp );
target固有のレスポンスを受け取った場合における処理を行う.
レスポンスを受け取った場合に毎回呼び出される.
text(これはHTML,CSS,Javascriptなどであったりする)を加工することが主な目的となるだろう.

@param ftr_send:
処理対象となるフィルタへの参照である.
あなたのpluginはこの関数内でこの値を参照および変更してかまわない.

@param hdr_vars:
処理対象となるレスポンスHTTP ヘッダへの参照である.
あなたのpluginはこの関数内でこの値を参照および変更してかまわない.
@param text:
処理対象となるレスポンステキストデータへの参照である.
あなたのpluginはこの関数内でこの値を参照および変更してかまわない.
@param req_urp:
処理対象となるURIのpath部分(URIにおけるオーソリティより後ろの部分)が渡される.
あなたのpluginはこの関数内でこの値の参照は可能だが変更することはできない.


目次に戻る

プラグインインターフェースで登場するlibZnk

plugin開発において出現するlibZnk特有の型に関して、以下に極一部を紹介します.
(libZnk Ver2.2に準拠するものとします).


ZnkMyf
libZnkが提供する汎用の設定用ファイルフォーマットmyfのデータを格納するための構造体である.
全フィールドは隠蔽されており、それらに対する操作はすべて関数を通じて行う.
インターフェースはZnk_myf.h によって提供され、Znk_myf.cがその実装部である.



ZnkStr
libZnkが提供する動的拡張可能な汎用文字列型を実現するための構造体である.
全フィールドは隠蔽されており、それらに対する操作はすべて関数を通じて行う.
インターフェースはZnk_str.h によって提供され、Znk_str.cがその実装部である.
(実装にあたってはZnkBfr(Znk_bfr.h)というさらに基本的な汎用バイト列型を利用している)



ZnkPrim
以下を格納可能なコンテナというか汎用型である.
  • 整数
  • 実数
  • ポインタ
  • バイト列データZnkBfr
  • ZnkBfrの配列
  • 文字列ZnkStr
  • ZnkStrの配列
  • ZnkPrimそのものの配列
フィールドは公開されているが、異コンパイラ間でのポータビリティに配慮すべくアラインメントに関して可能な限りの配慮がなされている.
以下のような形で使用する.
ZnkPrim_instance( prim );
ZnkPrimType type;
ZnkPrim_compose( &prim, ZnkPrim_e_Real ); /* 初期化 */

type = ZnkPrim_type( &prim ); /* 内部にどのタイプの値が入っているかを取得 */
if( type == ZnkPrim_e_Real ){
  prim.r64_ = 0.123; /* 内部が実数型なら実数をセットするなど */
}

ZnkPrim_dispose( &prim ); /* 破棄 */
インターフェースはZnk_prim.h によって提供される.



ZnkVar
ZnkPrimに加え、nameとfilenameをそのフィールドに持つ.
いわゆる「変数」のような構造を実現した型である. すなわちnameが変数名であり、ZnkPrimがその値となる.
主な関数を一部紹介しよう. これらのインターフェースはZnk_var.h によって提供され、Znk_var.cがその実装部である.
  • ZnkVar_compose
    初期化を行う.

  • ZnkVar_dispose
    破棄を行う.

  • const char* ZnkVar_name_cstr( const ZnkVar var )
    変数varの名前をC文字列として取得する.

  • ZnkVar_set_val_Str( ZnkVar var, const char* data, size_t data_size )
    varを文字列型変数として文字列dataで値をセットする.

  • const char* ZnkVar_cstr( ZnkVar var )
    var が文字列型変数の場合に限り使用できる簡易関数で、その値をC文字列として取得できる.



ZnkVarAry
ZnkVarの動的拡張可能な配列である.
HTTPヘッダ全体、POST変数群、Cooking変数群などは要するにすべて(値が文字列型である)変数の配列であって、いずれもこの型で実現できる.
インターフェースはZnk_varp_ary.h によって提供され、Znk_varp_ary.cがその実装部である.
実装にあたってはZnkObjAry(Znk_obj_ary.h)というさらに基本的なポインタ型汎用配列を利用している.


目次に戻る

This article was written by:
none image

Mr.Moai

@znk project

none image

Zenkaku

@znk project