Moai+Easter 上級マニュアル

トップページ   初級マニュアル   上級マニュアル   アナウンスメント   FAQ  

ご案内   Moaiエンジン   CustomBoyエンジン   HowToコンパイル   Moai CGI Developers  

C言語でCGIその1   C言語でCGIその2   mkfgenリファレンス  

はじめに

この記事ではMoai CGI上で動くWebアプリケーションの開発方法について述べます.

Moai CGI では基本的にApache上などで動く通常のCGIを動作させることもできます.
MoaiのWebサーバとしての機能はApacheなどと比べ機能が限定されますが、その一方で設定等が簡易になっています.
そして何よりMoaiフィルタリング機能との協調動作が容易に行える点がメリットとして挙げられます.
(Easterはまさにそれを実現したツールとなります.
しかしこの解説講座ではそこまでの説明はしませんのでご了承ください)

所詮はCGIスクリプトであるため、開発はそれほど難しくありません.
勿論使用する言語はCのみならず、あなたの得意なスクリプト言語でもOK.
必要な知識は使用する言語における標準出力をする方法(Cならprintfなど)、基本的なHTMLやJavascriptを扱う程度で事足りるでしょう.
(凝ったものを作ろうとすればそれなりに大変ではありましょうが…)


目次



Moai CGI 開発入門

これはCGIスクリプトなんて作ったことがない方向けのチュートリアルとなります.
またプログラムなんて少々かじった程度である方でも大丈夫と思います(多分).

このチュートリアルではC言語(一部例外もあります)を使用し、ひたすらそのサンプルを眺めながら
基本をミッチリ解説していくというまさにクソマゾい王道のスタイルになります.

世の中他に便利な言語が腐るほどある中、なぜクソマゾいC言語かというと、
  1. ネット上には C ベースの各種基本ライブラリが大量に溢れかえっており、必要ならそれを利用するのも(他の言語からより)容易い.
  2. C++よりもCの方が動的リンクライブラリとの相性がよい.
  3. 出来上がるバイナリは(動的リンクライブラリを除けば)単独で実行できる(別途インタプリタを必要としない).
  4. 記述されたこと以上の余計なことをあまりしない.
  5. Moai(正確にはlibRano)が提供するAPIがC言語で用意されているため、これらの機能と協調動作するのに親和性が良い.
などの点が挙げられます.
特に4については、CGIが内部でどのように呼び出されどのような仕組みでサーバとデータのやり取りが行われるかを
なるべく包み隠さず理解するのに最適で、まさに処理の透明性を謳うMoaiのポリシーに合致するのではないでしょうか?

やっていることはAPIの呼び出しと標準入出力をズラズラと並べる程度なので、
C言語以外の言語を使うプログラマの方でもさほど差し支えない内容と思います.

尚、以下で紹介しているサンプルコードのうち、私Mr.MoaiがAuthorとなっているもののライセンスはNYSLとします.
それ以外のものについては気になるようであればオリジナル配布元にてご確認ください(多分たいした制限はないと思います).

目次に戻る

Hello World!

まずは Moai CGI で単純にメッセージを出力してみます.




説明
上のリンクを開くといきなり次のような画面が表示されるかと思いますが、
これはMoai CGIのセキュリティー機構が働いたためです.
とりあえず気にせずに画面の指示に従って「Please retry this」を押してください.
(以降のサンプルではすべてこの画面が最初に表示されます)



参考

最初に開いたURLは http://localhost:8124/cgis/cgi_developers/protected/hello.cgi でした.
しかし「Please retry this」ボタンを押した後では、URLが何か違っていますね?
http://localhost:8124 と /cgis の間に、/authentic/16桁の16進数列というパターンが挿入された形となっています.
この16桁の16進数列の部分をMoai認証キー(Moai_AuthenticKey)と呼びます.

XhrDMZに隔離されたjavascriptは、このMoai認証キーの値を知ることができません.
これを利用して、あまり安全ではないよくわからないjavascriptはXhrDMZへ置き、
一方、安全であることがわかっていて重要な処理を行うCGIなどをMoai WebServer上で
それぞれ分離して実行させれば互いに干渉する範囲を小さくでき、セキュリティはより強固なものになります.
これはMoai CGI独自の機能の一つです.
XhrDMZについて興味ある方はこちらを参照してください.

