あなたの標準語
CGI 版
お試し用 Web版 方言変換サーバー試験運用中 !! -> こちら

使い方

 ここで解説しているWeb版がこちらで動いております.お試し下さい.

 ただ,あくまでWeb版は「お試し版」ですから,気に入っていただいた方はWindows版などをダウンロードしてご自分のパソコンでお楽しみ下さい.サーバに負荷がかかり過ぎると前触れなく閉鎖いたします.

 いやぁ,やはり「ダウンロードして下さい」とだけ言っても,自分がそんなサイトに行くと「面倒だ」と言ってやめてしまいますからね (^o^;.とりあえずWeb版でお手軽に楽しんでいただいてお客様の気を引こうかと考えたのですが.....Web版は手軽すぎて,返ってWindows版すらもダウンロードしてもらえない可能性がありますね(爆).

 本題に移ります.

 性質上,「使い方」と「技術的なおはなし」がオーバーラップしてしまいますがご了承下さい.

 重要なことをまず.サーバ上でコンパイルできない環境では使用できません

 自前のサーバなど,自分のホームディレクトリでC言語をコンパイルでき,かつそのサーバでWebサーバが走っており,バイナリをCGIで走らせられる環境でないと使用できません.

 ソースをダウンロードして解凍し,makeして出来上がったバイナリとテキストデータをCGIが実行できるディレクトリに放り込んでやって,chmod755 [バイナリ名] としてnobodyによる実行権を与えてやって下さい.

 私が製作したものは,URLで直接アクセスできるタイプです.何も引数がなければ(つまりCONTENT_LENGTHをgetenv()で取得したときにNULLだったら),同じディレクトリにある head.txt と tail.txt (このふたつは中身がHTML)を読み込んで標準出力にそのまま送り出してやります.初回アクセスのときの動作はまさにそれです.

 また引数があるときは(つまりCONTENT_LENGTHをgetenv()で取得したときに数値を持っていたら),

 くどくど日本語で説明するよりもソースを見る方が早いでしょう.特に複雑なことはやってません.

 フォームから辞書名を受け取れるようにしています.つまり辞書ファイルを複数用意しておけば,簡単に辞書を切り替えることができるということです.将来的には上の試験運用中のサイトでも辞書を増やしていこうかと思っております.上のサイトだと,ラジオボタンの選択によって辞書を切り替えるということになりそうですね.



技術的なおはなし

 CGIというとPerlでやるのが普通(常識?)でしょうが,せっかく変換エンジンをC++で書いたのでそのままC++でCGIに対応することを決意しました.

 stdio版に手を加えず,例えばWebサーバデーモン(Apacheなど)とのやりとりとエンコードを普通通りPerlでやり,変換の際はstdio版を起動してパイプで渡して変換してやっても良かったのですが,どうせならC++で全部できると嬉しいですよね.

 パイプでアプリケーション間をつないでデータのやりとりを行うと著しく速度が低下します.というわけで,送受信,デコードなどもすべてC++で行うのが好ましいでしょう.そこまでやらないとC++で変換する魅力が半減してしまいます.

 というわけで「CでCGI」を参考に,デコード専用のクラスを作成し(後々使い回しできまくり),stdio版のmain関数を改造してHTML対応として作り直しました.幸い「CでCGI」のふじたさんに公開の許可をいただきましたので,デコード専用クラスのソースを解説を交えてこちらで公開しておりますのでご参照下さい.

 全面的にC++で作成(つまりC)したため,Perlで行った場合と比較してかなり高速になったと思います.



C++でCGI(?)

 フォームから送られてくる暗号化されたデータを各フィールドで分割する関数,分割されたデータの各フィールドをデコードしてやる関数をDecodeクラスとしてパッケージ化して書き直しただけで,特に目新しいことはありません.核となるデコードルーチンは基本的にそのままです.公開を許諾して下さったふじたさんに感謝いたします.

 私は自分用に作ったクラスの解説のみにとどめます.POSTメソッドを使用しています.

 クラス化したメリットはそれなりにありまして,使い回しが楽になるとは思います.使い方もかなり簡単になっていて,クラスの中身がよくわからなくてもとりあえず使えますが,ソースを読んでみてもやっていることの意味がよくわからない方は,使用を遠慮すべきかもしれません.一歩間違えるとネットワークがいかに危険なものかは,このページをご覧になっている方なら理解されていることでしょう.

  私自身,自分の書いたソースに完全にホールがないかと言われると絶対に大丈夫とは言えないかもしれませんが,あまり難しいことをやっていない分,セキュリティホールになりそうな部分がわかりやすいので,それなりのチェック機構は備えたと思っております.フォームとして大概のものを流し込んでもクリティカルなエラーは発生しないと思っています.

 というわけで,参考程度に読んで下さい.なんならゼロからクラスを作り直すくらいの気概で使って下さい.プログラムを作り終えたら,公開する前に考えられるいかなる場合も想定して試験し,エラー対策を講じて下さい.自分だけでは気付かないこともありますので公開する前に詳しい知人にチェックしてもらうのがベターです.

 公開しているソースの関連部分を交えながら,実働している方言変換サーバを具体例に取って使い方を解説いたします.CGIのソースファイルをダウンロードしてしまってローカルで読むのが楽でしょうが,とりあえずここで関連している3つのファイルを見てみます.文字コードはS-JISに変換していますが,ソースファイルアーカイブはEUCです.さらにソースファイルの方のファイルにはデバッグ用のコードが入っていたり,コメントが違ったりしていますが,内容は同じものです(たぶん).念のため.

 ....ご覧になりました? もうわかりまくりですか? わかりまくった方は以下はお読みになる必要はありません.「使い方だけ教えてよ.」という方は以下の説明をお読み下さい.

 main.cppの中のmain関数を見て下さい.CGIに関連する記述を取り上げますと,main関数に入った直後に,
 

  if((char*)getenv("CONTENT_LENGTH") == NULL){
    HeadHTML();
    TailHTML();
    return 0;
  }

というところがあります.これはまずgetenvというライブラリ関数を用いて,環境引数CONTENT_LENGTHを受け取ろうとします.これはWebサーバが渡してくれるはずなのですが,getenvの返す値がNULLの場合があります.それはcgiプログラムをURLで引数なしで直接指定された場合です.

 具体的には,私が作成したクラスは,長めの文書にも簡単に対処できるようにとPOSTメソッド用いていますので,フォームからの情報が

http://www.ponpoco.ne.jp/~bububu/ongyaa.cgi?ele1=hoge&ele2=hage&...

のように「?」以降の暗号化されたデータは表示されないのですが,Webサーバから標準入力に対して渡されるデータ構造は同じです.しかし上のようにフォームからの送信でなく,ブラウザのURL窓などで直接CGIをURL指定した場合,つまり

http://www.ponpoco.ne.jp/~bububu/ongyaa.cgi

と指定された場合は,getenvはNULLを返します(おわかりいただけるでしょうか).ですから,もしNULLであった場合はHeadHTML()とTailHTML()というふたつの関数を実行してそのままプログラムを正常終了します.

 このふたつの関数はmain.cppを見ていただくとわかるとおり,HTMLが書かれたテキストファイルを開いてそのまま標準出力へ出力するものです.HeadとTailから想像できる通り,変換後の出力文書を出力する表でHTMLファイルを上下に分割しているだけのものです.

 上下のHTMLファイルはhead.txt,tail.txtとして保持しておき,これらのテキストファイルを書き換えるだけでページの構成を返ることができます.簡単な構成ならばプログラムソース中にHTMLを埋め込んでputsなどで出力してもよいでしょうが,後々のことを考えるとHTMLは別ファイルにしておく方が良策です.だってコンパイルが不要ですから.ファイルを開く分のロスタイムは微々たるものでしょう.

 最初のアクセス -> 引数がない -> 環境引数CONTENT_LENGTHがない -> getenvがNULLを返す -> 変換の必要がない -> 変換文書を表示する領域は必要ない -> 変換文書出力領域の上下のHTMLを出力して正常終了すればよい

という流れになります.

 ちょっと長くなってしまいましたが,ここからが本番でしょう (^^;.

 もしgetenvがNULLでなかったら,次はそれぞれのクラスへのポインタを作成し,newコマンドで実体を生成してメモリを確保しています.Decoderクラス関連は次のようになっています.
 

  Decoder * mem_decoder;
  .....
  mem_decoder = new Decoder;

これでDecoderクラスの実体ができました.今後のデコード処理はポインタであるmem_decoderに依頼します.

 さて,今度はmem_decoderを初期化して実際に使います.
 

  if(mem_decoder->Init(50000,4) == false){
      HeadHTML();//頭のhtmlを送る.
      cout << "文書を入力しましたか? もしくは長すぎませんか?" << endl;
      cout << "<br>思い当たる節はないのにこのメッセージが表示されましたら,私こと「いなだ」に状況を記したメールを御送付いただけると幸いです." << endl; // 
      TailHTML();
      delete mem_dic;
      delete mem_converter;
      delete mem_decoder;
      return (-1);
  }

という部分があります.エラー処理は見ての通りで,エラーが起こると同じようにHTML頭を出力した後,エラーメッセージを送りだし,HTML尻尾を出して,確保したメモリを解放し,一応異常終了してみます(0を返しても問題ない).

 mem_decoder->Init(50000,4)というのが,実行時に実行するメンバ関数Init(long,int)です.1番目の引数は環境引数CONTENT_LENGTHの最大値を与えます.つまりフォームから送られてくるデータの最大値です.これだと約50KBということになります.2番目の引数は要素数です.

 Initしてしまえば,分割,格納,デコードまで一気にやってしまいまして,後ほど説明するGetElementで取得できます.
 
 ご存知の通り,フォームから送られてくるデータはフォームの構成に依存しています.ふたつのフィールドを持つレコードが複数並ぶのが普通です.具体的には

http://www.ponpoco.ne.jp/~bububu/ongyaa.cgi?ele1=hoge&ele2=hage&.....

の「?」以降を日本語で書くと(?),「ele1の中身はhoge,ele2の中身はhage...」という風に並んでいます.もっと具体的に行くと,私が運用している方言弁変換サーバのフォームの中身は辞書選択の項目(input type = radio)と,テキスト入力の窓(textarea)があります.テキスト入力窓にtestと入力して変換ボタンを押すと,「?」の後は

Dictionary=batten-yarou.dic&InputText=test

という感じになります.つまり「Dictionaryの中身はbatten-yarou.dic,InputTextの中身はtest」となります.

 しかし,ですね,意味的にはそうなのですが,渡されるのはあくまで「Dictionary=batten-yarou.dic&InputText=test」という単なるテキスト情報なのです.というわけで,これらを分解するにあたり,それぞれの要素(Dictionary,batten-yarou.dic,InputText,test)に単純にバラして扱うことにしています.

 ここで紹介しているクラスではseparated_string[i]という配列にそのままバラしています.つまり

という感じで代入されていきます(単純).偶数要素が左辺,奇数要素が右辺となります.言い方を変えると区切り文字が「=」と「&」で,そのふたつのどちらかが現れるとそこまでをひとつの要素として格納していきます.上にあるInit(50000,4)の4は要素数で,そういう意味です.配列の宣言と似せようと思いまして,要素数は4でも,取り出すときの要素番号は0から始まり3で終わります.この関係ってプログラミングしていると何となく自然に思えてきますけど,日常生活的ではやっぱり不自然ですよね(余談).

 こちらの方がプログラム的には単純になると思ったのですがいかがでしょう?

 Decoderクラスでは,separated_stringは vector <string> separated_string と宣言されておりまして,取得した文字列から「=」か「&」を見つける度に,vector クラスのメンバ関数push_back(char*)を使って格納していきます.自動的にメモリを確保してくれて便利ですねー.

 もう鋭い方はお気づきかもしれませんが,自動的に格納してくれるなら別に要素数を指定する必要はないのでは? という疑問が浮上しますが,これは一応の安全確保のためです.要素数を指定しておいて格納してみたら指定した数と違うじゃないか!という事態が発生すると,メンバ関数Init(long,int)は遠慮なくfalseを返します

 普通に使う,つまり普通にフォームが送信されて来ることが前提であればそんなことは気にしなくてよいのですが,悪意のあるクラッカーが妙なフォームを送りつけた場合に対する処置です.

 falseが返ってくるのは,

などです.詳しくはソースを見て下さい.

 さて次です.

  string dic_name;
  string decoded_string;

  dic_name = mem_decoder->GetElement(1);
  decoded_string = mem_decoder->GetElement(3);

 そうそう,メンバ関数GetElement(int)は文字列をstiring型で返します.どうかお忘れなく.char*ではありません.

 これで使い方は終わりです.自分の趣味として,使えるところではchar*ではなくてstringを使うことにしていますので,辞書名とデコードされた文字列はstring型の変数に代入されます(それぞれdic_nameとdecoded_string).

 本来ならここでも安全のために要素0と2のチェックもすべきかもしれませんが(つまりmem_decoder->GetElement(0)が本当に"Dictionary"になっているかどうかなど)とりあえずそこまでする必要はないかなと思ってやっていません.

 ないはずの要素にアクセスしようとすると,ヌルストリングが返ります.これに関してはあまり厳しく考えていません.だってクラスを使えるのはプログラマだけですから,敢えて意味不明なことを自分ですることはないでしょう.バグ取りのために変なものを返すようにしておけばよかったでしょうか?

 で,注意点をひとつ.

 上の表の直後にメモリ消費を抑えるため,

delete mem_decoder;

としても理屈としては良さそうですが,どうもそれではうまく動きませんでした.string型で「=(代入演算子)」を用いるとその場で領域を確保してコピーしてくれそうですが,いや確かにコピーはしてくれているようなのですが,私がそうやって実行してみると正常に終了したかに見えてcoreを吐きました.Segmentation faultです.詳しくは調べていないのですが,タイミングが関係しているようです.

 stringの実装がどうなっているかはよくしりませんが,基本的には代入演算子で「その場でコピー」ではなく,「参照渡し」が行われるようです.だからstringは速い,と.しかしその先で参照を持っているstring型変数に変化(文字列の伸縮など)が発生すると,その時点で動的にメモリを確保してコピーするということになっているようなのです.

 不正確なことを言っているのかもしれませんが,メモリ解放のタイミングが影響しているのかもしれません.というわけで問題なく動作するようにmain関数の最後でメモリ解放を行っています.

 ここから以下はDecoderクラス以外話になります.出力の際にHTMLに対応するためにどのような処理を施すかについてです.上でいうところのmain関数内での処理です.

 あと,CGI用に書き足したのは,変換されたテキストを改行コード毎に切り出す作業です.main関数の最後の方にあるように,ここでまたstrtokを使用しています.

 まず,最初の行以外の行頭に「<br>」を挿入して,ブラウザ上で改行できるようにしています.またHTMLでは特殊文字として扱われる「<」「>」の扱いでしょうか.&ltと&gtで置換しています.今のところそれくらいで,空白やタブは無視してますねー.

 ああ疲れました.私もまだまだ未熟ですから,詳しい方がお読みになって「これってセキュリティ上まずいんじゃん?」とか「???」とか「なんじゃこりゃ?」なところがあれば是非ご教示いただきたいと思います.様々な御意見,ご感想,苦情などもお待ちしております.



[トップページへ]