ブラウザから送られて来るデータの処理
さて、いよいよ 「C言語でCMSを書く」の最後になりますね。
前回のブログでは、検索エンジンについて、その利用法とかいろいろかきまして、そこで、multipart/form-dataの処理のための、binstr()という関数をちょっとご紹介しました。これは、strstr() の拡張版で、strstr()では、文字列に対して、検索するものでしたが、binstr() は、バイナリデータの中で特定のパターンを検索することのできるものでした。
今回は、それを使って、multipart/form-dataを処理する部分についてご紹介です。一応、有料ブログなんですけれど、有料部分には、ちょっと長いプログラムを書いておくだけですので、必要な内容は、無料で読めるようになっています。
HTTPのヘッダ部分の処理
まず、ブラウザが、WEBサーバにアクセスする場合、ほとんどは、ページを見るためなので、ブラウザから送られてくるのは、HTTPのヘッダと呼ばれる部分だけです。で、このヘッダは、WEBサーバのhttpdというシステムが、勝手に環境変数に変換して、CGIに送ります。で、CGIは、それを展開して、データベースサーバHOTPortに丸投げします。それを受け取るHOTPortは、環境変数として受け取り、threadManager* ThMPtr の中にいれます。
----ここから----
typedef struct threadManager
{
//メモリーマネージャ
memoryManager MM;
//Envsの中に、CGIから受け取った環境変数が
//ほぼそのままの形で入っている。
char** Envs;
//環境変数の数
int32 NumEnvs;
//ソケットのファイルデスクリプタ
int32 SocketFD;
//エラーがあった場合、ここにつないでおく。
errorRecord* Last;
errorRecord* First;
};
----ここまで----
よって、この中から、環境変数を取り出すには、getEnv()という関数を使えば良く、これは C言語標準のgetenv(char* Name)と同じ仕組み仕様です。ただし、引数として、threadManager* ThMPtr と、検索するタグ文字列 char* Key の二つをとります。
getEnv()は短いのでここに書いておきます。
----ここから----
char* getEnv (threadManager* ThMPtr, char* Key)
{ if (FALSE ==Start ("getEnv")) return NULL;
threadManager*This =ThMPtr;
if (NULL ==Key) { Error (ERR_NULLPTR); return NULL;}
for (int32 I =0; I <This ->NumEnvs; ++I)
{
if (0 ==strncasecmp (This ->Envs [I], Key, strlen (Key)))
{
if ('=' !=*(This ->Envs [I] +strlen (Key))) continue;
char* ValStr =This ->Envs [I] +strlen (Key) +1; return ValStr;
}
} Error (ERR_NOTFOUND); return NULL;
}
----ここまで----
したがって、CGIから送られてきたデータのうち、ヘッダに相当するところは、getEnv()で内容を取り出すことができます。
問題は、ブラウザから、post メソッドで送られてくるメッセージボディですね。で、メッセージボディがあるときは、以下のようにして、取り出すことができます。
----ここから----
char* CLengthStr =getEnv (ThMPtr, "CONTENT_LENGTH");
size_t ContentLength =0;
if (NULL !=CLengthStr) ContentLength =atoll (CLengthStr);
----ここまで----
また、メッセージボディのデータタイプ(MIMEタイプ)は、CONTENT_TYPEに入ってきますので、こちらも、
char* ContentType = getEnv(ThMPtr,"CONTENT_TYPE");
で、取り出すことができます。
multipart/form-dataの処理
ブラウザから、postで送られてくるデータは、ほとんどの場合、ContentTypeが multipart/form-data というものです。これは、HTMLで、<form ..></form>で送られてくる場合、中には複数のフィールドがあって、そこに、文字列や、画像のバイナリデータなどが埋め込まれているもので、中に、<input..> (あるいは、<text ..></textt>など)が多数あると、その数だけのフィールドがあります。複数のフィールドは、全部繋いだ形で、一気にデータとして送られてきますから、まず、multpart/form-dataがメッセージボディに送られてきたら、個々のフィールドに分離して、その一つ一つのフィールドをデータ構造として入れないといけませんね。
さて、そのフィールドは、boundaryという文字列配列で区切られています。これは、環境変数の CONTENT_TYPEにくっつけてあります。つまり、
上記の char* ContentType は、
multipart/form-data; boundary=AXGHPAAABBBBBB
などとなっています。ここで、AXGHPAAABBBBBBというのが、boundary で、これでmultipart/form-dataが、区切られているわけですね。
だから、メッセージボディ全体で、boundary のパターンがいくつ出てくるか、というのが重要ですから、これの出て来る場所を binstr()で見つける、というお話です。
さて、そこで、区切られたフィールド一つ一つは、今度は、フィールドのヘッダがあり、それを解読しないといけません。そして、そのヘッダの後ろに、ボディがあり、そこに、文字列とか、画像データとか、そういうものが入ってきます。
まず、フィールド一つ一つの形式は、こんな感じです。
----ここから----
--AXGHPAAABBBBBB ←これがboundaryです。
Content-Disposition: form-data; name="(int32)Count"
←一行開ける(実際は、\r\nが二つ)
54 ←フィールドの文字列
--AXGHPAAABBBBBB
----ここまで----
さて、ここでは、HOTPort流の書き方になっていて、フィールドには、文字列かバイナリデータしかきませんが、HOTPortでは、文字列が数値を表す場合などは、その数値の型を、nameのところで、指定できます。ここでは、(int32)つまり、32ビットの整数値ということです。型指定は、(uint64)とか型をカッコでくくることにしています。型指定が無い場合は、テキスト(文字列)ということです。
次に、ファイルのアップロードの場合は、以下のような形になります。
----ここから----
--AXGHPAAABBBBBB
Content-Disposition: form-data; name="Icon"; filename="Icon.png"
Content-Type: image/png
..................ここにバイナリでIcon.png"のデータが来る............
--AXGHPAAABBBBBB
----ここまで----
ここでは、name のほかに、filenameというのがあって、そこに添付されたファイル名が入っています。そして、そのあと、Content-Type: があり、png形式の画像ファイルだということがわかります。
HOTPortでは、ファイルのアップロードの場合については、それがテキストとして読めるものであっても、すべてバイナリデータとして扱うことになっています。
とまあこれで、だいたい終わりです。書けば簡単そうだけれど、実際にこれを処理するプログラムとなると、結構面倒で、それがまたバグの温床にもなる、難しいところです。
まず、element_getPostBody()という関数で、ブラウザから送られてきたメッセージボディを、フィールドごとに分割し、そして、そのフィールドを element型構造体にいれます。この element型構造体については、以下のブログにかきました。
そうすると、element型構造体は、タグ付きでフィールドを取り込むので、そのタグとしては、各フィールドでの、nameで指定されたものをそのままタグにして、内容の文字列を値として書き込みます。
で、ファイルの場合は、バイナリの場合もあるので、content型というデータ構造体にデータをいれます。
----ここから----
struct content
{
//Nameにファイル名が入ります。
char* Name;
//Content-Typeの内容がTypeになります。
char* Type;
//フィールド内でのコンテンツのサイズですね。
//これは、陽には与えられませんので、計算する必要があります。
size_t Size;
//実体となるuint8型の配列です。
uint8* UInt8Array;
};
----ここまで----
さて、multipart/form-dataでは、画像や動画などもフィールド内にかかれるので、かなり大きいデータが送られてくることになります。そこで、それを、element型構造体にいれるときは、データのコピーをすると、メモリー使用量も、処理時間も長くなります。ですから、UInt8Arrayに入れるのは、ポインタだけです。そして、そのまま、データベースに格納し、ファイルにセーブすることで、コピーをなくし、メモリー領域を無駄にせずにデータが処理されます。
element_getPostBody()で重要な点は、nameとかfilename、あるいは、Content-Disposition:などと書かれた部分を認識して、その後ろの文字列を正しく認識し、element型構造体にいれることで、こういうプログラミングはちょっとなれると、ほとんど定型でやれますが、慣れないうちは、バグの温床になりますね。
ということで、有料ページには、element_getPostBody()のソースを書いておきます。