悪意ある外部スクリプトからこのCGIが自動実行されるのを防止するなどの効果が期待できるため、
私は Moai CGI エンジンを開発するにあたって、この protected に置かれたCGIに関しては、
最初にこのような画面を表示し、「Please retry this」ボタンでMoai_AuthenticKeyを付加する仕様にしました.

さて、それでは本題に入りましょう.
CGIスクリプトでは、標準出力を行うとそれがそのままHTMLとしてブラウザに渡される仕組みとなっています.
そしてそのようなHTMLの出力に先立って、C言語ではまず以下のようなHTTPヘッダの出力をしておく必要があります.
HTTPヘッダの部分の改行コードは\r\nとしなければなりませんので注意しましょう.
ソースコード内でこれに該当する部分を以下に示します.

/* Content-Typeの指定は必須です. */
printf( "Content-Type: text/html; charset=Shift_JIS\r\n" );
/* ブラウザにキャッシュさせたくない場合は以下の2行を加えます. */
printf( "Pragma: no-cache\r\n" );
printf( "Cache-Control: no-cache\r\n" );
/* 以下の改行はHTTPヘッダ部の終わりを意味し、必須です. */
printf( "\r\n" );
/* 以下、HTMLの本体を出力する処理となります. */

CGIスクリプトを初めて作成される方はまずはこの基本中の基本を押さえておいてください.

尚、このソースコード hello.c では cgi_util.h をincludeして使います.
cgi_util.h の実装部分となるのが cgi_util.c ファイルです.
これらは hello.c と同じディレクトリにあり、付属のMakefileでいっしょにビルドおよびリンクされるようになっています.
以降の説明ではこれらを単にcgi_utilと呼ぶことにします.
cgi_utilはC言語でCGIを作るならば最低限必要な処理をまとめたものです.



ビルド方法
この記事では、あなたが既にC言語コンパイラをインストールされていることを前提として話を進めますが、
インストールされていない方はその方法を HowToコンパイル で説明してありますのでよろしければ参考にしてください.
使用するC言語コンパイラは基本的になんでもよいですが、付属のMakefileがサポートするのは、以下のものとなっています.
  • MinGW
  • MSYS上のgcc(つまりMinGW)
  • VC(バージョン6.0以上)
  • Cygwin上のgcc
  • Linux上のgcc
  • Android NDKのgcc
特にVCでIDE環境を使用している方などあまりMakefileに馴染みのない方もおられるでしょう.
VCでMakefileを使ってビルドする場合はnmakeコマンドを使いますが、整えなければならない環境変数などもあり若干面倒です.
ここではauto_triggerというツールを使いましょう.

auto_trigger は特にWindows上でC言語ビルドするのを簡単にするために我々が開発したツールです.
Moai本体やEasterを始めとしたCGIアプリケーションはすべて auto_trigger によるビルドが可能になっています.
別にこれを使わなくてもコマンドプロンプトを自分で開いてmakeコマンドを叩けばビルドできるのですが、
特にWindows上ではこれを使った方が圧倒的に楽だと思います.

ソースコードと同じディレクトリに auto_trigger.batというファイルがありますのでそれをクリックして実行してください.



古き良き時代を彷彿とさせるコマンドラインベースでのメニュー選択UIが表示されるので、
「3. switch debug-mode」を何回か選んでDEBUG_MODEを「optimize」にし、
「4. switch mkfid」を何回か選んでMKF_IDを「vc」にし、
「5. switch machine」を何回か選んでMACHINEを「x86」あるいは「x64」にし、
最後に「0. make」または「1. make install」を選びましょう.

ここで「選ぶ」というのは、一番先頭にある数字のキーを押せばよいです.
またはその項目までカーソルキーで移動してEnterキーを押してもよいです.

VCの場合、auto_triggerはこれがインストールされていることを自動的に検出しますので、
特に何の設定をすることなく自動的にMakefileによるビルドが行われます.

