ニュース

PHPにPOSTでBase64の文字列を渡すときは注意

RubyのZlibで圧縮したデータをBase64にして、PHPのスクリプトにPOSTで渡すってことをやっていたのですが、圧縮データを展開できる時と、できない時があって悩んでしまった。

よくよく調べると、そもそもBase64をデコード出来ていなかった。それで調べてみると、PHPのドキュメントの下に書いてありました。
PHP: base64_decode – Manual
どうやら、POSTでデータを渡すと、Base64の「+」記号が勝手にスペースに変換されてしまうらしい。
以下のように修正したところ無事に動きました。

## str_replace でスペースを+に置換
$data = base64_decode(str_replace(' ', '+', $data));
## ヘッダ分を差し引いてあげないとだめなのね・・・
$_data = gzinflate(substr($data, 10, -8));

ちなみにRuby側です。
Zlib::MAX_WBITSに+16を足すと、前後に圧縮データ情報も付加されたGZIP形式になるそうです。

z = Zlib::Deflate.new Zlib::BEST_COMPRESSION, Zlib::MAX_WBITS + 16
param = [z.deflate(data, Zlib::FINISH)].pack('m')
z.close

VistaのIEではDNSラウンドロビンはしてくれない

2本の回線を使って色々トラフィックを分散していたのですが、結局の所、画像ファイルをリダイレクトして別のホスト(ドメイン)に投げてしまうと表示されないことが判明(IEだけ?)・・・。
あれこれどうしようか検討して、結局一番シンプルなDNSラウンドロビンを使うことにしました。

しかも最近のブラウザは賢いらしく、DNSラウンドロビンを使っていてもダウンしたサーバには接続しないらしい。
Page Not Found – CNET Japan

早速DNSラウンドロビンに切り替えて運用していたのですが、どうもトラフィックに偏りが・・・?最初は偶然かと思ったのですが、間違いなく偏ってるようです。
それで以下のような記事を発見しました。
DNS でラウンドロビンは当てにならない。 – JULYの日記

DNSラウンドロビンが最近見直され始めてきたのかもしれないですが、過信は禁物か・・・。
う〜ん、どうしたことか、SRVレコードを使えば色々出来るようだけど、最近のブラウザは対応しているのじゃろか。

debianでMySQL with sennaのパッケージを作ってみる

debian lennyをインストールしてみました。debianは過去に一度触ったことがあってそれ以来です。まだsargeも出ていなかった頃だったと思います。
FreeBSDとなんとなく似てる所もあるのですが、最小構成でインストールするとほんとにスッキリしてていいですね。

それで早速いろいろインストールしていたのですが、sennaバインディングしたMySQLをインストールしようとして、ちょっと手間取りました。ソースから入れてしまえば早いのかもしれないですが・・・。

sennaはソースからインストールしたのですが、どうもsennaMySQLパッケージを作ると、依存関係とかで引っかかってしまいました。

dpkg-shlibdeps: failure: no dependency information found for /usr/local/lib/libsenna.so.0 (used by debian/mysql-client-5.0/usr/bin/mysqltest_embedded).
dh_shlibdeps: command returned error code 512
make: *** [binary-arch] エラー 1

分からなかったので、sennaのパッケージも作ってみます。
ここを参考にしました。
|| Not Found ||

## dh_makeというパッケージを作るコマンドを入れる
# aptitude install dh-make
$ tar zxvf senna-1.1.4.tar.gz
$ cd senna-1.1.4
## -fでソースパッケージ指定
$ dh_make -f ../senna-1.1.4.tar.gz
## パッケージの種類(single binaryを選択)
> Type of package: single binary, multiple binary, library, kernel module or cdbs?
> [s/m/l/k/b] s
## 次に作成されたdebianディレクトリの中にある、
## debina/rulesを編集します。
## 今回はmecabを使わないので、configureの値に追記しました。
$ vim debian/rules
> ./configure $(CROSS) --prefix=/usr --mandir=\$${prefix}/share/man --infodir=\$${prefix}/share/info CFLAGS="$(CFLAGS)" LDFLAGS="-Wl,-z,defs" --with-mecab=no
## パッケージを作成
$ dpkg-buildpackage -rfakeroot
## 成功すればdebファイルが出来るので、そのままインストール
# dpkg -i ../senna_1.1.4-1_i386.deb

