2009年4月19日日曜日

mod_cacheまわり

Apache Web Serverでコンテンツのcacheをしたくなり調査しました。
まず、cache方法には3種類あって

  1. Reverse Proxy
    Web serverとWeb browserとの間に介入して、cacheする。Apacheにもその機能はあるし、別のツールでも実現できる。Web serverとは別モノを必要とするので構造がややこしくなるのでパス。
  2. mod_file_cache
    Apacheが最終的に読み込むfileそのものをcacheしてしまう。方法をfile openしっぱなしやmmap()しっぱなしなど、安直だけどうまくいけば効果的。cacheの対象がfileなので動的なコンテンツには対応できない。パス。
  3. mod_cache
    Apacheのfilterとして動作し、出力内容をcacheし、次に同じリクエストがあればcache内容を返す。動的なコンテンツにも対応する。本命。
というわけでmod_cacheの設定をします。
そもそもmod_rewriteを使った記事を参考に、コンテンツ圧縮をしていました。というのもApacheのmod_deflateはリクエストの度にコンテンツ圧縮をするので効率が悪かったのです。mod_cacheで圧縮後のコンテンツをcacheできればmod_rewriteのトリッキーな方法ともおさらばです。

さてmod_cacheにはdisk cacheを行うmod_disk_cacheとmemory cacheを行うmod_mem_cacheがあります。ディスクアクセスを減らしたいのでここはmod_mem_cacheで決まりです。
mod_mem_cacheはプロセスごとにmemory cacheを行うためマルチプロセスとなるpreforkモデルでは効果がありません。ここはマルチスレッドを併用するworkerモデルに切り替えます。(Debianなのでapache2-mpm-workerパッケージをインストールするだけ。)

次にmod_deflateによるコンテンツ圧縮の設定です。
AddOutputFilterByType DEFLATE text/plain text/xml application/javascript
text/htmlは加えません。そもそも細かい設定もしません。そこには事情があります。
text/htmlをコンテンツ圧縮したところIE6が固まりました。正確にはKeep Aliveが切断されるまで処理が固まり、他のコンテンツはタイムアウトエラーになりました。mod_setenvifを使用してIE6のみKeep Aliveを拒否してみましたがいまいち効果もなく、またConnection continueが使えないのは応答速度にも影響します。htmlコンテンツは大きくもなく、数も少ないため圧縮対象外としました。
mod_deflateに対して細かい設定をしないことにも事情があります。コンテンツ圧縮を行うことで古いブラウザでは問題を引き起こします。一般的にはそこでブラウザごとに圧縮可否を変えます。これを行ってしまうと、Varyレスポンスヘッダに影響を与えてしまいます。つまり、User-Agentリクエストヘッダに応じてコンテンツを切り替えていることになり「Vary: Accept-Encoding, User-Agent」となってしまいます。mod_cacheはこれを見てUser-Agentごとに異なるcacheを生成します。User-Agentにはブラウザバージョンなどの文字列が含まれているためほとんどcache hitが見込めなくなります。というわけであえてUser-Agentには依存しない設定にしました。

Vary: Accept-Encodingにも問題がありました。リクエストヘッダに「Accept-Encoding: gzip」とあれば圧縮する(なければ圧縮しない)わけですから、Varyに含めるのは当然なのですが、Accept-Encodingの内容がブラウザごとに異なるため、それぞれにcacheを生成してしまいます。そこで考えました。
SetEnvIf Accept-Encoding "\bgzip\b" gzip
RequestHeader set Accept-Encoding gzip env=gzip
RequestHeader unset Accept-Encoding env=!gzip
Accept-Encodingにgzipを含むか含まないかの2択でヘッダを書き換えてしまう方法です。これはどうやらダメでした。cacheのhit判定にはこの書き換えは含まれず、逆にcache保存時にはこの書き換えの影響を受けるためcache hitしなくなりました。というわけでこれに関しては目をつぶります。