Note

ただしVS2017上では環境変数の仕様が変わり、この検出は正しく動作しません.
環境変数 ZNK_VSCOMNTOOLS にVsDevCmd.batの存在するフォルダのフルパスを指定しておく必要があると考えられます.
これは例えば、"C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\Common7\Tools" といった値になります.
これに関してはこちらにも少し詳しく説明してあります.

MinGWの場合は、環境変数 ZNK_MINGW32_DIR に MinGWのトップディレクトリのパスを正しく指定すれば、
auto_triggerを使うことができます( つまり $ZNK_MINGW32_DIR/bin/gcc.exe が実行できるように設定する ).

「0. make」を選んだ場合は、このディレクトリ内にあるすべてのCファイルについて、必要なコンパイルが行われます.
このときout_dirディレクトリが自動的に作成され、その中にコンパイル結果(拡張子がcgiとなっているファイル)が作成されます.
「1. make install」を選んだ場合はまずcgiファイルがコンパイルによって作成された後、
さらにsrcディレクトリより一つ上の階層にmoaiインストール用のディレクトリが作成され、そこにcgiファイルがインストールされます.



目次に戻る

Hello 環境変数!

Moai CGI で使われる環境変数の内容をテスト出力してみます.




説明
CGIスクリプトでは環境変数を介して、クライアントやサーバに関するさまざまな情報を取得します.
環境変数って…OSで定義されているアレ?と思われる方もおられるかもしれませんが、そのアレと同じものです.
C言語ではgetenv関数を使って環境変数を参照することができるのですが、
次のサンプルにおいて詳しく述べますがこの関数には問題があるため、我々はこれを直接使うべきではありません.

一方、CGIスクリプトの環境変数はOSで定義されてるアレとは違うともいえます.
CGIに関する重要な環境変数としては、QUERY_STRING, CONTENT_TYPE, HTTP_COOKIE, SERVER_NAME, SERVER_PORT などがあります.
ところが実際Windowsで「システムのプロパティ」から「環境変数」のダイアログを開いてみてもこんな環境変数どこにも定義されてません.
Linuxでも同じで、envコマンドを叩いてもそれらしき環境変数はどこにも見当たりません.
どういうことでしょう?

からくりはCGIを呼び出すWebサーバにあります.
WebサーバはCGIスクリプトの実行要求を受け取ると、CGI用のプロセスを一つ立ち上げます.
そしてそれに先立ち、クライアントやサーバに関する環境変数をそのプロセス内だけから参照できる形で一時的にセットしているのです.
そのため、OSでは定義されているはずのない環境変数がCGIスクリプト内だけからは定義されているように見えるのでした.
OSで定義されている環境変数をグローバル変数とするなら、CGIの環境変数はローカル変数という位置づけになるでしょう.

では実際にこれらを取得する方法ですが、今回も cgi_util を使いましょう!
cgi_util が提供する関数 CGIEVar_create によってMoai CGIが提供する全ての環境変数を即座に取得できます.
ソースコード内でこれに該当する部分を以下に示します.

/* Moai CGIにおける全環境変数を取得します. */
CGIEVar* evar = CGIEVar_create();
...
/* CGIEVar* は使用後解放する必要があります. */
CGIEVar_destroy( evar );

環境変数は様々なCGIスクリプトに使われるものですので、これも押さえておいてください.


目次に戻る

Hello Query String!

なんかHelloばっかりじゃん!

お気づきになられましたか?
この解説講座ではしばらくこんなタイトルが続きます.
ここでは Moai CGI へQuery Stringを渡し、それをCGIスクリプト側から取得して内容を表示してみます.




説明
そもそもQuery Stringとは何でしょうか?
URLの後ろの方に「?」文字があるのをご覧になったことがあると思います.
その「?」文字よりさらに後ろにある文字列の部分をQuery Stringと呼びます.