次にここを参考にして、MySQLのパッケージを作ってみます。記事の内容がちょっと古いようで、今のバインディングパッチではautotoolsなどは不要で一気にconfigureを実行できるようです。

$ mkdir ~/mysql-server-5.0
$ cd ~/mysql-server-5.0
$ apt-get source mysql-server-5.0
# apt-get build-dep mysql-server-5.0

これでカレントディレクトリ内にソースパッケージが展開されます。
次にdpatchを作ります。その前にsennaのバインディングパッチをダウンロードします。
ここからパッチを落とした
ダウンロード – Tritonn – SourceForge.JP

$ cd mysql-dfsg-5.0-5.0.51a
$ dpatch-edit-patch patch 99_senna 95_SECURITY_CVE-2009-2446.dpatch
## ここでパッチ作成用のシェルに自動的に入ります
## キャンセルする時は、exit 230と打たないと反映されてしまうので注意
## 先ほど落としたパッチを当てる
$ patch -p1 < ~/msyql-server-5.0/tritonn-1.0.10-mysql-5.0.51a.diff
$ find . -name '*.rej'
$ find . -name '*.orig' -exec rm {} \;
$ exit

これでdpatchが作成されます。作成したdpatchの情報を書き込みます。

$ echo "99_senna.dpatch" >> debian/patches/00list
## configureに--with-sennaを追加する
$ vim debian/rules
> --without-ndb-docs \
> --with-senna'

参考サイトのまま実行すると、次にdebuildをするわけですが、以下のようなエラーが出てしまいます。

$ debuild -rfakeroot -us -uc
## こんなエラーが出て途中で止まってしまう
configure.in:2981: required file `debian/Makefile.in' not found
configure.in:2981: required file `debian/defs.mk.in' not found
configure.in:2981: required file `debian/control.in' not found
make[1]: *** [Makefile.in] エラー 1
make[1]: ディレクトリ `/home/hogehoge/mysql-server-5.0/mysql-dfsg-5.0-5.0.51a' から出ます
make: *** [build-stamp] エラー 2
dpkg-buildpackage: failure: debian/rules build gave error exit status 2
debuild: fatal error at line 1319:
dpkg-buildpackage -rfakeroot -D -us -uc failed

dpatchを作る際に、configure.inを戻すと書いてあったのですが、これを戻さないでおくとエラーが出る事なくできました。

## これを実行しない
$ cp ~/mysql-server-5.0/mysql-dfsg-5.0-5.0.51a/configure.in .

次にビルドは通ったのですが、どうしてもテストが通りませんでした・・・。とりあえず、テストをしないようにして回避しました。

$ DEB_BUILD_OPTIONS=nocheck debuild -rfakeroot -us -uc

このままでは、libsennaの依存でエラーになりましたので、debian/shlibs.localファイルに以下のように書き込みます。

libsenna        0       senna (>= 1.1.4)

同じようにdebuildしますが、失敗してから再度実行するとエラーになってしまったので、最初からやり直しました。
成功すると以下のようなファイルができます。

libmysqlclient15-dev_5.0.51a-24+lenny2_i386.deb
libmysqlclient15off_5.0.51a-24+lenny2_i386.deb
mysql-client-5.0_5.0.51a-24+lenny2_i386.deb
mysql-client_5.0.51a-24+lenny2_all.deb
mysql-common_5.0.51a-24+lenny2_all.deb
mysql-server-5.0_5.0.51a-24+lenny2_i386.deb
mysql-server_5.0.51a-24+lenny2_all.deb

かなりたいへんでした・・・ソースから入れたほうが楽だったかも。

lighttpdで直リンク対策を簡単にする

