CEHTTPではC++によってDLLを作成することによって、動的ページを作成することができます。それはApache上で動作するPerlやPHPに似ています。
現在のバージョンでは、MOD形式、CHX形式、DLL形式の三種類がネイティブに対応しています。また、ログファイルもDLLを作成することによって、自由にカスタマイズ可能です。
CEHTTPの拡張には2つのクラスライブラリが重要になってきます。こちらの説明もあわせて行っていきます。
環境変数とstdin/stdoutがありません。
環境変数には通常HTTP リクエストヘッダーがセットされます。通常ならば::getenv()にて、これらの値が取得できますが、CEではできません。このため、環境変数を動的にメモリに配置して拡張ファイルを呼び出します。
stdinにはPOSTコマンドで送信されたデータのボディデータが入ります。また、通常のCGIでは出力をstdoutに書き出します。HTTPサーバとCGIプロセスはパイプによって結ばれ、データのやり取りを行います。CEHTTPではこれらも動的にメモリに配置し、受け渡しを行います。
CHX拡張とDLL拡張の動作はほぼ同じです。CHXの正体は単なるDLLファイルです。
ユーザーからの要求で、CHX/DLLが呼び出された場合、CEHTTPは環境変数を作成し、CHX/DLLをロードします。
CHX/DLLはCEHTTPからユーザーリクエストを受けて動的にレスポンスを作成し、CEHTTPに返却します。
CHX/DLLが返すものは、ビットマップでもGIFでもHTMLでも何でも可能です。
リクエストされたファイルの拡張子によって、呼び出されるファイルを変更するCHX形式ファイルです。
登録された拡張子が呼び出されると、CEHTTPはMIMEタイプリストを走査し、実行させるCHXを決定します。
環境変数には、呼び出されたファイル名が入ります。
MODはこれらの情報を元に動的にレスポンスを作成し、CEHTTPに返却します。
MyVarMemは動的に伸張するメモリバッファです。バイナリデータやテキストデータを追加していくことができます。
仕様については「MyVarMem仕様書」を参照してください。
MyVarMemから継承されたクラスです。キーとバリューを保有し、検索、値の取得、書き込みが可能です。
仕様については「MyHash仕様書」を参照してください。
拡張子.chxのファイルで、クラスライブラリにより、DLL拡張より簡単に動的ページを作成することが可能です。 WindowsCEでは標準でPIPEをサポートしないために、メモリ経由で必要な情報を手渡します。 eMVC++にて拡張子「CHX」のDLLを作成し、次の関数をエクスポートしてください。
int fnChx(MyHash* pEnv, MyVarMem* pBody, MyVarMem* pRes)
in | MyHash* pEnv | 環境変数が入ります。 |
in | MyVarMem* pBody | POSTされた場合、リクエストボディが入ります。 |
out | MyVarMem* pRes | クライアントに返す内容を追加します。 |
pResの長さを返却してください。通常 pRes->GetLength() にて取得できます。
全ての受信データを受け取ることが可能で、更に返信伝文を完全にコントロールできますが、扱いが少々面倒です。ISAPIのようなものと考えてください。拡張子.dllのファイルで、サーバが受け取ったデータを全てメモリ経由で受領することが出来ます。 DLLを作成し次の関数をエクスポートしてください。
環境変数は下記の形式で入っています。
実際に入力されるものは、CHX形式と変わりません。
|key1|\0|value1|\0|key2|\0|value2|...|keyN|\0|valueN|\0|\0|
・終端は|\0|\0|
・valueが\0もありうる |key1|\0|\0|key2|...
・Keyが\0は許可しない
int fnCAPI(const void* pEnv, int nEnvLen,const void* pHead, int nHeadLen,const void* pBody, int nBodyLen,void** ppResponce)
in | const void* pEnv | 環境変数が入ります。 |
in | int nEnvLen | 環境変数の長さが入ります。 |
in | int nEnvLen | 環境変数の長さが入ります。 |
in | const void* pHead | ヘッダー部分が入ります。 |
in | int nHeadLen | ヘッダーの長さが入ります。 |
in | const void* pBody | ボディ部分が入ります。 |
in | int nBodyLen | ボディ部分の長さが入ります。 |
out | void** ppResponce | ブラウザに返す情報をmallocして返してください。 |
version 2.30からは
HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, size ) を ::malloc( size
)の代わりに使ってください。
HeapReAlloc( GetProcessHeap(), HEAP_ZERO_MEMORY, p, size ) を ::realloc( size
)の代わりに使ってください。
ブラウザに送信するデータ長(BYTE単位)を返答します。 この値を::send()します。
MIMEタイプ用の拡張モジュールです。関数の形式はCHXと同じです。
どのMIMEタイプを拡張するのかは、MOD拡張のファイル名で決定されます。
txt.modというファイルは、'.txt'拡張子のファイルがリクエストされた際に呼び出されます。
環境変数 SCRIPT_NAME に呼び出されたファイル名が入ります。
PATH_INFO/PATH_TRANSLATED が設定されません。
その他はCHX形式と同じです。
strcpy( szFile, pEnv->GetValueI("DOCUMENT_ROOT") );
strcat( szFile, pEnv->GetValueI("SCRIPT_NAME") );
// '/' ->'\'
char* p = szFile;
while( *p != '\0' ) {
if(*p == '/' ) {
*p++ = '\\';
}
これにより、呼び出されたファイルを変換し、ブラウザへ返却するなどの拡張が行えます。
CEHTTPは起動時にカレントディレクトリに存在する'*.mod'ファイルを列挙します。ここで'txt.mod'ファイルを発見すると、MIMEタイプを変更します。 例 .txt \program files\cehttp\txt.mod
MIMEタイプ設定ファイル'AddType.txt'を上記のように書き換えれば、同様のことが可能です。このファイルについては'CEHTTP ユーザーズ マニュアル'を参照してください。
ログファイルは外部DLL形式により提供されています。これをモディファイすることによって、ログファイルの出力方法を変更することが可能です。
ファイル名は'cehttplog.dll'、関数名は'fnCehttpLog'です。
ログファイル関数には環境変数が与えられますが、ログファイル用にいくつか追加の情報が入っています。
remote_addr | リモートホストのIPアドレスです。 |
request_line | ブラウザが送信してきたリクエストラインです。 |
send_bytes | CEHTTPが送信した有効バイト数です。 |
送信した有効バイト数とは、HTTP ボディ のカウント数です。HTTP ステータスが200以外の場合はセットされません。
typedef int (*CEHTTPLOGAPI)(WCHAR* pwszLogFile, int nLogSize, int nHttpStatus, MyHash* hashRequest);
in | WCHAR* pwszLogFile | CEHTTPが指定するログファイル名です。このファイルにログを記述します。 |
in | int nLogSize | ログファイルのMAX値です。 |
in | int nHttpStatus | CEHTTPが処理したHTTPステータスです。 |
in | MyHash* pHashHeaders | 環境変数が入ります。 |
成功した場合は0以外を、失敗した場合は0を返却してください。
コンパイルは全て Microsoft eMbedded Visual Tools 3.0 の eMVC++ で行われています。
eMVC++はマイクロソフト社から無償で提供されている、WindowsCE用コンパイラです。
CEHTTPは eMVC++ を利用して製作されています。ソースコードのコンパイルを行うにはこれらを入手する必要があります。
現在英語版のダウンロード、日本語版のCD購入が可能です。下記のURLを参考にしてください。
http://www.microsoft.com/japan/windows/embedded/ce/techinfo/default.asp
拡張モジュールはDLLのため、単体では行えません。一番簡単な方法はCEHTTPと拡張モジュールを同時にデバッグすることです。
ひとつのワークスペースにCEHTTPと拡張モジュールのプロジェクトファイルを追加すると、CEHTTPとDLLの両方をステップ実行することが可能です。
メニュー「プロジェクト」-「アクティブプロジェクトに設定」から、各プロジェクトをアクティブにできます。それぞれをデバッグモードでコンパイルします。
これで双方のコードをステップ実行することが可能となります。
WindowsCE上でDLLを取り扱う際に注意しなくてはならない点としては、DLLは一度読み込まれたら、開放されないという点にあります。この点がWindows上との違いになります。
CEHTTPから呼び出される拡張モジュールは、マルチスレッドセーフである必要があります。スタティック変数やグローバル変数を利用する際には注意が必要です。グローバル変数の書き込みは、他のスレッドで動作している拡張モジュールの動作に影響を与えます。
システムオブジェクトの初期化とリソースの開放はDllMain()で行います。たとえばCriticalSectionを利用する際は、下記のように記述します。
CRITICAL_SECTION cs; BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch( ul_reason_for_call ) { case DLL_PROCESS_ATTACH: { InitializeCriticalSection(&cs); break; } case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: break; case DLL_PROCESS_DETACH: { DeleteCriticalSection( &cs ); break; } } return TRUE; } int fnChx(MyHash* pEnv, MyVarMem* pBody, MyVarMem* pRes) { EnterCriticalSection( &cs ); // critical section code here... LeaveCriticalSection( &cs ); }
こうすることによって、他のスレッドがこのDLLを実行中に、不用意にシステムオブジェクトが開放される事を防げます。
WindowsCEはUNICODEがデフォルトとなっています。HTTPプロトコル自体はASCII文字列をシングルバイトで扱う事が主体となりますので、注意が必要です。
CEHTTPではTEXTマクロを使用せずに明示的にWSTRを利用するプログラミングスタイルに移行しています。
実際ブラウザで見るのが一番のテストですが、telnet にて生データのやり取りを閲覧することも有効です。
Windows2000やXPでは、テストに使えるには十分なtelnetコマンドが標準で用意されています。
telnet <接続先> <ポート番号> の書式で利用できます。
>telnet localhost 80 GET / HTTP/1.0
と打ち込むと、HTTPサーバーから返答が帰ってきます。
HTTP/1.0 200 OK Server: CEHTTP/2.2 Content-Length: 127 Last-Modified: Mon, 10 Feb 2003 04:28:49 GMT Connection: Keep-Alive Content-Type: text/html <HTML><BODY><H1>Can you see me?</H1><BR><HR>CEHTTP2 by<A HREF="http://www001.upp.so-net.ne.jp/ishi/">IshiSoft</A></BODY></HTML>
実際にブラウザとWebサーバーのやり取りを見るためのツールがIshiSoft上で提供されています。このツールを利用すると、ブラウザとWebサーバー上のやり取りを閲覧することができます。
マルチスレッドでの試験や、耐久試験などを自動で行いたい場合はWASが便利です。これはマイクロソフトから無償で提供されているツールです。
マクロが記述可能で、マルチスレッドで動作します。ダウンロードについては下記のURLから行えます。
// sample.chx #include "stdafx.h" #include "stdio.h" #include "chx.h" //***************************************************************************** // fhChx //***************************************************************************** int fnChx(MyHash* pEnv, MyVarMem* pBody, MyVarMem* pRes) { char work[2048]; if((char*)pBody) { sprintf(work, "Content-Type: text/html\r\n\r\n" "<html>" "<head><title>CECGI</title></head>" "<u><b>BODY</b></u><br>" "%s<br>" "<u><b>environment</b></u><br>" , pBody->GetBuffer() ); } else { pRes->Add("(null)"); } pRes->Add(work); pEnv->SeekToFirst(); do{ sprintf(work, "%s=%s<br>\n" ,pEnv->GetCurrentKey() ,pEnv->GetCurrentValue() ); pRes->Add(work); }while(pEnv->SeekNext()); pRes->Add("</html>"); return pRes->GetLength(); } //***************************************************************************** // DllMain //***************************************************************************** BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
CEHTTPは以下の環境変数を設定します。もし得られない情報があれば、セットされません。
変数名 解説 SCRIPT_NAME 実行されている拡張ファイル名 PATH_INFO URIの後ろについたパス情報 PATH_TRANSLATED パス情報のローカルファイルシステム上の位置 CONTENT_LENGTH POST時のボディ長 QUERY_STRING URIより後ろのパラメータ REMOTE_ADDR クライアントのIPアドレス REMOTE_HOST DNSによって解決されたホストネーム (dnslookupオプションが必要です) REMOTE_PORT クライアントのポート REQUEST_METHOD POST/GET SERVER_NAME クライアントが指定したWEBサーバのアドレス SERVER_PORT クライアントが指定したサーバーのポート SERVER_PROTOCOL クライアントが指定したHTTPプロトコルバージョン SERVER_SOFTWARE サーバソフト名 HTTP_ACCEPT クライアントが指定した受信可能なMIMEタイプ HTTP_USER_AGENT クライアントソフト名 HTTP_COOKIE クライアントが送信したクッキー名 HTTP_REFERER クライアントが来たページ DOCUMENT_ROOT ローカルファイルシステム上でのドキュメントルート位置 GATEWAY_INTERFACE 拡張ゲートウェイ名
全てのクライアントからのリクエストラインは大文字に変換され、HTTP_で始まるヘッダを付与され、'-'を'_'に変換されています。
たとえば「Accept-Encoding」ならば「HTTP_ACCEPT_ENCORDING」となります。