Query Stringは name=val という形式のデータの羅列となっており、これが複数ある場合はさらに & 文字でこれらを区切ります.
つまり一般に、name1=val1&name2=val2&name3=val3 … という形式になっています.
通常はnameの部分にそのスクリプトにとってなんらかの意味があってわかりやすい名前をつけます.
そして = 文字に続けてそれに対する値を付加することで、CGIスクリプトに対する引数を指定する形になります.

Query Stringの内容をCGIスクリプト側で獲得するにはどうすればよいでしょうか?
これは環境変数QUERY_STRINGを参照します.
我々は既に一つ前のサンプルでMoai CGIにおける全環境変数を取得していました.
今回も cgi_util にご登場願いましょう.

CGIEVar_createによってCGIEVarを作成すると、これの query_string_ メンバにQuery Stringの値が格納されています.
これを直接参照してもよいのですが、NULL値になっている場合のチェックが面倒なので、
cgi_util が提供する関数マクロ CGIUtil_getQueryString を使いましょう.
この関数マクロには CGIEVar を引数として渡します.

次の処理として、この query_string の解析を行います.
これには CGIUtil_getQueryStringToken を使いましょう.
この関数は、query_string を & 文字で分割し、それぞれの「key=val」という形式の塊(Token)において、そのkeyとvalにあたる部分を取得します.
この関数の第2引数では、それらの塊(Token)の何番目を取得したいかを指定します.

それでは今回も例によってキーポイントとなるコードを示して終わりとしましょう.

/* Moai CGIにおける全環境変数を取得します. */
CGIEVar* evar = CGIEVar_create();
/* Query Stringを取得します. */
const char* query_string = CGIUtil_getQueryString( evar );
...
/* query_stringの内容を解析し & 文字に関して分割(split)して結果を表示します. */
{
size_t i = 0;
char key[ 256 ] = "";
char val[ 256 ] = "";
for( i=0; i<64; ++i ){
int result = CGIUtil_getQueryStringToken( query_string, i,
key, sizeof(key),
val, sizeof(val) );
if( result == 0 ){ break; }
printf( "%s = [%s]\n", rejectHtmlTag(key), rejectHtmlTag(val) );
}
}
...
/* これは使い終わった後解放しなければなりません. */
CGIEVar_destroy( evar );

Query Stringについては以上となります.

補足事項

少し話が脱線しますが C言語で環境変数を取得する関数として getenv を思い浮かべる方もおられると思います.
ですが上述した通り、我々はこの関数を直接使うべきではありません. 安易に使うと大変危険なためです.
使うのはこの関数の特性を十分理解した上で、環境変数の取得/設定を行うラッパーを自作するような仕方のない場合に限定すべきです.
簡単に理由を説明しましょう.

例えば2つの環境変数QUERY_STRINGとHTTP_COOKIEの値を取得したいからといって以下のようなコードを記述してはいけません!

char* query_string = getenv( "QUERY_STRING" );
char* http_cookie = getenv( "HTTP_COOKIE" ); /* NG! */
printf( "query_string=[%s] http_cookie=[%s]",
rejectHtmlTag(query_string), rejectHtmlTag(http_cookie) );

パッと見では何の問題もなさそうに見えるかもしれませんが、実はgetenvを不用意に連続させていることが問題です.
その根拠は The C Standard, 7.22.4.6, paragraph 4 [ISO/IEC 9899:2011] において getenv に関する次のような記述に拠ります.

The getenv function returns a pointer to a string associated with the matched list member.
The string pointed to shall not be modified by the program but may be overwritten by a subsequent call to the getenv function.

つまり上の「NG!」の時点のgetenv呼び出しで早くもquery_stringの示すメモリ領域は無効化している恐れがあります.
これ以降でquery_stringにアクセスすると未定義の動作を引き起こすことでしょう.
従ってgetenvで取得した値は、即座に別の文字列バッファにでもコピーしておかなければなりません.
以下にstrdupを使った修正例を示しましょう. あらかじめEnvVar_get関数を下請けとして定義しておきます.