前回に引き続き直リンク対策をlighttpdでおこなってみたいと思います。Apacheと同じようにrewriteで・・・と行きたい所ですが、lighttpdmod_rewriteは、Apacheほどの機能は持っていないようです。
設定ファイルにずらずらと書いて行ってもいいのですが、それでは簡単ではありません。色々調べた所、lighttpdでは、include_shellという便利な機能があります。これは、スクリプトなどで出力した結果をそのまま設定として読み取ってくれるという便利な機能です。
今回は、include_shellを使って、ドメイン許可リストから読み取ったものを設定として出力するといったことをしたいと思います。

lighttpdの設定は以下のようになります。

include_shell "/usr/local/etc/lighttpd/referer_list.rb"

referer_list.rbの中身は以下になります。

#!/usr/bin/env ruby
list_file = '/usr/local/etc/referer_list'
referer_list = []
File::open(list_file) do |f|
while line = f.gets
referer_list << line.chomp if line.chomp[0, 1] != "#"
end
end
str = referer_list.join("|").gsub(/\./, "\\.")
puts <<EOS
$HTTP["referer"] !~ "^($|http://(www\\.)?(#{str}))" {
url.access-deny = (".jpg", ".jpeg", ".gif", ".png", ".bmp", ".swf")
}
EOS

例の如くreferer_list.rbには実行権限が必要です。$HTTP[“referer”]を使って、正規表現で許可するドメイン(ホスト)を羅列していくシンプルなものです。後は、条件に一致しないドメインに対し、access-denyをかけます。

referer_list.rbに実行権限をかけるのを忘れ、ひたすら悪戦苦闘してしまった・・・。何もエラーが出ないようなので気をつけてください。

mod_rewriteで直リンク対策を簡単にする

外部から勝手にリンクされて困ってしまう場合、特に画像や動画ファイルなど比較的重たいファイルを勝手にリンクされてしまうと帯域を無駄に使ってしまい、困ってしまうことがあると思います。
そこでリファラを使って、直リンクされている場合に403を返すようにmod_rewriteを使ってみます。
今回はそれを簡単にするために、直リンクを許可するドメインをリスト化し、RewriteMapを使って処理したいと思います。

RewriteMapについては下記の記事でも取り上げています。
Apacheのmod_rewriteを使ってフェイルオーバー? – フタなしカンヅメ

Apacheの設定ファイルは以下のようにします。

<IfModule mod_rewrite.c>
RewriteEngine On
# 必ずロックファイルを指定
RewriteLock /tmp/map.lock
# RewriteMapで使う外部プログラムを指定
RewriteMap referercheck-map prg:/usr/local/bin/referer_check.rb
</IfModule>
<Directory />
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} \.(gif|png|jpg|jpeg|bmp)$ [NC]
RewriteCond ${referercheck-map:%{HTTP_REFERER}|NG} ^NG$
RewriteRule ^.*$ / [L,F]
</Directory>

許可ドメインリストを読み取って処理する「referer_check.rb」の中身です。

#!/usr/bin/env ruby
$stdin.sync = 1
$stdout.sync = 1
require 'uri'
CHECK_OK = 'OK'
CHECK_NG = 'NG'
list_file = '/usr/local/etc/referer_list'
referer_list = []
File::open(list_file) do |f|
while line = f.gets
referer_list << line.chomp if line.chomp[0, 1] != "#"
end
end
while true
flag = nil
buffer = $stdin.gets
referer = URI.encode(buffer.strip)
if referer.empty?
flag = true
else
begin
uri = URI.parse(referer)
flag = referer_list.any? {|v| uri.host.include?(v)} if uri.host
rescue Exception
#
end
end
$stdout.puts(flag ? CHECK_OK : CHECK_NG)
end

referer_check.rbには実行権限が必要ですので注意してください。リストファイルは、一行ずつ許可ドメイン(ホスト)を記述します。

example.com
example.co.jp

許可するドメインが増えたら、リストを更新するだけで対応できるので、ちょっと簡単?になった気がします。今回は許可ドメインということでしたが、同じように拒否ドメインリストで対応ってことも可能だと思います。

LighttpdのFD_SETSIZE【続】

以前の記事で以下のようなエラーがでるということを書きましたが、ちょっと嘘ついてたみたいというか、適当すぎたのでよく調べてみました。

