プログラムの変数名を考えるアドバイスツール

プログラムを書いていて、秀逸な変数名を考えるのは難しいなとつくづく思うんですが、コンセプトが素敵なサービスを見つけました。

デベロッパーのためのネーミング辞書」
http://codic.jp/

例えば「登録」とか入力すると以下のように変数名がサジェストされます。

マンパワーで辞書データ作っているようなので、まだまだ改善余地というかデータの充実余地があるのですが、生産性と品質に寄与する素敵サービスだなぁと思いました。

ターミナル終了後もプロセスを継続させたいとき

ターミナル終了後もプロセスが継続されるようにしたい場合、2つ方法があると知ったので覚書。

事前にターミナル終了することが分かっているとき

あらかじめターミナル終了することを分かってコマンド実行するときは、nohupをつけてコマンドを実行すればOK

$ nohup コマンド &

これで、ターミナルを閉じてもプロセスは継続される。nohupについて詳しくは、以下ページなどを参照。
http://itpro.nikkeibp.co.jp/article/COLUMN/20060227/230850/

あとになってターミナル終了しないといけないことが分かった時

実行完了まで長い時間がかかるプロセスを動かしている最中に、何らかの理由でターミナルを終了しないといけなくなったようなときは、プロセスをバックグラウンド実行に切り替えてからdisownすればOK

$ Ctrl + z
$ jobs
$ bg %ジョブ番号
$ disown %ジョブ番号

Starletメモ

mod_perlで動かしてるコードをStarletで動くようにして、
それぞれworkerの数を10にしてab -c 10 -t 1したら、
StarletのほうがRequests per secondが3くらい多かった。

Percentage of the requests served within a certain time (ms) の結果が

ってなってるんだけど、これは半数(50%)のリクエストにおいて、
32ms程度はレスポンス速度が高速化されてるという認識でいいのかな。

HTML返すのに100msくらい高速化できたら、
mod_expiresとの上手い組み合わせでスマホとかの体感速度変わりそう。

ModPerl::RegistryとModPerl::PerlRunの違いを実現してる実装を読んだ

mod_perl*1を使う際、PerlResponseHandler*2に、
ModPerl::RegistoryとModPerl::PerlRunのどちらを使うか選択肢が発生します*3

ModPerl::RegistryとModPerl::PerlRunの違い

両者における明確な違いは以下の2点です。

これを検証するために、BEGINやCHECK、INITブロックにprint文を入れ込んで、
ModPerl::RegistryとModPerl::PerlRunで挙動がどう異なるのか調べる作業を
やったことがある方もいらっしゃるかと思います。

かく言う自分も「mod_perlの実験(2) - Perlプログラムのライフサイクル」を読みながら、
ハンドラーの違いで評価の作法がどう異なるのかを検証をしたことがあります。

で、ModPerl::RegistryとModPerl::PerlRunのこれらの挙動の違いは、
XSレベルで実現されているのだとずっと思っていたのですが、
意外にもPerlレベルで、ModPerl::RegistryCookerというモジュール実装されていました。

この土日にその辺のことを勉強したので、以下で説明してみます。

違いを実現している実装部

まず、ModPerl::RegistryとModPerl::PerlRunは、
ModPerl::RegistryCookerというモジュールを継承して出来ています。
ModPerl::RegistryとModPerl::PerlRunの中には実際、大した内容は書かれていなくて、
ほとんどの実装はModPerl::RegistryCookerが持っています。

ということで、以下ではほとんどModPerl::RegistryCookerの説明を書いていきます。

mod_perlではブラウザからスクリプトがリクエストされると、
ModPerl::RegistryCooker::convert_script_to_compiled_handlerの中で、
スクリプトのファイルパスを利用してユニークなパッケージ名を生成し、作ったパッケージ名で、
スクリプトの元処理を動的に以下のようにラップします*4。そして、それをevalします。

package ModPerl::ROOT::ModPerl::PerlRun::opt_local_apache2_htdocs_perl_test_2epl;

sub handler {
    local $0 = '/opt/local/apache2/htdocs/perl/perlrun.pl';

    #line 1 /opt/local/apache2/htdocs/perl/perlrun.pl
    #!/opt/local/bin/perl

    # 元々の処理がsub handler内に組み込まれる
    use strict;
    use warnings;
    use CGI;
    ・
    ・
    ・
}