char* EnvVar_get( const char* varname ){
    char* tmp = getenv( varname );
    return tmp ? strdup( tmp ) : NULL;
}
...
char* query_string = EnvVar_get("QUERY_STRING");
char* http_cookie = EnvVar_get("HTTP_COOKIE");
printf( "query_string=[%s] http_cookie=[%s]",
rejectHtmlTag(query_string), rejectHtmlTag(http_cookie) );
if( query_string ){ free( query_string ); } /* strdupでは解放が必要です */
if( http_cookie ){ free( http_cookie ); } /* strdupでは解放が必要です */

マルチスレッドの場合にも万全を期すなら、EnvVar_get内の開始と終わりにおいてGlobalMutexなどでlock/unlockを掛ける必要があります.
環境変数を設定する関数にputenvというものもありますが、これも同様に(むしろgetenv以上に)深刻な問題があります.
libZnk が提供する関数 ZnkEnvVar_get と ZnkEnvVar_set はgetenvとputenvに関するこの種の問題を解消したものです.


目次に戻る

Hello Post!

今回はいよいよ Moai CGI へフォームの内容をPOSTしてみます.
つまりC言語ではそれをCGI側でどうやって受信するのかを学ぶというわけです.




説明
このCGIスクリプトは二つの仕事を同時に行っています.
一つはクライアントが送信するためのフォームを表示することであり、
もう一つはクライアントが送信したフォームの内容を獲得してその結果を表示することです.

送信したフォームの内容をCGIスクリプト側で獲得するにはどうすればよいでしょうか?
大抵は言語毎にそれを取得するための専用のライブラリが設けられているため、それを利用します.

ではC言語の場合は?
CGIでは標準入力よりクライアントが送信したフォームの内容を獲得する仕様となっています.
(実はCに限らず、どんな言語でも裏では標準入力を分析することにより獲得処理を行っています).

ですが毎回そんなことをしていては大変ですので、今回も例によって cgi_util にご登場願いましょう!
cgi_util が提供する関数 CGIUtil_getStdInStr がこの仕事を行ってくれます.
ソースコード内でこれに該当する部分を以下に示します.

CGIEVar* evar = CGIEVar_create();
...
char stdin_bfr[ 4096 ] = "";
char* end = NULL;
size_t content_length = strtoul( evar->content_length_, &end, 10 );
printf( "Content-Length = [%u]\n", content_length );
if( evar->content_length_ != end ){
size_t i = 0;
CGIUtil_getStdInStr( stdin_bfr, sizeof(stdin_bfr), content_length );
printf( "Original data = [%s]\n", rejectHtmlTag(stdin_bfr) );
}
...
CGIEVar_destroy( evar );

まずはこれから受信されるデータのサイズを調べておきます.
このサイズは環境変数 CONTENT_LENGTH に文字列として格納されています.
CGIEVarの場合content_length_メンバの値がこれに該当します.
C言語の標準関数 strtoul で content_length_ の値を10進数整数として解釈し、文字列から非負整数(size_t型)へ変換します.
このstrtoul関数を実行した結果、第2引数で与えたポインタ値 end が第1引数と異なっていれば、この変換は成功しています.

次に CGIUtil_getStdInStr によって標準入力から最大content_lengthバイトだけ読み込み、
結果を文字列バッファstdin_bfrへ格納しています.
このとき同時にstdin_bfr_size(stdin_bfrの確保サイズ)を指定する必要があります.
この関数は読み込みデータの大きさがstdin_bfrの確保サイズを超える場合、そこで適切に打ち切ってNull終端します.
ちなみにこの関数の戻り値は実際に読み込まれたバイトサイズとなります.

最後に取得したstdin_bfrの内容を表示しています.



目次に戻る

Transfer-Encoding: chunked転送モードに関する実験

今回は Moai CGI でTransfer-Encoding: chunked 転送モードがどのように機能しているかを確認してみます.
何の暗号ですかソレは?な方もご安心ください.
これが一体何なのか、そしてどのような場合に使うのかも説明致します.




