C言語でCMSを書く その5 multipart/form-data の処理

記事
IT・テクノロジー
ブラウザから送られて来るデータの処理
さて、いよいよ 「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()のソースを書いておきます。
この続きは購入すると読めるようになります。
残り:10,690文字
C言語でCMSを書く その5 mul... 記事
IT・テクノロジー
500円
サービス数40万件のスキルマーケット、あなたにぴったりのサービスを探す