さて上記の処理で、リクエストされたスクリプト
packageとして評価(コンパイル)されたことになります。

冒頭で書いたModPerl::RegistryとModPerl::PerlRunの違いは
この評価処理を1度のみとするか、リクエストの度に行うかの違いです。

2回目以降のリクエストを処理する際に、再び評価(コンパイル)すべきなのかどうか分岐する処理は、
ModPerl::RegistryCooker::default_handler()の冒頭で行われます。

具体的には、if($self->should_compile)という箇所なのですが、
ModPerl::PerlRunではこのif文が絶対に真になるように実装されていました。

ModPerl::Registryでは、ModPerl::Registry::cache_tableという、
評価済か否かの情報を保持する機構を用いて、
評価済であれば偽に、未評価であれば真になる仕組みになっていました。

sub default_handler {
    my $self = shift;

    $self->make_namespace;

    # ModPerl::PerlRunでは、絶対このif文で真になる
    if ($self->should_compile) {
        #
        # うまい説明の仕方が分からないのですが、
        # $self->should_compileにはコードリファレンスがあてがわれていて、
        # ModPerl::PerlRunでは、use constant TRUE => 1の結果が返ります。
        #
        my $rc = $self->can_compile;
        return $rc unless $rc == Apache2::Const::OK;
        $rc = $self->convert_script_to_compiled_handler;
        return $rc unless $rc == Apache2::Const::OK;
    }
# 以下省略

以上、ModPerl::RegistryとModPerl::PerlRunの評価作法の違いは、if分岐のコントロールによって成り立っていました。

余談になりますが、一般にはModPerl::RegistryとModPerl::PerlRunでは、
速度はModPerl::Registryのほうが速いと言われます。
この速度差は、前述したevalが毎回発生するかどうかのコスト差によるもののようです。

最後に

冒頭に記載しなかったのですが、実はもうひとつ、実行フェーズで行われた
requireの扱いもModPerl::RegistryとModPerl::PerlRunで異なっていて、
それも土日に勉強したのですがそれはまた別エントリで書こうと思います。

*1:ちなみにmod_perl2です

*2:ApacheのResponseフェーズで呼び出されるPerlモジュールを指定するディレクティブ

*3:実際には他にも選択肢はあるし、自分でオリジナルのハンドラを実装することも出来ます

*4:/opt/local/apache2/htdocs/perl/に置かれたtest.plというスクリプトで行った場合の例です

iモードにおけるgmailのセッション管理の検証(その1)


(2013.06.17追記) このエントリの考察は間違ってるので参考にしないでください。

先週の土曜日に書いたエントリ(未検証なので信憑性はないが、iモードにおけるgmailのセッション管理を解析してみた)の続き。ちょっと実験してみました。分かったところをご報告。

1. リバースプロキシ(*1)環境ではSSL_SESSION_IDが環境変数で取得できない

試してみて分かったことですが、リバースプロキシごしではSSL_SESSION_IDが取得できませんでした。理由はmod_sslがリバースプロキシ上で動いているため、背後のWebサーバにまでSSL_SESSION_IDが渡ってこないからです。

参考に、次のような情報があります。
[Apache-Users] リバースプロキシ環境で環境変数を渡すには 2002/2/12(*2)

(*1) プロの棋士が打った手を「あ、あ、すいません。今のなしでお願いします」というリバースプロ棋士とは違うので注意。
(*2) 宮川さんが登場してる。かっこいーなー。

2. googleはどうやってんの?

できない、という話ではなく手段はいくつかある様子です。googleは上の参考情報で言われているように、URLをRewriteしてSSL_SESSION_IDをつけてWebサーバに引き渡しているのではないでしょうか。


ちなみに、いちばん簡単なのはRewriteの際にURL内にユーザ名を埋め込むことではないでしょうか。
例えば、TEST_USERというユーザなら

http://WebServer.mydomain.com/$1?TEST_USER

というリクエストがWebServerに飛ぶように。

[Apache-Users 990] Re: リバースプロキシ環境で環境変数を渡すには」より引用

もしくは、Webサーバに環境変数SSL_SESSSION_IDを引き渡すモジュールを自作してるのかもしれません。余談的ですがオラクルの製品には、リバースプロキシ環境でもSSL_SESSION_IDを取得できるようにするモジュールが含まれているようです。


mod_certheaders

Oracle HTTP Serverの前でSSL接続が終了するリバース・プロキシ(OracleAS Web Cacheなど)が、
SSLクライアント証明書情報などのSSL接続に関する情報を、Oracle HTTP Serverおよび
Oracle HTTP Serverの背後で動作しているアプリケーションに送信できるようにします。
この情報は、HTTPヘッダーを使用してリバース・プロキシから Oracle HTTP Serverに送信されます。
情報はヘッダーから標準CGI環境変数に送信されます。SSL接続がOracle HTTP Serverによって
終了する場合はmod_osslまたはmod_sslがこの環境変数を移入します。これはOracleモジュールです。

モジュールの理解」より引用

[Apache-Users] リバースプロキシ環境で環境変数を渡すには 2002/2/12では、'mod_proxy_add_forward.c'という既存のモジュールを参考にして、リバースプロキシから環境変数を引き渡せるモジュールを組んでみては?的な話もされています。参考になる実装があるのであれば、自作もそんなに難しくはなさそうですよね。

3. 気になること

参考にした情報がけっこう古く(2002年)Apache1.3系をベースに話しているので、もしかするとApache2系だと簡単に取得する手段があったりしないか気になります。もしくは、用途を満たす安定したモジュールを誰かが既に開発済みとか。小一時間ググってた感じでは特に見つからなかったのですが、スマートな方法が既にある気がします。

どなたか、リバースプロキシから環境変数をバックエンドのWebサーバに引き渡す方法をご存知の方いらっしゃいましたら教えてください。

未検証なので信憑性はないが、iモードにおけるgmailのセッション管理を解析してみた

1. iモードにおけるSSL事情
iモードSSL通信中、下記のどの方法でもユーザーを一意に識別する情報を取得する事ができません。

・utn
・NULLGWDOCOMO
・guid=ON
cookie(iモードは未対応)

そのため、iモードSSL通信中にユーザーを一意に識別したい場合(*1)はURLにsession_id=*****というようなセッション情報を付加して引き回さないといけません。しかし、そうするとセッション情報が付加された状態のURLが何らかの理由で他人の手に渡った場合(*2)、セッションハイジャックの恐れが出てきます。

*1 ECサイトの決済画面など
*2 ユーザーがメールで他人に送ってしまったり、通信が盗聴されたり...


2. gmailの不思議
まず先に結論を述べておくと、iモードgmailを閲覧した時のSSL通信中のURLを、他の端末に転送してもセッションハイジャックはできません。強制的にログアウトされてしまいます。

1で述べたように、iモードではSSL通信中はURLにセッション情報を付加する以外の方法では、ユーザーを一意に識別する事はできません。それにもかかわらず、URLを盗んだセッションハイジャックが不可能ということは、何かしらの方法でリクエストを送ってきた端末がセッション情報の正当な持ち主かどうか検証できる手段を講じていることが伺えます。

gmailは携帯電話からでも閲覧ができるようにマルチプラットフォーム対応されており、私が持っているiモード端末でもメールの確認をすることができます。gmailでは全ての画面においてSSLが適用されており、gmailのログイン後のURLを見ると、何やら複雑なランダム文字列が並んでいて、長さは256バイトにも及んでいます。

私は最初、携帯サイトにしては異常な長さのURLにしておくことで、ユーザーがメールで送信することを防ごうとしているのかと思いました。長さにかかわらず、ログイン後のURLを別端末に送信したらセッションハイジャックできてしまうのだろうと思い、別端末にURLをメールで送りセッションハイジャックの実験を行ってみました。しかし、予想外にも別端末でアクセスすると強制的にログアウトされてしまい、URLを盗み出してもセッションハイジャックできないということが判明したのです。


3. ユーザーを一意に識別している手段の考察
SSL通信中にユーザーから得られる情報と言えば、UserAgentの情報くらいしかありません。私は、URLの中に端末の機種情報が含まれていて、セッションハイジャックしようとした端末の機種が異なっていたら強制ログアウトになるのだろうかと考えました。しかし、それだと同じ端末であればセッションハイジャックできてしまうことになるので少しお粗末な感じです。

とは言え、念のために実験を行ってみました。ログイン後のURLをメールで自分宛に送信して、メールに記載されたURLに接続するという実験です。行った結果、予想通り強制ログアウトされました。UserAgentの情報を使ってごにょごにょというのはやっていないようだということが分かりました。

やってみたあとで気づいたのですが、実はこの実験でとても重要な事実に気づく事が出来ました。同じ機種の端末であってもセッションハイジャックできないことが証明されただけでなく、本人の端末でもログアウトになるということから、端末を一意に識別はしていない、ということが判明したからです。

補足:iモードでは「Internet」->「URL入力」とメニューをたどってURLを直接指定してアクセスする方法があります。メールへの転送だけではなく、ログイン後のURLを直接叩いてページを開く場合も強制ログアウトになります。


4. 仮説
端末を一意に識別しているのではないということが分かりました。となると、何の情報を使ってURLをリクエストした端末が、URLの正当な持ち主かどうかを検証しているのでしょうか?

自分のURLを自分宛にメールで送るという実験の一件から、私は何となくSSL通信が怪しいと目をつけました。SSLでは鍵の交換が完了した後に暗号化された通信路が確立されますが、リクエストの度にいちいち鍵交換をやっていなさそうだと思い、鍵交換が完了したあとにはクライアントとサーバー間でステートフルな状況があるのではないかと考えました。ステートフルな状況、それはユーザーを一意に識別する事が可能な状態だと言えます。

調べて行くと、案の定そういった情報を見つける事が出来ました。SSL通信には、SSLセッションIDというのがあって、暗号化された通信路を確立した以降は、確立するまでの同じ作業(鍵交換)を省略するため、サーバーとクライアント間でSSLセッションを保持するための情報を互いに持つようです。

gmailでは、ログイン時にSSLセッションIDとユーザー情報を紐付けているのではないでしょうか。
そうしておくことによって、クライアントから送信されたSSLセッションIDからユーザーを一意に識別する事ができ、リクエストされたURLを処理すべきか判断できます。逆に、送信されてきたSSLセッションIDが新規作成のもので、それにもかかわらずログイン後のURLがリクエストされていた場合は'あり得ない状況'として判断ができ、不正なアクセスとして強制ログアウトにできます。


5. perlの実装例(未検証)
未検証なのですが、言っている意味としては以下のような実装になると思います。mod_perl環境下での動作を想定しています。

CPANモジュールを漁って、SYNOPSYS見ただけなので動くかどうかも分かりませんが、イメージだけでも分かって頂けるとうれしいです。

#!/usr/bin/perl

use strict;
use warnings;
use Apache2::Access ();
use Apache::SSLLookup;

my $ssllookup = Apache::SSLLookup->new($r);
my $value = $ssllookup->lookup_var('SSL_SESSION_ID');

# $value の値をDBなどで引き、存在すればそこからユーザーを
# 特定する。ない場合は不正アクセスと見なして強制ログアウト。

(参考情報)
SSLセッションIDをperlコードで取得するためのモジュール
http://search.cpan.org/~geoff/Apache-SSLLookup-2.00_04/SSLLookup.pm

・mod_sslに'SSL_SESSION_ID'という環境変数(Envaironment Variables)があること
http://httpd.apache.org/docs/2.0/mod/mod_ssl.html

6. 最後に
正当な証明書をもったSSL通信可能なサーバーがないので、残念ながら上記の仮説は未検証です。いずれは自分で検証してみたいのですが、どなたか環境がある方が先に試してくださったりすると、うれしいです。

SSL通信時に端末を識別する事ができないiモードの仕様は、開発者にとっては苦しい問題でありますが、googleは何らかの手段でセキュアな実装を可能にしています。この仮説が正しければセキュアなサイトを作るための良いノウハウになるのではないかと思います。

一番うれしいのはSSL通信中でも、一意に端末を識別できる情報を取れる事ですが、iモード端末の後方互換性などを考えると面倒な時代はまだまだ続きそうです。