Moai+Easter Advanced Manual
ご案内 Moaiエンジン CustomBoyエンジン HowToコンパイル Moai CGI Developers Raraku言語
RarakuでCGI入門 C言語でCGI mkfgenリファレンス
はじめに
Raraku言語は我々Moai開発チームが独自に開発したプログラミング言語であり、Moai Ver2.3よりデフォルトで付属します。 この記事では、Raraku言語使ってMoai上で動くCGIプログラミングの方法について述べます。 これはCGIを全く作ったことがない方向けのチュートリアルとなります。 またプログラミングを少し齧ったことがある程度の知識があえば望ましいですが、 まったくゼロの方でも(テキストエディタの使い方や改行コードの意味、 コマンドプロンプトやターミナルでのCUI操作などの基本的な知識があれば) 頑張れば多分おおよその雰囲気は掴めるかと思います。尚、以下で紹介しているサンプルコードのライセンスはすべてNYSLとします。
目次
- Hello World
- Hello 環境変数
- Hello Query String
- Hello Moai AuthenticKey
- Hello Post1
- Hello Post2
- Transfer-Encoding: chunked転送モードとRrkFile_flush
- テンプレートHTMLを使って表示する.
- 時間のかかる処理の途中経過を表示する
- おわりに
Hello World
実行テスト
プログラミングにおいて(プログラマが記述する)処理の羅列をソースコードと呼び、 ソースコードが書かれたファイルをソースファイルと呼びます。 ソースファイルはそのまま実行できる場合もありますし、 コンパイルという過程を経てバイナリファイルに変換しないと実行できない場合もあります。 ちなみにRarakuではその両方が可能です。
まずはソースファイルhello.rrksを用意しましたのでこれを実行してみます。 これは Moai CGI で単純にメッセージを出力するようなソースファイルです (尚、以降のサンプルを実行(青いリンクをクリック)するにはMoaiを起動しておく必要があります)。
Execute : hello.rrks
Download(hello.rrks) View source code (110 lines)
#=*
* このファイル(拡張子rrks)はRaraku言語のソースコードファイルです。
* C言語式の /* ... */ や #if 0 ... #endif も使用可能です。
* またRaraku独自の #* ... *# で囲う形式もあります。
* コメント内に「#*」や「*#」といった文字列を含む場合、
* 「#」と「*」の間に「=」を入れたもので囲います。
* コメントで指定されたテキスト領域はRarakuの実行においては無視されます。
*=#
// Rarakuでは「//」から行末までがコメントです。
# Rarakuでは「# 」(#と半角スペースの並び)から行末までがコメントです。
/*
* import文は、簡単に言えばこのファイル以外のRarakuプログラムが提供する機能をこのファイルで
* 使うための宣言です。今回はstd/fileが提供する機能をこのファイルで使用するため、std/fileを
* インポートしています。
* 尚、std/fileはファイル入出力に関する機能を提供するもので、このファイル内ではプリフィックスが
* RrkFileから始まるものがstd/file由来になります(このファイルでは最後の方でこれを呼び出しています)。
*/
import std/file
#*
* このRarakuプログラムは、Moai CGI で簡単な挨拶を表示させます。
* 挨拶を表示させる部分は高々数行で終わりますが、ここでのポイントはそれ以外の部分、
* つまりCGI起動のための事前準備の処理を学ぶことにあります。
*
* CGIを作成する場合、始めにHTTPヘッダを標準出力に印字する必要があります。Raraku言語において、
* 文字列を標準出力に印字するための関数は Rrk_print となります。「"」と「"」で囲まれたものや
* 「@'」と「'」で囲まれたものを文字列リテラルと呼びますが、これも文字列の一種です。一般に
*
* Rrk_print( "string literal" )
* Rrk_print( @'文字列リテラル' )
*
* といった形式で文字列リテラルを標準出力に印字できます。
* 全角文字からなる文字列を囲う場合は「@'」と「'」で囲うことをお勧めします(その方が安全だからです)。
* 尚、Raraku言語ではC言語とは異なり、行末の「;」はほとんどのケースで不要です。
*#
/*
* HTTPヘッダでは \r\n で改行を記述します。ところがWindowsの場合、デフォルトではこれでは
* 問題となります。Windowsでも \r\n で記述できるようにするため、以下の2行を実行します
* (これは標準入力と標準出力をバイナリモードへ変換するためのもので、このプログラムが終了
* するまで有効となります)。
*/
Rrk_setMode( RrkStdIOKind_e_stdin, true )
Rrk_setMode( RrkStdIOKind_e_stdout, true )
/*
* ここではHTTPヘッダを出力します。Rarakuでは「&」演算子を使うことで文字列(string)の連結が行えます。
* 例えば以下の通りです。
*
* "string1" & "string2" & "string3"
*
* ただしRrk_print関数内に文字列リテラルが連続する場合、この「&」を省略できます
* (省略せずに「&」を記述しても構いません)。
*
* HTTPヘッダの終わりには\r\nを一つおきますが、この指定は必須です。
*/
Rrk_print(
"Content-Type: text/html;\r\n"
"Pragma: no-cache\r\n"
"Cache-Control: no-cache\r\n"
"\r\n" /* HTTPヘッダの終わりを示す改行(必須) */
)
/*
* ここまででHTTPヘッダは終わりです。ここから先はHTTPヘッダではないので、改行は\nで構いません。
* ここからはHTML本体をRrk_printで出力します。
*
* 「@'」と「'」で囲った文字列リテラルの場合、中身の文字列を改行することができます
* (正確にはこれはヒアドキュメントと呼ばれます)。
* Rarakuではヒアドキュメント内の生の改行は必ず \n に変換されます。
* 以下では「@'」ではなく「@|'」としていますが、行頭の余計なインデント文字(タブ文字)を自動で除去します。
*/
Rrk_print( @|'
<!DOCTYPE html>
<html>
<head>
<META http-equiv="Content-type" content="text/html; charset=Shift_JIS">
<META http-equiv="Content-Script-Type" content="text/javascript">
<META http-equiv="Content-Style-Type" content="text/css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/bulma.css" rel="stylesheet" type="text/css" />
</head>
')
/*
* Raraku言語において const キーワードは定数の宣言と初期化を意味し、
*
* const 名前 型 = 定数の値
*
* という順序で指定します(ただし右辺の「定数の値」より型が推定できる場合は「型」の
* 指定は省略できます。
*
* 尚、Rrk_print関数内に文字列リテラルと定数が隣合う場合も、文字列連結演算子「&」を省略できます
* (省略せずに「&」を記述しても構いません)。
*/
const greeting = "Hello World"
Rrk_print(
"<body>\n"
"<div class=section>\n"
greeting " on <b>Moai CGI</b>.\n"
"</div>\n"
"</body></html>\n"
)
# 上記の内容をこのタイミングで標準出力へ確実に出力させます。Rrk_stdoutはファイルハンドラ
# と呼ばれる型の一種で標準出力を意味し、Rrk_fflushは指定されたファイルハンドラ(ここでは
# Rrk_stdoutで標準出力)の内容を確実に出力させるための関数です。
RrkFile_flush( RrkFile_stdout() )
CloseRarakuのコメント
では hello.rrks の中身の解説に入りましょう。 まずファイル名に付いている拡張子 rrks はRaraku言語のソースファイルであることを示します。 Raraku言語を記述する場合は基本的にはファイル名の拡張子を rrks にしておかなければなりません。
hello.rrks では、Moai CGI で簡単な挨拶を表示させます。 挨拶を表示させる部分は高々数行で終わりますが、ここでのメインはそれ以外の部分、 つまりCGI起動のための事前準備の処理を学ぶことにあります。
多くの場合、ソースファイル中にはそのソースコードの意味を説明するためのコメントが含まれています。 Rarakuにおいて、「//」が現れた位置からその行末まではコメントであり、 その範囲に書かれた内容はRarakuの実行においては無視されます (これを利用して、プログラマが自由な説明を記述できるというわけです)。 また「# 」(#と半角スペースの並び)が現れた位置からその行末までもコメントです。 例えば以下の通りです。
// Rarakuでは「//」から行末までがコメントです。
# Rarakuでは「# 」(#と半角スペースの並び)から行末までがコメントです。
複数行に渡ってコメントしたい場合は、「/* ... */」や「#if0 ... #endif」を使用することもできます。 「#* ... *#」で囲う形式もあります。
/*
これはいわゆるC言語などで使用されてきたスタイルのコメントです。
*/
/**
* このように行頭に「 * 」を付けて
* その行がコメント行であることを分かりやすくする記法も
* よく使われます。
*/
#if 0
これはいわゆるC言語のプリプロセッサで使用されてきたスタイルのコメントです。
説明を書くよりもコードを一時的に無効にしたいような場合に使用します。
このスタイルのコメントはネストが可能です(/* ... */ ではネストができません)。
#endif
#=*
* これはRaraku独自のスタイルのコメントです。
* 「#*」は必ず行頭に記述する必要があります。
* コメント内に「#*」や「*#」といった文字列を含む場合、
* 「#」と「*」の間に「=」を入れたもので囲います。
*=#
import文
次にimport文について説明します。 import文は、簡単に言えば(このhello.rrksファイル以外の)他のRarakuプログラムが提供する機能を、 この hello.rrks 内で使うための宣言です。今回はstd/fileが提供する機能をこのファイルで使用するため、 std/fileを以下のようにしてインポートしています。
import std/file
尚、std/fileはファイル入出力に関する機能を提供するもので、 このファイル内ではプリフィックスがRrkFileから始まるものがstd/file由来になります (このファイルでは最後の方でこれを呼び出しています)。
HTTPヘッダの出力
多くのプログラミング言語では文字列(string)を処理の対象として取り扱います。 この文字列を表現する記法は色々あるのですが、最も基本的な記法は対象文字列を「"」と「"」で囲うといったものです。 例えば「"hello"」と記述すると、「hello」といった文字列をソースコード上で表現したことになります。 このように「"」と「"」で囲うことにより、文字列をソースコード上に直接記述したものを文字列リテラルと呼びます。
「"」と「"」で囲わなかった場合、通常それはソースコード上においては文字列とは別の意味になります。
そのためにこの両サイドの「"」は必要になります。
文字列は標準出力に印字することでその内容を確認できます。 通常のプログラミング入門講座ではその一番初めに「"hello world"」といった文字列リテラルをWindowsのコマンドプロンプトや Linuxのターミナル上で表示させる例を紹介することがよく行われます。
今回の記事はCGIプログラミング入門ですのでそれについては省略しますが、標準出力自体はCGIでも必須になります。 Raraku言語において、文字列を標準出力に印字するには「Rrk_print( "string literal" )」といった書式で記述します。 例えば以下の通りです。
Rrk_print( "string literal" )
このとき標準出力に印字されるのは「string literal」の部分になります(両サイドの「"」は含まれません)。 「"」と「"」で囲う記法以外にも「@'」と「'」で囲う記法もあり、これも文字列リテラルと呼びます。 例えば以下の通りです。
Rrk_print( @'文字列リテラル' )
全角文字からなる文字列を囲う場合は「"」と「"」よりも「@'」と「'」で囲うことをお勧めします(その方が安全だからです)。 Rarakuにおいて文字列リテラルの中身の文字コードは(ISO-2022-JP以外であれば)何でも構いません。
「Rrk_print(...)」のように識別子の直後に括弧で囲ったものが来る形式を関数(function)と呼びます。 また関数を使うことを特に関数呼び出し(function call)と呼びます。 「その関数が具体的に何をするものか」が定義された部分は、通常「関数呼び出し」とはまた別の離れた場所にあり、 この関数を使っている側からは(その関数について既に知ってない限り)その機能は直ちにはわかりません (その関数の定義部を確認するか、ドキュメント等を読んでその機能を確認する必要があります)。 ただ通常はある程度わかりやすい名前が付いており、それがどんなものかを連想しやすくはなっているとは思います。
CGIを作成する場合、まず始めに以下のようなHTTPヘッダと呼ばれる文字列を標準出力に印字する必要があります。
Content-Type: text/html;
Pragma: no-cache
Cache-Control: no-cache
HTTPヘッダの改行コードは\r\nとしなければなりませんので注意しましょう。 また上記で最後に一つ空行がありますが、それはこの部分にも(上記ではわかりにくいですが)単独で「\r\n」が存在するためです。 さらにCGIでは、HTTPヘッダの記述後に続けてHTMLを記述しますが、 そのHTMLがそのままブラウザに渡されて表示される仕組みとなっています。
参考: HTTPヘッダ
HTTPヘッダの中身についても少しだけ話をしておきましょう。一般にHTTPヘッダは次のような書式を持つ行の連続です。
CGIの場合ブラウザに内容をキャッシュさせたくない場合がほとんどであると考えられます。 ブラウザにこれを指示するため、PragmaキーとCache-Controlキーの値を「no-cache」とします。 また繰り返しになりますが、HTTPヘッダの終わりには\r\nを一つおきますが、この指定は必須です。
Close
キー名: 値
CGIの場合ブラウザに内容をキャッシュさせたくない場合がほとんどであると考えられます。 ブラウザにこれを指示するため、PragmaキーとCache-Controlキーの値を「no-cache」とします。 また繰り返しになりますが、HTTPヘッダの終わりには\r\nを一つおきますが、この指定は必須です。
Close
ところがWindowsの場合、デフォルトではこの「\r\n」による改行が字面通りに行われないため問題となります。 WindowsやLinuxなどで統一して \r\n で記述できるようにするため、以下の2行を実行します (これは標準入力と標準出力をバイナリモードへ変換するためのもので、現在の実行プログラムが終了するまでこの指定は有効となります)。
Rrk_setMode( RrkStdIOKind_e_stdin, true )
Rrk_setMode( RrkStdIOKind_e_stdout, true )
これでようやく上述のHTTPヘッダを標準出力する準備が整いました。 Rarakuのソースコード上では例えば以下のようにします。
Rrk_print(
"Content-Type: text/html;\r\n"
"Pragma: no-cache\r\n"
"Cache-Control: no-cache\r\n"
"\r\n" /* HTTPヘッダの終わりを示す改行(必須) */
)
「"」と「"」で囲った文字列リテラルの場合、その中身に「\」と「r」を続けて書くとそれは(その二文字で)一つの改行コード \r を意味します。 同様にその中身に「\」と「n」を続けて書くとそれは(その二文字で)一つの改行コード \n を意味します。
Rarakuでは「&」演算子を使うことで、文字列(string)の連結を行うことができます。 例えば以下の通りです。
"string1" & "string2" & "string3"
ただしRrk_print関数の括弧内に文字列リテラルが連続する場合、この「&」を省略できます(省略せず記述しても構いません)。 上記のHTTPヘッダの記述ではこれを利用して、「&」演算子なしで文字列を連結しています。
HTMLの出力
ここまででHTTPヘッダは終わりです。ここからはHTML本体をRrk_printで出力します。 HTMLの場合、その改行コードは(\r\nではなく)\nで構いません。 ですがその前に「@'」と「'」で囲った文字列リテラル(正確にはこれはヒアドキュメントと呼ばれます)について もう少し詳しく説明しましょう。
「@'」と「'」で囲った文字列リテラル(ヒアドキュメント)の場合、 (「"」と「"」で囲った場合と異なり)「\」と「n」を続けて書いてもそれは改行の意味ではなく 字面通りそれら二文字が連続している意味になります。 またヒアドキュメントの場合、中身の文字列に「生の改行」を入れることができます(「"」と「"」で囲った場合は「生の改行」を入れることはできません)。 ヒアドキュメント内にこのような「生の改行」を書くと、その改行コードは(現在のファイルの改行コードがどうなっているかに関わらず) 必ず \n と解釈されます。 例えば以下の通りです。
Rrk_print( @|'
<!DOCTYPE html>
<html>
<head>
<META http-equiv="Content-type" content="text/html; charset=Shift_JIS">
<META http-equiv="Content-Script-Type" content="text/javascript">
<META http-equiv="Content-Style-Type" content="text/css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/bulma.css" rel="stylesheet" type="text/css" />
</head>
')
尚、上記では「@'」ではなく「@|'」としていますが、このように「@」と「'」の間に「|」を加えると
各行の行頭にある余計なインデント文字(タブ文字)を自動で除去します。
参考までに上記を敢えて「"」と「"」で囲った文字列リテラルで書きますと、
以下のようになります。
「"」と「"」で囲った文字列リテラルでは中身を直接改行できませんので、 行末部分に明示的に「\n」を記述する必要があります。 また、中身の「"」は全て「\"」と書かなければなりません(さもなくば文字列リテラルがそこで終了してしまいます)。 このように「"」の(文字列リテラルの終了子としての)意味を打ち消す「\」をエスケープ文字と呼びます。
Rrk_print(
"<!DOCTYPE html>\n"
"<html>\n"
"<head>\n"
"<META http-equiv=\"Content-type\" content=\"text/html; charset=Shift_JIS\">\n"
"<META http-equiv=\"Content-Script-Type\" content=\"text/javascript\">\n"
"<META http-equiv=\"Content-Style-Type\" content=\"text/css\">\n"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n"
"<link href=\"/bulma.css\" rel=\"stylesheet\" type=\"text/css\" />\n"
"</head>\n"
)
「"」と「"」で囲った文字列リテラルでは中身を直接改行できませんので、 行末部分に明示的に「\n」を記述する必要があります。 また、中身の「"」は全て「\"」と書かなければなりません(さもなくば文字列リテラルがそこで終了してしまいます)。 このように「"」の(文字列リテラルの終了子としての)意味を打ち消す「\」をエスケープ文字と呼びます。
定数の宣言と型
ここまで長かったですがようやく「挨拶」を出力する部分を書く段階に来ました。 しかしその前に定数の宣言方法について説明しましょう。 Rarakuにおいて const キーワードは定数の宣言と初期化を意味し、
const 名前 型 = 定数の値
という順序で指定します。 例えば以下の通りです。
const greeting conststr = "Hello World"
上記は、greetingが定数の名前、conststrが定数の型、"Hello World"が定数の値です。 定数の名前はプログラマが自由に付けることができ、以降の処理では名前を介してその定数を参照します。 ただし定数の名前においては全角文字は使用できません。 使用可能な文字は半角英数字と「_」だけであり、しかも一番最初の文字は数字であってはいけません。 このような条件を満たす文字の並びを識別子(identifier)と呼びます。
「=」の左側は左辺(式)、右側は右辺(式)と呼ばれます。 このとき左辺と右辺の型は一致させなければなりません。 conststrは(中身が書き換え不能な)文字列であることを示す型です。 一方、"Hello World"という文字列リテラルも、(中身が書き換え不能な)文字列を意味しますので、 この例では左辺と右辺の型は一致しています(逆にこれが一致しない場合はエラーとなります)。
この「=」は「両辺が数学的に等しい」という意味ではなく、 (両辺が数値型や文字列型の場合)「今から左辺に右辺の値を格納する」といった意味になります。 (あるいは数値型以外の場合、「今から左辺が右辺の内容を指し示すようにする」といった意味になります)。
右辺の「定数の値」から、左辺の型が何であるべきか推定できる場合、「型」の指定は省略できます (これを一般に型推論と呼びます)。 例えば以下では型であるconststrを省略しています。 "Hello World"の型から、greetingの型はconststrであることが推定できるからです。
const greeting = "Hello World"
このgreeting定数と文字列リテラルを連結し、挨拶を出力させましょう。 以下のようになります。
const greeting = "Hello World"
Rrk_print(
"<body>\n"
greeting " on <b>Moai CGI</b>.\n"
"</body></html>\n"
)
Rrk_print関数の括弧内においては、 文字列リテラルと文字列リテラルが隣り合う場合だけでなく、 上記のように文字列リテラルと定数が隣合う場合も、 文字列連結演算子「&」を省略できます(省略せず記述しても構いません)。
ここまででメインの処理の記述は終わりましたが、OSは上記の内容を(効率上の理由により)標準出力へ出力するのを遅延させる場合があります。 しかしCGIではそのような遅延をされても困るので、このタイミングで標準出力へ確実に出力させる必要があります。 そのため hello.rrks の最後において以下の処理を実行しています。
RrkFile_flush( RrkFile_stdout() )
RrkFile_flush と RrkFile_stdout はともにstd/file内で宣言されている関数です (最初にimport std/fileを実行したのは、ここでこれらを使用したかったためです)。 RrkFile_stdout() は標準出力を意味するRrkFile型のオブジェクトを返します。 またRrkFile_flushはそれを受けて、標準出力の内容を確実に出力させるための関数です。
Hello Worldのまとめ
hello.rrksについては以上です。 「Hello World」という単純な文字列を表示するだけのプログラムですが、 ここまで随分盛り沢山であったと思います。 このようにプログラミングに初めて触れる(あるいは初めてのプログラミング言語に触れる)場合、 それがたとえ単純なものであっても、学習すべき文法が大量に現れます。 (プログラミング言語に限った話ではないかもしれませんが)最初が一番大変です。 慌てずじっくりまいりましょう。
Rarakuについてのさらなる詳細はRarakuマニュアル
をご覧下さい。
これはRarakuについての完全なマニュアルになります。
目次に戻る
Hello 環境変数
CGIの環境変数
このセクションでは Moai CGI で使われる環境変数の内容をテスト出力します。
Execute : evar.rrks
Download(evar.rrks) View source code (104 lines)
import std/cgi
import std/htp_util
import std/file
/*
* Rarakuにおいて function キーワードは関数の宣言および定義を意味します。
* 仮引数は、「名前 型」という順序で指定します。
* 仮引数の指定の後ろに関数の戻り値の型を指定します(この例ではvoid)。
*/
function show_result( evar RrkCGIEVar ) void
{
/*
* Rarakuのヒアドキュメントにおいて、生の改行はデフォルトでは \n に変換されます。
* また「@|'」で始めますと行頭の余計なインデント文字を自動で削除します。
* しかしさらに「@|'」の替わりに「@|/"\r\n"'」を使いますと、生の改行はデフォルトでは \r\n に変換されるようになります。
* 以下ではこれを利用してHTTPヘッダをより簡易に記述しています(最終行には敢えて空行を入れ、\r\nが一つ入るようにしています)。
*/
Rrk_print( @|/"\r\n"'
Content-Type: text/html; charset=Shift_JIS
Pragma: no-cache
Cache-Control: no-cache
')
Rrk_print( @|'
<!DOCTYPE html>
<html>
<head>
<META http-equiv="Content-type" content="text/html; charset=utf-8">
<META http-equiv="Content-Script-Type" content="text/javascript">
<META http-equiv="Content-Style-Type" content="text/css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/bulma.css" rel="stylesheet" type="text/css" />
</head>
')
Rrk_print( @'
<body>
<div class="section">
Moai CGI Enviroment Variables:<br>
')
Rrk_print( "<pre>\n" )
/*
* Rarakuで「\=」はバックスラッシュオペレータと呼ばれます。
* 「\=X」のように書くと(Xが単純な形であれば)それは「"X=" & X」のように展開されます。
* Xの値を確認したい場合に(Xを冗長に二回書く手間が省け)便利です。
*/
const msg string = (
\=evar.server_name_ \n
\=evar.server_port_ \n
\=evar.content_type_ \n
\=evar.content_length_ \n
\=evar.remote_addr_ \n
\=evar.remote_host_ \n
\=evar.remote_port_ \n
\=evar.request_method_ \n
\=evar.query_string_ \n
\=evar.http_cookie_ \n
\=evar.http_user_agent_ \n
\=evar.http_accept_ \n
)
/* msg内にある一切のHTMLタグの効果を打ち消す */
RrkHtpUtil_negateHtmlTagEffection( msg )
Rrk_print( msg )
Rrk_print( "</pre>\n" )
Rrk_print( "</div> <!-- section -->\n" )
Rrk_print( "</body></html>" \n )
/*
* Rarakuの代入や初期化において、右辺の型から左辺の型が推定可能な場合、左辺の型の指定を省略して記述することもできます。
* 下記では本来は「const stdout RrkFile = RrkFile_stdout()」と書くべきところですが、
* 右辺であるRrkFile_stdout()はRrkFile型であることが明らかであるため、その左辺も自動的にRrkFile型とみなされ、
* その記述を省略できます。
* ただしこのように書くとどんな型が使われているのか字面上では少し分かりにくくなるため、このサンプルでは
* 省略可能なケースでも敢えて省略せずに書くこともあります。
*/
//const stdout RrkFile = RrkFile_stdout()
const stdout = RrkFile_stdout()
RrkFile_flush( stdout )
}
/**
* Moai CGIにおける全環境変数を取得。
* RrkCGIEVar は環境変数の値文字列をメンバとして含む構造体です。
*/
const evar RrkCGIEVar = RrkCGI_getEVar()
/**
* Windowsにおける標準入力、標準出力における \n => \r\n 自動変換を無効にします。
*/
Rrk_setMode( RrkStdIOKind_e_stdin, true )
Rrk_setMode( RrkStdIOKind_e_stdout, true )
/**
* 環境変数群evarの内容の出力です。
* 長くなるため、サブルーチン化します。
*/
show_result( evar )
CloseCGIプログラミングでは環境変数(environment variable)を介して、クライアントやサーバに関するさまざまな情報を取得します。 環境変数といえばOSで定義されているものですが、CGIの環境変数はOSで定義するのではなくWebサーバによって定義されます。
例えばCGIの環境変数で重要なものとしては、QUERY_STRING, CONTENT_TYPE, HTTP_COOKIE, SERVER_NAME, SERVER_PORT などがありますが、 Windowsで「システムのプロパティ」から「環境変数」のダイアログを開いてみても、通常このような環境変数は定義されてません。 Linuxでも同じで、envコマンドを実行してもそれらしき環境変数は見当たりません。
WebサーバはCGIプログラムの実行要求を受け取ると、CGI用のプロセスを一つ立ち上げます。 そしてそれに先立ち、クライアントやサーバに関する環境変数をそのプロセス内だけから参照できる形で一時的にセットします。 そのため、OSでは定義されているはずのない環境変数がCGIプログラム内だけからは定義されているように見えるわけです。
グローバル変数/ローカル変数という言葉をご存知の方ならば、
OSで定義されている環境変数はグローバル変数に、CGIの環境変数はローカル変数に相当するというように
(その関係性を比喩的に)考えてもよいかもしれません。
では実際にこれらを取得する方法です。 Rarakuでは関数 Rrk_getenv によって環境変数を取得できます。 それが最も基本的で原始的な手段ではあるのですが、しかしそれよりも便利な関数 RrkCGI_getEVar が std/cgiで宣言されていますので、 この記事ではそれを使います(そのため std/cgi をインポートしています)。
evar.rrks 内ではこれに該当する部分は最後のあたりにあります。
evar.rrks 内の前半にある「function show_result( evar RrkCGIEVar ) void」から「}」までの範囲はユーザ定義関数と呼ばれるものですが、
これはこのファイルの一番最後で呼び出されて実行されます。
逆に言えば、ファイルの一番最後に到達するまでこの中身は実行されないということでもあります。
このようにプログラムの実行順序が下から上へと移る場合もあります。
このユーザ定義関数の中身については後で詳しく解説しますので、まずは一旦これを読み飛ばしてください。
ファイルの最後の方へスクロールすると以下のような記述があるはずです。
/**
* Moai CGIにおける全環境変数を取得。
* RrkCGIEVar は環境変数の値文字列をメンバとして含む構造体です。
*/
const evar RrkCGIEVar = RrkCGI_getEVar()
右辺にRrkCGI_getEVar関数の呼び出しを与えることによって、左辺でRrkCGIEVar型のデータを取得することができます。 左辺のRrkCGIEVar型定数evarには、この初期化によりMoai CGIが提供する全ての環境変数が格納されます。 RrkCGIEVar型のデータはいくつかのデータを一まとめにしたものであり、 このようなものを一般に構造体(struct)と呼びます。
構造体
構造体に含まれるデータのことをメンバ(menber)あるいはフィールド(field)と呼びます。 構造体は通常「構造体定数名.メンバ名」という書式で、各データへアクセスできます。 例えば以下の通りです。
import std/cgi
const evar RrkCGIEVar = RrkCGI_getEVar()
Rrk_print( "evar.query_string_=" evar.query_string_ )
Rrk_print( "evar.content_type_=" evar.content_type_ )
RrkCGIEVar型の構造体では、メンバとしてquery_string_やcontent_type_が含まれており、 上記では構造体定数名がevarですので、それぞれ「ever.query_string_」や「ever.content_type_」と記述することで両者にアクセスできます。 また両者はどちらも文字列型です(今回は両者の型が偶々どちらも文字列型でしたが、 構造体の中身では各メンバがそれぞれ固有の型を保持しますので、一般にはその型は異なります)。
RrkCGIEVarは std/cgi で宣言されていますが、 この std/cgi とは具体的にはRarakuがインストールされたディレクトリ内のrrk_pkg/std/cgi.rrkhファイルを意味します。
Moaiをインストールした場合、その中にいっしょにRarakuもインストールされます。
よって今回の場合、この「Rarakuがインストールされたディレクトリ」とは
「Moaiをインストールしたディレクトリ」であると考えて構いません。
テキストエディタでこのファイルを開くと、以下のような記述があるのが確認できると思います。
struct RrkCGIEVar {
server_name_ string
server_port_ string
content_type_ string
content_length_ string
remote_addr_ string
remote_host_ string
remote_port_ string
request_method_ string
query_string_ string
http_cookie_ string
http_user_agent_ string
http_accept_ string
}
「struct RrkCGIEVar {」から「}」までで囲まれた部分がこの構造体のメンバになります。 これを見ると確かにquery_string_やcontent_type_がRrkCGIEVarのメンバとして含まれており、 しかもそれらが文字列型(string型)として宣言されていることがわかります。
ただし、ここには「=」や右辺は記述されておらず、これらの値が具体的に何なのかまでは確認できません。 これらに具体的に値を格納するのはRrkCGI_getEVar関数の仕事であり、 その処理を行う本当の実体は std/std.rrkx ファイルになります。
通常のプログラミングでは、このファイルの中身がどうなっているのかまで知る必要はありません。 と言うより std/std.rrkx ファイルは(処理を高速化させるためコンパイル化された)バイナリファイルとなっており、 この中身を人の目で直接確認するのは難しいでしょう。 (このコンパイル化が行われる前の状態の)RrkCGI_getEVar関数のソースコードにあたるものは、 Rarakuのソース(raraku/rrk_src/std/cgi.rrks)として公開しておりますので、 中身に興味がある方はそちらを参照してもらうのが一番よいですが、 ここではせっかくですのでRrkCGI_getEVar関数の定義部だけを抜粋したものを以下に示しましょう。
global RrkCGI_getEVar() RrkCGIEVar
{
function getenv( key conststr ) conststr {
varia val = Rrk_getenv( key )
return val ? val : ""
}
varia evar RrkCGIEVar
evar.server_name_ = getenv( "SERVER_NAME" )
evar.server_port_ = getenv( "SERVER_PORT" )
evar.content_type_ = getenv( "CONTENT_TYPE" )
evar.content_length_ = getenv( "CONTENT_LENGTH" )
evar.remote_addr_ = getenv( "REMOTE_ADDR" )
evar.remote_host_ = getenv( "REMOTE_HOST" )
evar.remote_port_ = getenv( "REMOTE_PORT" )
evar.request_method_ = getenv( "REQUEST_METHOD" )
evar.query_string_ = getenv( "QUERY_STRING" )
evar.http_cookie_ = getenv( "HTTP_COOKIE" )
evar.http_user_agent_= getenv( "HTTP_USER_AGENT" )
evar.http_accept_ = getenv( "HTTP_ACCEPT" )
return evar
}
関数呼び出し「RrkCGI_getEVar()」により、プログラムの実行は一時的に上記の定義部(「{」と「}」で囲まれた内部)へと移ります。 定義部の一番初めにある「function getenv」は環境変数を取得するローカル関数と呼ばれるものですが、 今回はこの中身を気にする必要はありません。とりあえずこれにより以下のgetenvで環境変数の値が取得できると考えてください。
その直ぐ下にある「varia evar RrkCGIEVar」では、RrkCGIEVar型の構造体の変数(variable)を宣言しています。 「const」では定数を宣言しましたが、それの替わりに「varia」を使用した場合は変数を宣言します。 こちらは重要です。 このようにconstやvariaで始まる文を、Rarakuではそれぞれconst文、varia文と呼びます。
尚、Rarakuでは(C言語などとは異なり)、ほとんどのケースで文の最後に「;」をつける必要はありません。
ただし付けても構いません。
変数は定数と異なり、宣言が終わった後でさらに値を格納することができます。 これを代入と呼びます。
ただしこの記事では、特に「初期化」と明確に区別したい文脈では、代入ではなく再代入と呼ぶことにします。
文脈によっては変数宣言時の初期化で「=」によって値を格納することを「代入」と呼称することもあり、
単に「代入」と呼ぶとこれとの区別が紛らわしいからです。
上記では evar 変数の宣言(初期化)の時点では、各メンバに値は何も格納されていません。 一方、その宣言が終わった後、Rrk_getenv関数ですべての環境変数を取得し、それらを対応するすべてのメンバに代入(再代入)しています。
最後にある「return evar」はreturn文と呼ばれ、この関数の戻り値を(evarとして)指定するものになります。 関数の戻り値とは、その関数を外側から呼び出して右辺として指定した時、 そこにあたかもその戻り値が指定されたのと同じような状況を実現させるためのものです。 また同時にreturn文によってこの関数の定義部での実行はこれで終了し、 プログラムの実行は再び元の位置(正確には関数呼び出し「RrkCGI_getEVar()」の直後)へ移ります。 この関数(RrkCGI_getEVar)を外側から呼び出したものを改めて以下に再掲します。
const evar RrkCGIEVar = RrkCGI_getEVar()
上記の「RrkCGI_getEVar()」の部分では、あたかもその戻り値(内部の変数evar)がそこに指定されたのと 同じような状況が実現します。 これによって内部の変数evarから外部にある定数evarへの値の受け渡しが可能になるわけです。
ユーザ定義関数
RrkCGI_getEVar関数はRarakuがデフォルトで提供する関数です。 一方、Rarakuのユーザ(プログラマ)が自分でオリジナルの関数を作ることもできます。 これをユーザ定義関数と呼びます。
evar.rrks 内ではshow_resultという名前でユーザ定義関数を作成しており、 以下のように「function show_result( evar RrkCGIEVar ) void」から「}」までの範囲がshow_resultの定義部になります。
/*
* Rarakuにおいて function キーワードは関数の宣言および定義を意味します。
* 仮引数は、「名前 型」という順序で指定します。
* 仮引数の指定の後ろに関数の戻り値の型を指定します(この例ではvoid)。
*/
function show_result( evar RrkCGIEVar ) void
{
/* 長いため途中省略 */
}
ユーザ関数の定義部は「function」キーワードから始まります。 次にその関数の名前(関数名)が続きます(上記ではshow_resultが関数名です)。 関数名はプログラマが自由に指定可能ですが、その名前は識別子の条件を満たさなければなりません。 関数名の後には括弧で囲まれた部分が続きますが、この括弧の中身は関数の仮引数(function parameter)と呼ばれます。 上記では「evar RrkCGIEVar」が仮引数であり、evarが仮引数の名前、RrkCGIEVarが仮引数の型となります。 このように仮引数は「名前 型」という順序で指定します。
最後に仮引数の後ろにこの関数の戻り値の型を指定します。 RrkCGI_getEVar関数では戻り値の型はRrkCGIEVar型でしたが、一方で戻り値のない関数というものもあり、 このshow_result関数はまさにそれに相当します。 このように戻り値のない関数の場合、戻り値の型として「void」と指定します。
evar.rrks の一番最後において、以下のように上記の関数show_resultを呼び出しています。 この関数呼び出しにより、プログラムの実行は上記の関数show_resultの定義部の一番初めへと移ります。
/**
* 環境変数群evarの内容の出力です。
* 長くなるため、サブルーチン化します。
*/
show_result( evar )
関数呼び出しの括弧の中身には、定数や変数、文字列リテラルなど(関数定義部の仮引数とは違ってある意味具体的なもの)を指定でき、 これを関数の実引数(function argument)と呼びます。 上記では関数呼び出し「show_result( evar )」の実引数として evar が指定されており、 これは関数定義部の仮引数におけるevarへ反映されます。
この実引数から仮引数へ受け渡しにより、関数呼び出しの外側における情報を関数定義部の内側へ受け渡すことが可能になります。 今回、show_result内部でもこの(外側の)evarの情報が必要となるため、このような指定を行っているというわけです。 尚、実引数と仮引数を明確に区別する必要のない文脈では、これらを単に引数と呼ぶこともあります。
このユーザ定義関数show_resultは、一番最後の「}」へ至るかあるいはreturn文に遭遇するとその実行を終了し、 プログラムの実行を元の関数呼び出しの位置(正確にはその関数呼び出しの直後)に戻します。 今回のevar.rrksでは関数呼び出し「show_result( evar )」の直後にはもう何もありませんので、 show_resultの実行が終了した時点で evar.rrks 自体の処理も終了となります。 それでは次にshow_result内で具体的に何が行われるか、その定義部の詳細について説明しましょう。
ヒアドキュメントによるHTTPヘッダの指定
show_result内で最初に行っているのはHTTPヘッダの標準出力です。 evar.rrksもCGIプログラムですので、hello.rrksの時と同様にまず最初にHTTPヘッダを標準出力する必要があります。 hello.rrksで行っていた処理を以下に再掲します。
Rrk_print(
"Content-Type: text/html;\r\n"
"Pragma: no-cache\r\n"
"Cache-Control: no-cache\r\n"
"\r\n" /* HTTPヘッダの終わりを示す改行(必須) */
)
上記のように記述しても全く問題はないのですが、今回はヒアドキュメントを使ってこれをもっとすっきりと書く方法を紹介しましょう。 ただしRarakuのヒアドキュメントにおいて、生の改行はデフォルトでは \n に変換されます。 しかし既に述べた通り、HTTPヘッダでは必ず「\r\n」で改行しなければなりません。 またHTTPヘッダにおいては行頭に余計なインデント文字(タブ文字)を付けてはいけません。 ヒアドキュメントでHTTPヘッダを書く場合、この二点の問題を解決しなければなりません。 そこで以下のようにヒアドキュメントを「@'」ではなく「@|/"\r\n"'」で始めて記述します。
Rrk_print( @|/"\r\n"'
Content-Type: text/html; charset=Shift_JIS
Pragma: no-cache
Cache-Control: no-cache
')
ヒアドキュメントにおいて「@'」ではなく「@|'」で始めますと行頭の余計なインデント文字を自動で削除します。 また「@'」ではなく「@/"\r\n"'」で始めますと、生の改行が(\nではなく) \r\n に変換されるようになります。 上記の「@|/"\r\n"'」はこの二つの合わせ技です。 ただし「|」と「/"\r\n"」は必ず「@」と「'」の間にあり、しかも「|」は「/"\r\n"」より必ず先に記述しなければなりません。 ちなみに最終行には空行を一つ入れてありますが、これも必須です。 これによりこの部分に\r\nが一つ入るようにしています。
バックスラッシュオペレータ
show_result内の中程では構造体変数evarの全メンバの値を確認表示するためのの文字列msgを作成しています。 その部分を以下に抜粋します。
const msg string = (
\=evar.server_name_ \n
\=evar.server_port_ \n
\=evar.content_type_ \n
\=evar.content_length_ \n
\=evar.remote_addr_ \n
\=evar.remote_host_ \n
\=evar.remote_port_ \n
\=evar.request_method_ \n
\=evar.query_string_ \n
\=evar.http_cookie_ \n
\=evar.http_user_agent_ \n
\=evar.http_accept_ \n
)
RrkHtpUtil_negateHtmlTagEffection( msg )
Rrk_print( msg )
構造体のメンバを確認するのに単に「evar.server_name_」と記述した場合、 確かにそれによってevar.server_name_の中身の値は表示されるのですが、 そのメンバの名前の情報が付加されていないため、実際に実行したときそれがどのメンバの値なのかがわかりにくくなります。 そのため、メンバ名を意味するプリフィックスを付けて「"evar.server_name_=" & evar.server_name_」と記述したいところですが、 このようにすると今度は「evar.server_name_」というコードを冗長に二回記述する必要があります。 しかもこれを構造体の全メンバにおいて行わなければなりません。
Rarakuでは「\=」という特殊な演算子が用意されており、これはバックスラッシュオペレータと呼ばれます。 「\=X」のように書くと(Xが単独の識別子か構造体のメンバへのアクセスの形であれば)それは「"X=" & X」のように自動的に展開されます。 Xの値を確認したい場合に(Xを冗長に二回書く手間が省け)便利です。 また、Rarakuでは(文字列リテラル内ではない)コード中に直に「\n」と記述することもでき、 これは"\n"と全く同じ意味になります(単に「"」二つ分を省略して記述できるというものになります)。
上記の例ではこれらを使って一つの文字列に連結したものをmsgとしています。 また関数RrkHtpUtil_negateHtmlTagEffectionでは、引数として指定した msg の中身にHTMLとして特殊な意味を持つ記号 (タグの開始終了の>や<や&で始まる文字コードの指定など)が含まれていた場合、 それらを単なる文字として字面通り表示されるように変換しています。 最後にRrk_print( msg )によりこれを標準出力します。
Hello環境変数のまとめ
evar.rrksについては以上です。 このセクションで登場した関数の概念はプログラミングにおいて非常に重要です。 しっかり把握した上で次へ進みましょう。
目次に戻る
Hello Query String
Query String
このセクションでは Moai CGI へQuery Stringを渡し、それをCGIスクリプト側から取得して内容を表示してみます。
Execute : query_string.rrks?mode=cat&sort=1
Download(query_string.rrks) View source code (87 lines)
import std/cgi
import std/file
import std/htp_util
/**
* functionキーワードによる関数の定義では、
* 関数の戻り値の型がvoidの場合、それを省略して記述することも可能。
*/
function show_result( post_vars RrkVar[] )
{
/* Output HTTP Header for CGI */
Rrk_print( @|/"\r\n"'
Content-Type: text/html;
Pragma: no-cache
Cache-Control: no-cache
')
/* Output HTML for CGI */
Rrk_print( @'
<!DOCTYPE html>
<html>
<head>
<META http-equiv="Content-type" content="text/html; charset=utf-8">
<META http-equiv="Content-Script-Type" content="text/javascript">
<META http-equiv="Content-Style-Type" content="text/css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/bulma.css" rel="stylesheet" type="text/css" />
</head>
')
Rrk_print( @'
<body>
<div class="section">
PostVars from Query String: <br>
')
Rrk_print( "<pre>\n" )
/***
* for文により配列post_varsの全要素にアクセス
*/
varia msg string = ""
varia i uint
const size uint = Rrk_numof(post_vars)
for i=0; i<size; ++i {
const var RrkVar = post_vars[ i ]
if u := RrkPrim_getStr( var.prim_ ); {
msg &= ( var.name_ " = [" u.val_ "]\n" )
}
}
/* XSS対策. msg内にある一切のHTMLタグの効果を打ち消す */
RrkHtpUtil_negateHtmlTagEffection( msg )
Rrk_print( msg )
Rrk_print( "</pre>\n" )
Rrk_print( "</div> <!-- section -->\n" )
Rrk_print( "</body></html>\n" )
const stdout = RrkFile_stdout()
RrkFile_flush( stdout )
}
/* CGIにおける環境変数を取得 */
const evar = RrkCGI_getEVar()
/* Query Stringを取得 */
const query_str = RrkCGI_getQueryStr( evar )
/* Post変数群格納用のRrkVar型構造体の配列の宣言 */
varia post_vars RrkVar[]
/* Query Stringを & 記号で分割し、Post変数群を取得 */
RrkCGI_splitQueryStr( post_vars, query_str, false )
/* Windowsにおける標準入力、標準出力における \n => \r\n 自動変換を無効にする */
Rrk_setMode( RrkStdIOKind_e_stdin, true )
Rrk_setMode( RrkStdIOKind_e_stdout, true )
/***
* 環境変数群evarの内容の出力
* 長くなるため、サブルーチン化
*/
show_result( post_vars )
CloseそもそもQuery Stringとは何でしょうか? URLの後ろの方に「?」文字があるのをご覧になったことがあるかもしれません。 その「?」文字よりさらに後ろにある文字列の部分をQuery Stringと呼びます。
Query Stringは「name=val」という形式の文字列の羅列となっており、これが複数ある場合はさらに & 文字でこれらが連結されています。 つまり一般に、「name1=val1&name2=val2&name3=val3 …」という形式になっています。 通常はnameの部分になんらかの意味のあるわかりやすい名前をつけます。 そして = 文字に続けてそれに対する値を付加することで、CGIプログラムへの引数を指定する形になります。
関数の引数というのを既に説明しましたが、このようにCGIプログラムそのものにも引数を指定することができます。
Query Stringの内容をCGI側で獲得するにはどうすればよいでしょうか? そのためには環境変数QUERY_STRINGを参照しなければなりません。 前のセクションではRrkCGI_getEVarにより、Moai CGIにおける全環境変数を格納したRrkCGIEVar構造体を取得できました。 この構造体内のメンバquery_string_が環境変数QUERY_STRINGの値を保持しますので、結局のところこれを参照すればよいことになります。
次の処理として、この evar.query_string_ の解析を行います。 これには RrkCGI_splitQueryStr を使いましょう。 この関数は、query_string を & 文字で分割し、さらにそれぞれの「key=val」という形式の塊(Token)において、そのkeyとvalにあたる部分を取得し、 最終的にRrkVar型構造体の配列という形に変換します。 ですがこの先に進む前に、まずそもそも配列とは何かを説明しましょう。
配列
配列(array)とは構造体と同じくいくつかのデータを一まとめにしたものです。 構造体ではそれに含まれるデータをメンバと呼びましたが、配列ではそれを要素(element)と呼びます。 また構造体では各メンバ毎に固有の型を持っていました(従ってメンバ毎に型が異なっていても構わないのでした)が、 配列では全要素が同一の型でなければなりません。 例えば「文字列の配列」と言った場合、その要素はすべて文字列となります。 文字列の配列を宣言するには型指定において「string[]」と記述します。 例えば以下の通りです。
varia str_ary string[]
配列は宣言時においては空です(要素を一つも持ちません)。 これに要素を追加するには文字列の配列の場合はRrk_push_bk_cstr関数、文字列の配列以外の場合はRrk_push_bkを用います。 例えば以下の通りです。
varia str_ary string[]
Rrk_push_bk_cstr( str_ary, "hello" )
Rrk_push_bk_cstr( str_ary, "world" )
上記ではRrk_push_bk_cstrに二つの実引数を与えています。 このように関数の引数は複数存在することもあり、その場合は各引数を「,」で区切って指定します。 また1番めの引数を第1引数、2番めの引数を第2引数、N番めの引数を第N引数などと呼びます (仮引数と実引数の区別を明確にしたい場合は第N仮引数、第N実引数などと呼びます)。
関数の仮引数と実引数の個数は一致していなければなりません。 例えばRrk_push_bk_cstr関数では2個の仮引数がありますので、それに指定すべき実引数も2個でなければならないということです。
また関数の各仮引数と各実引数の対応する型は通常は一致していなければなりません。 例えばRrk_push_bk_cstr関数では第1仮引数は文字列の配列型(conststr[]型)、第2仮引数は文字列型(conststr型)で宣言/定義されていますので、 Rrk_push_bk_cstr関数の呼び出し側においても同様に、文字列の配列型の実引数(str_ary)と、文字列型の実引数("hello"や"world") をこの順番で与えなければなりません。
Rrk_push_bk_cstr関数はRarakuのインストールディレクトリ内のrrk_pkg/std/core.rrkhに宣言/定義されています。
その部分を抜粋したものを以下に示します。
これを見ると確かに第1仮引数のaryがconststr[]型、第2仮引数のcstrがconststr型になっているのが確認できます。
尚、rrk_pkg/std/core.rrkh内に宣言/定義されているものは特別であり、 特に何もインポートすることなくデフォルトで使用することができます。
function Rrk_push_bk_cstr( varia const ary conststr[], cstr conststr ){
Rrk_push_bk( ary, cstr->string /* clone */ )
}
これを見ると確かに第1仮引数のaryがconststr[]型、第2仮引数のcstrがconststr型になっているのが確認できます。
「varia const」とある部分はダブルモディファイアと呼ばれますが、これについてはこの記事のレベルを超えますので気にする必要はありません。
また「{」と「}」で囲まれた中身についても今は気にする必要はありませんが、
ざっくり言えば、指定されたcstrのコピーを作ってそれを配列aryへ追加するといったことを行っています。
尚、rrk_pkg/std/core.rrkh内に宣言/定義されているものは特別であり、 特に何もインポートすることなくデフォルトで使用することができます。
次に配列の各要素にアクセスする記法について説明しましょう。 そのためには「配列の変数名」「[」「配列のインデックス」「]」といった記法を用います。 ここで配列のインデックス(index)とは何番目の配列であるかを示す0以上の整数値です。 例えば以下の通りです。
varia str_ary string[]
Rrk_push_bk_cstr( str_ary, "hello" )
Rrk_push_bk_cstr( str_ary, "world" )
Rrk_print( str_ary[0] \n )
Rrk_print( str_ary[1] \n )
上記ではRrk_push_bk_cstr関数によって配列str_aryに2個の要素が追加されています。 その2個の要素にアクセスするためにそれぞれ「str_ary[0]」、「str_ary[1]」といった記法を用い、 それをRrk_print関数で標準出力しています。 上記の実行結果は以下のようになります。
hello
world
配列のインデックスは 0 から開始することに注意してください。 つまり1番目の要素にアクセスするにはインデックスとして 0 を、 2番目の要素にアクセスするにはインデックスとして 1 をそれぞれ指定します。 もっと一般的に言えば、その配列にN個(N>0)の要素が存在する場合、 配列のインデックスとして 0 から N-1 までが指定できるということです。 もしも仮に N 以上の整数を指定した場合はRarakuはエラーで停止します(これをランタイムエラーと呼びます)。
RarakuではGC(Garbage Collection)でメモリ管理していますので
(C言語などのように)配列の使用後にそれを解放するような処理の記述は不要です。
RrkVar型構造体の配列
配列の要素として構造体を指定することもできます。 例えばRrkVar型構造体の配列を宣言するには以下のようにします。
import std/var
varia post_vars RrkVar[]
RrkVar型構造体はRarakuのインストールディレクトリ内のrrk_pkg/std/var.rrkhに宣言/定義されています (そのため最初にimport std/varを実行しています)。 RrkVar型構造体の宣言部分だけを以下に抜粋します。
struct RrkVar {
name_ string
misc_ string
misc_type_ int
prim_ RrkPrim^? = null
};
上記を見ると、RrkVar型構造体には4つのメンバ(name_、misc_、misc_type_、prim_)があることが確認できます。 post_varsはさらにこれを配列としたものですので、 (post_varsには)4に配列の要素数を乗算した個数分のデータが含まれることになります。
実際にはメンバprim_の型であるRrkPrimも構造体となっており、
そのメンバを考慮するとさらに多くの個数のデータを含む可能性もあります。
三項演算子とnull
さてここでQuery Stringの話に戻りましょう。少し間が開いたので改めて流れを振り返っておきます。 RrkCGI_getEVarによりRrkCGIEVar型構造体evarを取得し、 この構造体内のメンバ evar.query_string_ がQuery Stringの値を保持しますので、これをRrkCGI_splitQueryStr関数を用いて解析するという話でした。 RrkCGI_splitQueryStr関数は、evar.query_string_ の値を元に 最終的に「RrkVar型構造体の配列」という形に変換します(最終的な生成物はpost_varsに格納されます)。 query_string.rrks内でここまでの処理を行っている部分を以下に抜粋します。
/* CGIにおける環境変数を取得 */
const evar = RrkCGI_getEVar()
/* Query Stringを取得 */
const query_str = RrkCGI_getQueryStr( evar )
/* Post変数群格納用のRrkVar型構造体の配列の宣言 */
varia post_vars RrkVar[]
/* Query Stringを & 記号で分割し、Post変数群を取得 */
RrkCGI_splitQueryStr( post_vars, query_str, false )
RrkCGI_getQueryStr関数とRrkCGI_splitQueryStr関数については、 RrkCGI_getEVar関数と同様に std/cgi(ファイルとしてはrrk_pkg/std/cgi.rrkh)において宣言/定義されています。 これらが宣言/定義されている部分だけを以下に抜粋します。
function RrkCGI_getQueryStr( evar RrkCGIEVar ) const string {
const void_str string = ""
return evar.query_string_ ? evar.query_string_ : void_str
}
global RrkCGI_splitQueryStr( varia post_vars RrkVar[], query_str conststr, is_unescape_val bool ) void
RrkCGI_getQueryStrについては上記でその定義部(「{」と「}」で囲まれた部分)を確認することができます。 一般にfunctionキーワードで始まる関数では、その定義部もいっしょに付属して記述されるため、 このように中身を即座に確認可能です。 一方、RrkCGI_splitQueryStrについては、その定義部(「{」と「}」で囲まれた部分)は付属していません。 一般にglobalキーワードで始まる関数では、その定義部は別のファイル(rrksファイル)に記述されます。
尚、globalキーワードで始まる関数をわざわざ別ファイルにする理由は色々あるのですが、
例えばその定義部の記述が複雑で何行にも渡るため、rrkh内に書くと単純に邪魔であるという理由もあります。
関数RrkCGI_getQueryStrの内部におけるreturn文については解説が必要でしょう。 このreturn文では「evar.query_string_ ? evar.query_string_ : void_str」と指定されています。 これは「X ? Y : Z」という形式をとっている式(expression)の一種であり、 一般には三項演算子(ternary operator)と呼ばれます。 この式の意味は「Xの値が真(true)である場合はこの式の値を Y とし、さもなくば(Xの値が偽(false)である場合は) この式の値を Z とせよ」となります。 従って「evar.query_string_ ? evar.query_string_ : void_str」の意味は evar.query_string_ が真であれば evar.query_string_ をそのまま返し、さもなくば void_str を返すというものになります。
ところでevar.query_string_ は単なる文字列ですが、これが真であるとか偽であるとは何を意味するのでしょうか? evar.query_string_ は元を辿ればRrkCGI_getEVar内でRrk_getenv関数によって取得された環境変数QUERY_STRINGの値となります。 もし仮にこの環境変数が定義されていないような状況でRrk_getenvを呼び出した場合、Rrk_getenvはnullと呼ばれる特殊な値を返します。 この場合、結果的にevar.query_string_の値もnullになります。 そして文字列の場合、その値が null でなければ真、nullであれば偽と見なされます。
もっとも、MoaiのWebサーバでは環境変数QUERY_STRINGは(たとえその中身が空文字であっても)必ず定義されておりますし、
Moai以外のWebサーバでもそうなっていることが多いと思いますので、CGIプログラミングでevar.query_string_を参照する文脈である限りは
これがnullになることは実質的にはほぼないと考えられますが、
ここでは万全を期して関数RrkCGI_getQueryStrを介してこの値を取得しています。
if-else文
rrk_pkg/std/cgi.rrkhファイルで宣言されたglobal関数の場合、その定義部は、 Rarakuのソースファイルディレクトリ内のrrk_src/std/cgi.rrksファイルに記述されています。 ただしMoaiのインストールディレクトリにはこれは付属されていないため、この中身を確認するには githubで公開されているRarakuのソースファイルを閲覧する必要があります。
関数RrkCGI_splitQueryStrもglobal関数となっており、その定義部はcgi.rrksファイルに存在するのですが、 そこからRrkCGI_splitQueryStr関数の定義部分だけを抜粋したものを以下に示します。
以下の全容を詳細に把握する必要は全くありませんが、ざっくりとした処理の雰囲気だけでも掴むと RrkCGI_splitQueryStrが結局何を生成しているのかといった理解がより深まるかと思います。
global RrkCGI_splitQueryStr( varia post_vars RrkVar[], query_string conststr, is_unescape_val bool ) void
{
varia stmts string[]
/***
* &に関してsplitする.
*/
RrkStr_addSplitC( stmts,
query_string, 0, Rrk_NPOS,
'&', false, 8 )
/***
* URL unescapeしたものをpost_varsに登録.
*/
foreach stmt : stmts {
const key string = ""
const val string = ""
const key_unesc string = ""
RrkStr_getKeyAndVal( stmt, 0, RrkStr_leng(stmt),
"=", "",
key, val )
RrkHtpURL_unescapeStr( key_unesc, key, 0, Rrk_NPOS )
/* 多重登録防止 */
if Rrk_NPOS == RrkVarAry_find( post_vars, key_unesc ) {
varia var RrkVar = {}
var.name_ = key_unesc
var.misc_type_ = RrkHtpPostVarKind_e_None->int
var.prim_ = RrkPrim_create( RrkPrim_e_Str )
varia var_str string
RrkPrim_str( var.prim_, var_str )
if is_unescape_val {
RrkHtpURL_unescapeStr( var_str, val, 0, Rrk_NPOS )
} else {
RrkStr_set( var_str, val )
}
Rrk_push_bk( post_vars, var )
} /* endof if */
}
}
詳細を把握する必要はないとは述べましたが、上記には文法上重要なポイントも含まれますので、 その部分については解説を加えましょう。 繰り返しますがここはざっくりとした理解で十分です。
まずRrkStr_addSplitC関数の呼び出しでは、query_stringを'&'を区切り文字とみなして分割し、 その分割した文字列群を文字列の配列stmtsへ格納します。
次にforeach文と呼ばれるループ文(ループ文については後で詳しく述べます)によって、 配列stmtsの各要素をstmtという名前をつけてアクセスします。 ここで各要素 stmt は、「key=val」という形式をとった文字列になっていますので、 RrkStr_getKeyAndVal関数の呼び出しで、これをkeyとvalに分割します。
RrkHtpURL_unescapeStr関数ではURLエンコーディングされた文字列をオリジナルの文字列に復元します。 これはちょっと今回の趣旨から脱線する話になるのでよくわからなければ気にしなくてもOKです。
RrkVarAry_find関数の呼び出しでは、配列post_varsの中に含まれるすべての要素、すなわちRrkVar型構造体の要素を調べ、 そのRrkVar構造体のメンバname_がkey_unescと等しいものが存在するか否かを調べています。 これが存在する場合、この関数はその要素のインデックスを返し、存在しない場合はRrk_NPOSと呼ばれる特殊な整数を返します。
「if Rrk_NPOS == RrkVarAry_find( post_vars, key_unesc )」のようにキーワードifで始まるものをif文と呼び、 その直後にある「{」から「} /* endof if */」とある行までの中身をifブロックと呼びます (コメント「/* endof if */」の部分は本当はなくても構いませんが、今回は位置をわかりやすくするためこれを書いています)。 if文では、ifの直後に指定された条件式(この例では「Rrk_NPOS == RrkVarAry_find( post_vars, key_unesc )」の部分が真であれば、 ifブロックが実行され、偽であればifブロックを実行しません。
今これが真である場合(つまり配列post_varsの中に、まだRrkVar構造体のメンバname_がkey_unescと等しいものが存在しない場合)を考えましょう。 この場合に限り、ifブロックが実行されます。 ifブロックでは最初にRrkVar構造体varを宣言し、これを新しく生成しています。 この部分だけを抜粋すると以下になります。
varia var RrkVar = {}
var.name_ = key_unesc
var.misc_type_ = RrkHtpPostVarKind_e_None->int
var.prim_ = RrkPrim_create( RrkPrim_e_Str )
varia var_str string
RrkPrim_str( var.prim_, var_str )
if is_unescape_val {
RrkHtpURL_unescapeStr( var_str, val, 0, Rrk_NPOS )
} else {
RrkStr_set( var_str, val )
}
上記ではまだ説明していない「{}」や「->」といった記号、RrkHtpPostVarKind_e_None、RrkPrim_create、RrkPrim_e_Str、 RrkPrim_str、RrkStr_set などが登場しますが、これらについては今は気にする必要はありません。 ここでは要するに新しくRrkVar型構造体varを作り、そこにキーと文字列値を格納しているという理解で十分です。
ただ一点注意して欲しいのは、今現在ifブロックの中を見ているわけですが、 そのifブロックの中にさらにもう一つのif文(if is_unescape_val)とifブロックが登場することもあるということです。 このようにif文はネストすることができます。
このif文(if is_unescape_val)の後ろにキーワードelseが続いていますが、これをelse文と呼びます。 elseの直後にある「{」から「}」とある行までの中身をelseブロックと呼びます。 else文は(それが存在する場合は)必ずif文の直後に続くため、両者をまとめてif-else文と呼ぶこともあります。 if-else文では直後に指定された条件式が真の場合はifブロックが実行され、 条件式が偽の場合はelseブロックが実行されます。 例えば上記では、is_unescape_val の値が真の場合は、「RrkHtpURL_unescapeStr( var_str, val, 0, Rrk_NPOS )」が実行され、 そうではなく is_unescape_val の値が偽の場合は、「RrkStr_set( var_str, val )」が実行されるということです。
最後の「Rrk_push_bk( post_vars, var )」では新しく生成したRrkVar構造体varをpost_varsに追加しています。
ここまでをまとめますと、結局RrkCGI_splitQueryStr関数内で行われていることは、 query_stringを分割して解析しつつ、そこに指定されているキーが既にpost_varsに存在しているかをif文で確認しつつ、 まだ存在しないならば新しくRrkVar型構造体varを作ってpost_varsに追加するといった処理になります。
Rrk_push_bkはジェネリクスと呼ばれる特殊な関数で、その第1引数には任意の配列型を指定できます。
第2引数にはその配列の要素の型に合致するものであれば指定できます。
以上かなりおおまかに大急ぎでRrkCGI_splitQueryStr関数の定義全体を概観しました。 プログラムのソースコードを読む上では事細かに詳細を追って行く場合が重要である場合もあれば、 今回のように全体をざっくりと概観する視点が大事になることもあります。 このような緩急をつけた読み方が出来るようになるにはかなりの修練と慣れも必要ですが、 これについてはコードを読む経験を重ねれば追々身についていくと思います。
for文
では残ったshow_result関数の解説に入りましょう。 今回は以下のような形になっています。
/**
* functionキーワードによる関数の定義では、
* 関数の戻り値の型がvoidの場合、それを省略して記述することも可能。
*/
function show_result( post_vars RrkVar[] )
{
/* 長いので中身は省略 */
}
今回のshow_result関数は前回のevar.rrksとは異なり、引数としてRrkVar[]型をとります。 戻り値の型は前回と同じくvoidです。 ただ今回のshow_resultではこのvoidを省略して記述しています。 このようにfunctionキーワードで始まる関数の定義では、その戻り値の型がvoidの場合、それを省略して記述することが可能です。
関数の中身については前回のevar.rrksとかなりの部分が同じなので、その部分の説明は省略しますが、 今回は新しく以下のような記述があります。
/***
* for文により配列post_varsの全要素にアクセス
*/
varia msg string = ""
varia i uint
const size uint = Rrk_numof(post_vars)
for i=0; i<size; ++i {
const var RrkVar = post_vars[ i ]
varia str string
RrkPrim_str( var.prim_, str )
msg &= ( var.name_ " = [" str "]\n" )
}
上記でuintとあるのは非負整数型を意味します。 例えば「varia i uint」は、変数 i が0以上の整数であることを宣言しています。
Rrk_numof関数は任意の配列型を実引数として指定できる特殊な関数で、 指定した配列の要素数を戻り値として返します。 戻り値の型はuint型です。
このように任意の型の引数を指定できる特殊な関数をジェネリクスと呼びます。
ただし指定できる型が完全に自由というわけではなく、例えば何らかの配列型でなければならないといった
ある種の制限を設けることができます(この制限が存在することがジェネリクスにおいて重要です)。
キーワードforに続く文をfor文と呼びます。 for文は「for 初期化文 ; 条件式 ; 後処理文 ブロック」という書式になります。
「初期化文」とは一番初めに一回だけ実行される特殊な文です。 「文」とは書きましたがどんな文でもここに指定できるわけではなく、 識別子で始まる形式の文(代入の形式をした文や単純な関数呼び出し)などに限られます。 例えば上記の「i=0」がこれに相当します。 直後にある「;」は必ず記述しなければなりません(たとえ初期化文に何も指定しなくともこの「;」だけは必須です)。
次の「条件式」と「後処理文」の説明は少し後回しにしてブロックの説明を先にしましょう。
ブロックとは「{」と「}」で囲まれた領域を意味します。 これまでもif文においてはifブロック、else文においてはelseブロックが登場しましたが、あれもブロックの一種です。 同様にfor文においてはforブロックが後ろに続くわけです。 そしてfor文とはこのブロックの中身を繰り返し実行するための文です。 このように後続するブロックの中身を繰り返し実行するための文を一般にループ文と呼びます。 ただしそれを何回繰り返すのかを規定する部分が必要で、for文の場合それは「条件式」と「後処理文」になります。
条件式はif文の説明でも登場しましたが、for文にも存在します。 for文ではブロックの実行に先立ってこの条件式の値が真であるか偽であるかが判定されます。 この条件式の真偽判定のことを(条件式の)評価と呼びます。 for文では条件式の評価が真である限り、forブロックが繰り返し実行されます。 例えば上記では「i<size」の部分が条件式に相当し、 これが真と評価される限り上記のforブロックは繰り返し実行されることになります。 尚、この条件式の後ろにある「;」も必ず記述する必要があります。
「後処理文」とはforブロックの実行後に処理される特殊な文です。 「文」とは書きましたがどんな文でもここに指定できるわけではなく、 指定可能なのは、インクリメント形式の文や代入の形式をした文、単純な関数呼び出しなどに限られます。 インクリメントとは整数型の変数に1を加算することです。 例えば上記の「++i」がこれに相当します。 forブロックを一回実行するとこの「後処理文」もその後に必ず実行されますので、 forブロックが何回か実行されるとこのiの値も次々と増加して行くことになります。 やがてsizeの値と同じになるはずですが、このとき条件式「i<size」の評価は偽となります (この条件式はiの値がsize未満の場合のみ真となるからです。 ただしiは0から始めていますのでforブロックは調度size回実行されることに注意しましょう)。 条件式が偽になった時点でforブロックはもはや実行されなくなり、for文全体も終了します。
forブロックの中身についても少し解説しておきます。 この部分だけを以下に抜粋します。
const var RrkVar = post_vars[ i ]
varia str string
RrkPrim_str( var.prim_, str )
msg &= ( var.name_ " = [" str "]\n" )
post_varsはRrkVar型構造体の配列でしたから、「post_vars[ i ]」と記述した場合、 そのi番目(ただしiは0から始まります)の要素にアクセスします。 続くRrkPrim_strではvar.prim_メンバの中身から文字列を抜き出してstrに格納しています。 このstrは、Query Stringに含まれる「key=val」という形式の文字列の右辺(val)に相当する値になっています。 一方、var.name_は、その形式の左辺(name)に相当する値です。 「msg &= ( var.name_ " = [" str "]\n" )」では、それらの値を確認表示するための文字列をmsgへ連結しています。 このmsgは後でRrk_printにより標準出力されます。
foreach文
ここまででquery_string.rrksの解説は終わりになりますが、前項でfor文について紹介したので foreach文についても説明しておきましょう。 foreach文を使うとfor文で必要であった一時変数iやsizeなどを準備する必要がなくなり、 配列要素へのアクセスの記述も少し楽になります。 またforeach文の方がRaraku内部で一時変数iにあたるものを管理しているため、for文より若干高速になる場合もあります。
foreach文もfor文と同じくループ文であり、「foreach 要素参照用変数 : 配列変数名 ブロック」という書式になります。 前項でfor文によって記述していた部分をforeach文によって書き換えると以下のようになります。
/***
* foreach文により配列post_varsの全要素にアクセス
*/
varia msg string = ""
foreach var : post_vars {
varia str string
RrkPrim_str( var.prim_, str )
msg &= ( var.name_ " = [" str "]\n" )
}
始めに「foreach var : post_vars」と記述していますが、このvarの部分が「要素参照用変数」であり、 post_varsの部分が「配列変数名」です。 このforeach文におけるvarは、for文で言えば「const var RrkVar = post_vars[ i ]」と記述していた部分に相当します。 またこのvarは、foreach文のブロックが実行されるたびにpost_vars[0]、post_vars[1]、...、post_vars[size-1]を指し示すよう自動的に更新されます。 このようにしてpost_varsの全要素にアクセスすることができるわけです。 尚かつ、post_varsの最後の要素を指し示したブロックの実行が終了した時点でforeach文自体の繰り返し処理も終了します。 そのため、for文の「for i=0; i<size; ++i」のように明確に繰り返し回数を指定したり、iの値をインクリメントする処理がなくとも、 ちょうどsize回の繰り返し処理が可能になります。
ただforeach文は簡潔に書ける反面、for文に比べ柔軟性は失われます。 例えば配列要素全体ではなくある一部分だけを処理するような場合には向いていません。 foreach文では繰り返し回数の指定が明確にはできないためです。 とは言え、実際のプログラミングにおいては配列要素全体にアクセスするような処理は頻繁に発生します。 そのような場合はforeach文の方が便利でしょう。
Query Stringのまとめ
このセクションでは配列や三項演算子、if-else文、for文、foreach文について学びました。 配列を使うと動的にサイズが変動するようなデータの処理を行うことができます。 またif-else文、for文、foreach文を使うと、この配列に対して ある条件を満たす要素を見つけ出すといったいわゆる「検索」に相当するような処理を記述することができます。
目次に戻る
Hello Moai AuthenticKey
実行テスト
このセクションでは Moai_AuthenticKey について説明します。 以下はこれを取得して表示するためのRarakuのコードです。
Execute : moai_auth.rrks
Download(moai_auth.rrks) View source code (140 lines)
import std/cgi
import std/file
import std/htp_util
import std/str_path
function I_getMoaiAuthKey( auth_key string, ermsg string ) bool
{
function I_getMoaiDir( moai_dir string, ermsg string ) bool
{
static const dsp = '/'
static const depth = 8u
RrkStr_set( moai_dir, "../" )
if !RrkStrPath_searchParentDir( moai_dir, depth, "target.myf", RrkDirType_e_File, dsp ) {
if ermsg {
ermsg &= "[NG]: RrkCGICtx : Error : Searching moai_dir is failure. target.myf does not found."
}
RrkStr_clear( moai_dir )
return false
}
return true
}
function I_loadAuthenticKey( authentic_key string, moai_dir conststr ) bool
{
const path = ( moai_dir "authentic_key.dat" )
if fp := RrkFile_open( path, "rb" ); {
defer RrkFile_close( fp )
RrkStr_clear( authentic_key )
RrkFile_getLine( fp, authentic_key )
return true
}
return false
}
const moai_dir string = ""
/***
* moaiディレクトリの自動検出.
*/
if !I_getMoaiDir( moai_dir, ermsg ) {
/* error */
ermsg &= "[NG]: RrkCGICtx : Error : moai_dir is not found.\n"
return false
}
ermsg &= @$'[OK]: RrkCGICtx : moai_dir is detected : [${moai_dir}].' \n
/***
* authentic_key のロード.
* (このキーの実体は、moaiが起動している場合必ずmoai_dir直下に存在します)
*/
if !I_loadAuthenticKey( auth_key, moai_dir ) {
/* error */
ermsg &= "[NG]: RrkCGICtx : Error : Moai authentic_key cannot load.\n"
return false
}
return true
}
/**
* functionキーワードによる関数の定義では、
* 関数の戻り値の型がvoidの場合、それを省略して記述することも可能。
*/
function show_result( post_vars RrkVar[] )
{
const ermsg string = ""
/* Output HTTP Header for CGI */
Rrk_print( @|/"\r\n"'
Content-Type: text/html;
Pragma: no-cache
Cache-Control: no-cache
')
/* Output HTML for CGI */
Rrk_print( @|'
<!DOCTYPE html>
<html>
<head>
<META http-equiv="Content-type" content="text/html; charset=utf-8">
<META http-equiv="Content-Script-Type" content="text/javascript">
<META http-equiv="Content-Style-Type" content="text/css">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/bulma.css" rel="stylesheet" type="text/css" />
</head>
')
const body string = ""
const auth_key string = ""
varia is_auth = false
if !I_getMoaiAuthKey( auth_key, ermsg ) {
RrkCGIStr_addDefaultSyntaxStyle( body, "RrkMsgConsole" )
body &= "<div class=RrkMsgConsole>\n"
RrkHtpUtil_specialErmsgSyntax( ermsg, "RrkMsgConsole" )
RrkHtpUtil_replaceNLtoHtmlBR( ermsg )
body &= ermsg
body &= "</div>\n"
goto FUNC_END
}
body &= ( "Moai_AuthenticKey=[" auth_key "]<br>\n" )
FUNC_END:
Rrk_print( @|'
<body>
<div class="section">
')
Rrk_print( body )
Rrk_print( @|'
</div> <!-- section -->
</body></html>
')
const stdout = RrkFile_stdout()
RrkFile_flush( stdout )
}
/* CGIにおける環境変数を取得 */
const evar = RrkCGI_getEVar()
/* Query Stringを取得 */
const query_str = RrkCGI_getQueryStr( evar )
/* Post変数群格納用のRrkVar型構造体の配列の宣言 */
varia post_vars RrkVar[]
/* Query Stringを & 記号で分割し、Post変数群を取得 */
RrkCGI_splitQueryStr( post_vars, query_str, false )
/* Windowsにおける標準入力、標準出力における \n => \r\n 自動変換を無効にする */
Rrk_setMode( RrkStdIOKind_e_stdin, true )
Rrk_setMode( RrkStdIOKind_e_stdout, true )
/***
* 環境変数群evarの内容の出力
* 長くなるため、サブルーチン化
*/
show_result( post_vars )
CloseMoai_AuthenticKeyとは、今現在起動しているMoaiが固有で割り当てている認証キーで、16桁の16進数となります。 この次のセクションではPOSTについて学びますが、その前段階としてこれを取り挙げるのは、 単にPOSTするよりこのような認証キーを付加してPOSTした方がより安全性が高まるためです。
CGIへのPOSTはJavascriptから自動的に行うこともできます。 この記事はJavascriptの講座ではありませんが、そのメカニズムを理解するために、 一旦 XMLHttpRequest について説明しましょう。 例えばJavascript上から、以下のようなコードで指定したサイトへ自動的にPOSTすることができます。
function checkPost( filename ){
var data = {
"emal":"sage"
,"com": "Test"
};
var xhr = newXhr();
xhr.open( "POST", "http://127.0.0.1:8124/cgis/cgi_developers/" + filename );
xhr.onreadystatechange = function() {
if( xhr.readyState == 4 ){
if( typeof xhr.status != "undefined" ){
if( xhr.status == 200 ){
var tgt = document.getElementById( "result" );
var text = xhr.responseText;
tgt.innerHTML = text;
} else {
var tgt = document.getElementById( "result" );
var text = "status code = " + xhr.status;
tgt.innerHTML = text;
}
}
}
};
xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded' );
xhr.send( EncodeHTMLForm( data ) );
}
上記の data の部分がこれまでに学んだpost_varsにあたるもので、 このコードはこれを xhr.open の第2引数で指定したサイトへPOSTするものになります。 またこのコードによるPOSTは、それがPOSTされるサーバとは別の位置にあるJavascriptからも行うことができます (ただしすぐ後で述べますが、そのような位置にあるJavascriptからの実行の場合、上記の「xhr.responseText」の値は取得不能です)。
さて、POSTされるサーバ側でこのようなJavascriptでの投稿を拒否したければ、 これがJavascriptからの投稿か否かを判別する情報が必要となり、そのための情報がMoai_AuthenticKeyとなります。 Moai_AuthenticKeyはPOSTされるサーバ側からユーザに向けて発行されます。 POST時にこのキーもいっしょに含めて投稿すれば、このキーが指定されていないか、その値が異なる場合は無効な投稿とみなし、 その投稿を棄却することができます。
しかしこのMoai_AuthenticKeyの値をJavascript上から自動的に取得される恐れはないでしょうか? 実はPOSTされるサーバとは別のURLに置かれたJavascriptからの実行の場合、 このMoai_AuthenticKeyをJavascript上から取得することはできません。 というのは、Javascript上でサーバから発行されたHTMLの内容を取得するには、上記の「xhr.responseText」の値を調べる必要がありますが、 POSTされるサーバとは別のURLに置かれたJavascriptからの実行の場合、xhr.responseTextの値を取得することはできないためです。 よってJavascript上から自動的にMoai_AuthenticKeyを取得し、それを元に自動的にPOSTするといったようなことは、 (POSTされるサーバとは別のURLのJavascriptからは)不可能となります。
以下はそれを確かめるための実験となります。
moai_auth(by xhr_auth(8124)) moai_auth(by xhr_dmz(8125))
上記のmoai_auth.html内における「checkGet(moai_auth.rrks)」と「checkGet(moai_auth.rrks)」は いずれも「http://127.0.0.1:8124/cgis/cgi_developers/moai_auth.rrks」が出力するMoai_AuthenticKeyを(Javascriptから)取得しようとするものです。 このとき、これとポート番号が同じ8124側からの実行ではMoai_AuthenticKeyが(これらのJavascriptから)取得できるのに対し、 これとポート番号が異なる8125側からの実行ではMoai_AuthenticKeyが(これらのJavascriptから)取得できません。 8125側での実行では「status code = 0」などと表示されるかと思います。 これはサーバからの受信が失敗していることを意味し、同時にMoai_AuthenticKeyの受信も不可であることを意味します。
ローカル関数
ではここからはmoai_auth.rrksのコードの解説に入りましょう。 moai_auth.rrksはサーバ側からユーザに向けてMoai_AuthenticKeyを発行するためのCGIプログラムになります。
このプログラムは大きく二つの関数からなり、最初がI_getMoaiAuthKey関数、二番目がshow_result関数となります。 show_result関数については今までと同じ結果のHTTPヘッダやHTMLを標準出力するものですが、 その最初あたりでI_getMoaiAuthKey関数を呼び出しています。
I_getMoaiAuthKey関数の内部をよく見ると、その中にさらにI_getMoaiDir関数とI_loadAuthentickKey関数が定義されていることがわかります。 このように関数内に定義された関数をRarakuではローカル関数と呼びます。
より正確にはグローバルブロック内にfunctionキーワードで宣言された関数もローカル関数と呼ばれますが、
それについては今回は置いておきます。
これらのローカル関数は、この関数内だけから呼び出すことができます(この関数の外からは呼び出すことは通常はできません)。 わざわざそのような制限を設けるのは、その方が全体の依存関係の見通しがよくなるからです。 これらをこの関数の外で(ローカル関数としてではなく通常の関数として)定義することもできますが、 そのように記述するとこれらの関数に依存する関数がどれであるかを考えるとき、 その考慮範囲をファイル全体に広げなければならなくなります。 ローカル関数として定義することで、これらがこの関数内限定で呼ばれることが瞬時にわかるため、 依存関係を考慮する範囲と労力を小さくすることができます。
I_getMoaiDir関数は、Moaiがインストールされたトップディレクトリのパスを得るための関数です。 以下にその定義部分だけを抜粋します。
function I_getMoaiDir( moai_dir string, ermsg string ) bool
{
static const dsp = '/'
static const depth = 8u
RrkStr_set( moai_dir, "../" )
if !RrkStrPath_searchParentDir( moai_dir, depth, "target.myf", RrkDirType_e_File, dsp ) {
if ermsg {
ermsg &= "[NG]: RrkCGICtx : Error : Searching moai_dir is failure. target.myf does not found."
}
RrkStr_clear( moai_dir )
return false
}
return true
}
内部で呼び出されるRrkStrPath_searchParentDir関数は、現在のCGIプログラム(moai_auth.rrks)が置かれたディレクトリを上へ順に辿り、 target.myfファイルが存在するディレクトリを探します。 それが存在するディレクトリを発見した場合、そのディレクトリのパスをmoai_dir変数へ格納します。
I_loadAuthenticKey関数は、moai_dirディレクトリに存在するはずのauthentic_key.datファイルを開き、 その第1行目に書かれてある文字列をauthentic_key変数へ格納します。 実際、Moaiではこのauthentic_key.datファイルの第1行目に書かれてある内容がMoai_AuthenticKeyそのものになります。 以下にその定義部分だけを抜粋します。
function I_loadAuthenticKey( authentic_key string, moai_dir conststr ) bool
{
const path = ( moai_dir "authentic_key.dat" )
if fp := RrkFile_open( path, "rb" ); {
defer RrkFile_close( fp )
RrkStr_clear( authentic_key )
RrkFile_getLine( fp, authentic_key )
return true
}
return false
}
上記ではまだ紹介していない文法事項が含まれています。 例えばif文の条件式の中に「:=」が使われていたり、defer文と呼ばれるものが使われています。 これらの詳しい意味については次のセクションで詳しく述べるのでここでは置いておきます。 とりあえず今の段階ではRrkFile_open関数でpathに指定されたファイルを開き、 RrkFile_getLine関数でそのファイルの内容を一行だけ読み、最後にRrkFile_close関数でそのファイルを閉じるといった流れを 大まかに認識しておくだけで十分です。
I_getMoaiAuthKey関数で行われていることは、これら二つを単純に呼び出し、 それらが失敗した場合はermsgにエラーメッセージを格納することです。 以下にその処理を行っている部分だけを抜粋します。
const moai_dir string = ""
/***
* moaiディレクトリの自動検出.
*/
if !I_getMoaiDir( moai_dir, ermsg ) {
/* error */
ermsg &= "[NG]: RrkCGICtx : Error : moai_dir is not found.\n"
return false
}
ermsg &= @$'[OK]: RrkCGICtx : moai_dir is detected : [${moai_dir}].' \n
/***
* authentic_key のロード.
* (このキーの実体は、moaiが起動している場合必ずmoai_dir直下に存在します)
*/
if !I_loadAuthenticKey( auth_key, moai_dir ) {
/* error */
ermsg &= "[NG]: RrkCGICtx : Error : Moai authentic_key cannot load.\n"
return false
}
return true
上記において、これらが二つとも成功した場合は、最終的にauth_keyにMoai_AuthenticKeyが格納されることになります。
goto文
show_result関数内ではI_getMoaiAuthKey関数を呼び出し、その結果をif文で評価しています。 以下にその処理を行っている部分だけを抜粋します。
if !I_getMoaiAuthKey( auth_key, ermsg ) {
RrkCGIStr_addDefaultSyntaxStyle( body, "RrkMsgConsole" )
body &= "<div class=RrkMsgConsole>\n"
RrkHtpUtil_specialErmsgSyntax( ermsg, "RrkMsgConsole" )
RrkHtpUtil_replaceNLtoHtmlBR( ermsg )
body &= ermsg
body &= "</div>\n"
goto FUNC_END
}
body &= ( "Moai_AuthenticKey=[" auth_key "]<br>\n" ) /* X */
FUNC_END:
I_getMoaiAuthKeyが成功した場合は、if文の後ろにある「/* X */」とある部分において、 Moai_AuthenticKeyの値を表示するためのbody文字列を組み立てます。 一方、I_getMoaiAuthKeyが失敗した場合は、ifブロック内部において、エラーメッセージを表示するためのbody文字列を組み立てます。 このifブロック内部のRrkCGIStr_addDefaultSyntaxStyleやRrkHtpUtil_specialErmsgSyntaxはエラーメッセージを一部色づけするための設定です。 また、RrkHtpUtil_replaceNLtoHtmlBRでは、ermsg内に含まれる改行コードをHTMLの「<br>」に置換しています。 ifブロック内部の最後にある「goto FUNC_END」はgoto文と呼ばれるもので、 「FUNC_END:」と書かれた位置までの処理を飛ばして一気にジャンプします (よってこのとき、「/* X */」と書かれた部分は実行されないことになります)。 一般にgoto文はこのようなエラーハンドリング(エラーが発生した場合に適切に処置すること)においてよく使われます。 Close