他にも問題がありました。mod_deflateはコンテンツ圧縮後のETagに「-gzip」を付与します。cacheのhit判定には「-gzip」が付与されていないためこれまたcache hitしませんでした。そこで
FileETag None
でETagを消しました。元々Last-Modifiedレスポンスヘッダは付いているのでそれほど問題にはなりません。

最後にmod_cacheとmod_mem_cacheの設定です。これはパラメータを適切に設定するだけです。

2009年4月11日土曜日

ToolStripMenuItem.CheckOnClick

こんな機能が欲しかった!
ToolStripMenuItem.CheckOnClickより

ToolStripMenuItemが自動的にチェックされた状態で表示され、クリックしたときにチェック解除されるかどうかを示す値を取得または設定します。
...
クリックしたときにToolStripMenuItemが自動的にチェックされた状態で表示される場合はtrue。それ以外の場合はfalse。既定値はfalseです。
何か矛盾してる。ソースコードを追ってみたところ、前半はウソ。後半が正しい。もう一度、引用しよう。
クリックしたときにToolStripMenuItemが自動的にチェックされた状態で表示される場合はtrue。それ以外の場合はfalse。
翻訳すると、trueにした場合の振る舞いとして、このメニューをクリックするとチェックが付く…チェックを外す方法については言及していません。

大事なことなので2度言いましたよ。

2009年4月4日土曜日

C#でできることはC++/CLIでもできる

私にもそう思っていた頃がありました。
ADO.NET周りに新機能が多くて何か使ってみようと思って調べてみました。

Data Entity Framework
C#とVB.NETのみ。code generatorがC++/CLIに対応してない。ちなみにVisual Studioが生成するファイルとcode generatorが引数に要求するファイルは異なるので自分で処理するのは面倒。次バージョンに期待。
SQL Server Compact 3.5 SP1
C++/CLIからも使用可能。やったね☆
Linq to SQL
さすがにC#のような構文拡張はC++/CLIに期待しないとして…C#とVB.NETのみ。code generatorがC++/CLIに対応してない。更にSQL Server Compact 3.5 SP1はLinq to SQLに未対応。
型指定されたDataSet
C#とVB.NETのみ。code generatorがC++/CLIに対応してよくわからないエラーを吐く。そのためなのか、Visual Studioでも「新しい項目の追加」のテンプレートからも外されている。
Linq to DataSet
型指定されたDataSetが作れない時点で終わってます。
番外編…Settings.settings
動いてるような、動いてないような。型指定されたwrapperクラスを作ろうとSettings.hファイルを作成しstdafx.hに書き込むところまで自動でやってくれます。Settings.hファイルの中身? しらんがな。そのためなのか、Visual Studioでも「新しい項目の追加」のテンプレートからも外されている。

C++/CLIはnativeと.NETとの橋渡し程度に考えておくべきかもしれません…。

2009年4月2日木曜日

逆double thunking

double thunkingに逆も何もないけどなんとなく…。

状況としては次の関数をnative C++でコンパイルしa.dllに入れます。

__declspec(dllexport) LRESULT CALLBACK CBTProc( int nCode, WPARAM wParam, LPARAM lParam );

そしてa.dllにリンクするb.exe内でC++/CLIを使って次の呼び出しをします。
HINSTANCE module = GetModuleHandleW( L"a.dll" );
SetWindowsHookExW( WH_CBT, CBTProc, module, 0 );
するとどうなるか。

コンパイラは単なる関数ポインタCBTProcをmanaged codeから呼べるようwrapします(1段目のthunk)。
次にwrapされたmanagedな関数ポインタをunmanaged codeから呼べるようにwrapします(2段目のthunk)。
この2重にthunkされた関数ポインタをSetWindowsHookExW()に渡します。

#pragma unmanagedとか試してみましたが変化しませんでした。結局、この部分だけソースコードを独立させ、/clrコンパイルオプションを外して強制的にnative C++としてコンパイルすることで回避できました。もっとsmartな方法がありそうですが、見つけられなかった。