2009-08-31 14:05:37: (server.c.1337) [note] sockets enabled again
2009-08-31 14:07:16: (server.c.1383) [note] sockets disabled, connection limit reached

FD_SETSIZEを変更してビルドし直したのですが、またエラーが出るようになりました。色々調べていると、server.max-connectionsなるものがあるらしい。こいつにはまったく手をつけていませんでした。以下のように設定を変更。

# max-fdsは、max-connectionsの2倍以上の値が必要
server.max-fds = 8192
server.max-connections = 4096

max-connectionsは、未設定の場合、(max-fds / 3)の値が適用されるようです。ということは1365くらいになってたってことか。

それでよくよくソースコードを見てみると、今回の場合は、そもそもがFD_SETSIZEを変更してビルドする必要はなかったようです。
event-handlerという設定があるのですが、こちらはデフォルトで「0」が設定されているようなので、以下の条件に一致します。

        if (srv->srvconf.event_handler->used == 0) {
/* choose a good default
*
* the event_handler list is sorted by 'goodness'
* taking the first available should be the best solution
*/
srv->event_handler = event_handlers[0].et;
if (FDEVENT_HANDLER_UNSET == srv->event_handler) {
log_error_write(srv, __FILE__, __LINE__, "s",
"sorry, there is no event handler for this system");
return -1;
}
} else {

event_handlersの最初の値が使われるようです。

        struct ev_map { fdevent_handler_t et; const char *name; } event_handlers[] =
{
/* - poll is most reliable
* - select works everywhere
* - linux-* are experimental
*/
#ifdef USE_POLL
{ FDEVENT_HANDLER_POLL,           "poll" },
#endif
#ifdef USE_SELECT
{ FDEVENT_HANDLER_SELECT,         "select" },
#endif
#ifdef USE_LINUX_EPOLL
{ FDEVENT_HANDLER_LINUX_SYSEPOLL, "linux-sysepoll" },
#endif
#ifdef USE_LINUX_SIGIO
{ FDEVENT_HANDLER_LINUX_RTSIG,    "linux-rtsig" },
#endif
#ifdef USE_SOLARIS_DEVPOLL
{ FDEVENT_HANDLER_SOLARIS_DEVPOLL,"solaris-devpoll" },
#endif
#ifdef USE_FREEBSD_KQUEUE
{ FDEVENT_HANDLER_FREEBSD_KQUEUE, "freebsd-kqueue" },
{ FDEVENT_HANDLER_FREEBSD_KQUEUE, "kqueue" },
#endif
{ FDEVENT_HANDLER_UNSET,          NULL }
}; 

Linux環境だったので、poll、select、linux-sysepoll、linux-rtsigから選択できる感じなのでしょうか?デフォルトでは、pollが使用されるようですが、カーネルが2.6以上の場合は、epollを使うと効率的らしいです。
Server.event-handlerDetails – Lighttpd – lighty labs
ということは、デフォルトでpollを使っているならば、初期値は以下の4096が適用される感じになるはず。

        if (srv->event_handler == FDEVENT_HANDLER_SELECT) {
/* select limits itself
*
* as it is a hard limit and will lead to a segfault we add some safety
* */
srv->max_fds = FD_SETSIZE - 200;
} else {
srv->max_fds = 4096;
}

さらに、max-fdsを設定しているならば、以下のように設定された値がそのまま使われるようです。

                if (use_rlimit && srv->srvconf.max_fds) {
/* set rlimits */
rlim.rlim_cur = srv->srvconf.max_fds;
rlim.rlim_max = srv->srvconf.max_fds;
if (0 != setrlimit(RLIMIT_NOFILE, &rlim)) {
log_error_write(srv, __FILE__, __LINE__,
"ss", "couldn't set 'max filedescriptors'",
strerror(errno));
return -1;
}
}
if (srv->event_handler == FDEVENT_HANDLER_SELECT) {
srv->max_fds = rlim.rlim_cur < FD_SETSIZE - 200 ? rlim.rlim_cur : FD_SETSIZE - 200;
} else {
srv->max_fds = rlim.rlim_cur;
}

ということなので、event-handlerを特に設定していない状態で、selectを指定していないのであれば、FD_SETSIZEを変更して、ビルドし直す必要はなかった・・・でした。
あとは、max-workerなるものもあるようで、負荷が増えてきたらこいつも設定してあげるといいのじゃろか。

JavaScriptのラジオボタンでのOnChangeイベントの挙動

Railsでは簡単にAjaxを使えるような仕組みとして、RJSなど便利ものがたくさんあるので、最近よく使ってみたりしています。

ちょっとアレ?と思ったのは、ラジオボタンのOnChangeイベントの挙動についてです。IEとFireFoxでどうやら違うみたい。IEの場合は、フォーカスを失った時点でイベントが発生しますが、FireFoxではフォーカスが移った時点でイベントが発生するようです。
正確には、値が変更された後ってことなのかな?

JavaScriptを使ってる人にとっては、かなり周知のことだと思うのですが・・・。OnBlurイベントというのがあるのならば、IEの挙動は微妙な気がしますが、どうなんじゃろか。

とりあえず、動きを統一したいならば、OnClickイベントを使えということらしいです。

参考
HTML ラジオボタンのonchangeイベントのブラウザごとの挙動について
にゃほ~ – IEにおけるラジオボタンのonchange

Railsでmixiアプリを作ってみました

mixiアプリってなんじゃろ・・・と思いつつ、調べてみたらOpenSocialというコンテナを使って、mixiの中で自分の作ったアプリが動くらしい。
何やら面白そうだったので、Railsの勉強がてらに作ってみました。

あしあとプラス
ソーシャル・ネットワーキング サービス [mixi(ミクシィ)]
あしあとの履歴を長期間とったり、ユーザが消したあしあとも見れちゃうアプリです。誰かが10人使ってくれればカテゴリに登録できるみたいです。
内容が内容なので審査を通るのかあやしいですが・・・。

携帯開発環境をMoxyからSSBに

今までの開発環境はMoxyを利用させて頂いていたのですが、OSを入れ直したついでにMoxyを新しくしようとしたところ、どうしてもうまく動かすことができませんでした。古いのでいいかと思ったのですが、SSBというのが目につきました。

どうやらこいつは、Rubyで出来ている携帯開発環境(ブラウザ?)らしい。インストールもあっさりできてたので、こっちを使って見ることにしました。どうもRailsで出来ているのかと思ったら違うみたい・・・。インタフェースも今風です。

SSBのダウンロード先

ssb –
CodeRepos::Share – Trac

早速、動かしてみたのですが一つだけハマりました。PHPのNet_UserAgent_Mobileを使っていたので、初期の設定で以下のようなエラーがでました。

DoCoMo/2.0 P905i(c100;TB;ser000000000000000;icc0000000000000000): might be new variants. Please contact the author of Net_UserAgent_Mobile!

ソースを確認したところ、マッチするUserAgentがないとエラーになるようです。ここで引っかかっています。

if (preg_match('/^icc(\w{20})?$/', $value, $matches)) {

どうやら、初期の設定だとiccが15桁なので20桁に変更してあげれば無事動きました。

LighttpdのFD_SETSIZE

Lighttpdを利用していて、エラーログに以下のようなエラーメッセージが表示されるようになりました。

2009-08-31 14:05:37: (server.c.1337) [note] sockets enabled again
2009-08-31 14:07:16: (server.c.1383) [note] sockets disabled, connection limit reached

調べてみるとFD_SETSIZEを変更してビルドし直せばいいらしい。ちなみにFD_SETSIZEとは、ファイルディスクリプタのデフォルトサイズで、ファイルを入出力したりソケットを開いたりすると増えていって、この設定したサイズを上回るとエラーになるようです。

以下のようにビルドし直して、様子を見てみることにしました。

$ CFLAGS="-DFD_SETSIZE=4096" ./configure && make
# make install

参考
高負荷、大量アクセスなサイトで Lighttpd を使う場合の注意点 :: drk7jp

続編記事
LighttpdのFD_SETSIZE【続】 – フタなしカンヅメ