説明
CGIスクリプトでprint(C言語ならprintf)した文字列のデータは、ブラウザ上にHTMLとして表示されるのでした.
これはCGIスクリプトからWebServerへ、そしてWebServerからブラウザへとデータがパイプライン方式で転送されていくことで実現します.
このデータが大量である場合そのデータ全体が完成するのを待つのではなく、
出来上がったものから逐次ブラウザへと運ばれていった方が効率がよいです.
そのためにデータ全体を適切なサイズとタイミングでいくつかの断片に区切りながら転送します.
この断片データをチャンク(chunk)と呼びます.

Transfer-Encoding とは転送するための符号化方式といったような意味ですから、
つまり Transfer-Encoding: chunked 転送モード とはそのようなチャンクに分けて転送する方式を意味します.
具体的にプログラムの中でどのようにしてやってるかまではMoai_cgi.cのソースをご覧kここでは詳しく説明しませんが、
Googleで「Transfer-Encoding chunked」で検索してみれば沢山解説されていると思いますので興味ある方はご覧ください.
基本的にはまずサイズの情報があって改行、次にデータ本体が来て改行、これが一つのチャンクデータとなります.
あとはこの繰り返しです.

さて、ここではfflush 関数の呼び出しで chunk が区切られて送信されるというのがポイントです.
Moai WebServerは(というか一般的にWebServerは)、fflushを感知するとそれを即座にchunkとしてブラウザへ転送します.
実際にサンプルを表示させてみた方がわかりやすいと思います.
fflushを区切りとして段階的に表示されていく様子が確認できると思います.


目次に戻る

Hello Post!(GO言語版)

今回はちょっと趣向を変えて、Moai CGI でGO言語を実行するデモをご紹介します.




説明
ソースコードの内容についての説明はここでは致しません(あしからず).
これを動作させるにはGOインタプリタをインストールしておく必要があります.
そしてそのインタプリタへのフルパスをmoaiディレクトリ直下にあるconfig_cgi.myfに記述しておく必要があります.
これに関してはこちらを参照してください.

GO1.10.1 を使った場合は、正常に動作することを確認していますが、
もっと古いバージョンでも大丈夫だと思います.


目次に戻る

他サイトで公開されているPHPスクリプトなどを実行させてみる(その1)

今回は Moai CGI でPHPスクリプトを実行するデモをご紹介します.




説明
デモとして、ふたばちゃんねるにおいて配布されているfutaba.phpを題材にしています(若干コードを修正させて頂きました).
ソースコードの内容についての説明はここでは致しません(あしからず).
これを動作させるにはPHPインタプリタをインストールしておく必要があります.
そしてそのインタプリタへのフルパスをmoaiディレクトリ直下にあるconfig_cgi.myfに記述しておく必要があります.
これに関してはこちらを参照してください.

筆者の環境では以下のような画面が表示されました.



php-4.4.6-Win32を使った場合は、正常に動作することを確認しています.
現在ふたばちゃんねるで使っているPHPはVer5.6.30のようですのでおそらくそのバージョンでも動作はするのでしょう.
残念ながら最新バージョンのPHPではこのスクリプトは正常に動作しないようです.


目次に戻る

他サイトで公開されているPHPスクリプトなどを実行させてみる(その2)

Moai CGI でPHPスクリプトを実行するデモの続きです.




説明
デモとしてとして、ふたばちゃんねるにおいて配布されているup.phpを題材にしています(若干コードを修正させて頂きました).
ソースコードの内容についての説明はここでは致しません(あしからず).
これを動作させるにはPHPインタープリタをインストールしておく必要があります.

筆者の環境では以下のような画面が表示されました.
こちらのスクリプトもまたMoai CGIで安全に実行させることに成功したようです.



php-4.4.6-Win32を使った場合は、正常に動作することを確認しています.
現在ふたばちゃんねるで使っているPHPはVer5.6.30のようですのでおそらくそのバージョンでも動作はするのでしょう.


目次に戻る

さて、この次は?

これで一通りC言語によるCGI開発の入門編が終わりました.
お疲れ様でした.

お次はライブラリを使ったCGIの開発、すなわちMoaiCGI 実践編になります.
ですが、まだまだ先は長いのです.
次へ進む前にここで一旦コーヒーブレークとしましょう.

目次に戻る

This article was written by:
none image

Mr.Moai

@znk project