一般的な更新チェッカ――良きWWWDであれ、WWWCであれ、ウェブ上のアンテナであれ――に捕捉されないサイレント更新は比較的容易だ。しかし、ブラウザからの If-Modified-Since リクエストを自力で適切に扱うことは比較的めんどうくさい。このメモでは、両方について説明する。
クライアント側からみると更新チェッカは更新されているサイトを効率良くまわるためのものだが、サーバ側には別の意味がある。前にも書いたが、「更新されてないのに(つまり、あなたのローカルにすでに同じキャッシュがあるのに)再度 GET されるのは、あまりありがたくない」。いちいち数十ないし数百キロバイトのHTMLファイル全文をブラウザで取得せずとも、HTTPレスポンスヘッダだけで――すなわち、HEAD リクエストを送ってもらえれば――実体を1バイトも送らずに、更新されたかどうかお知らせできるからだ。
このことは、大手プロバイダにホストされているこじんまりとしたサイトなどでは、事実上、問題にならないだろう(むしろ何度もリロードされるとカウンターの数値が増えてうれしい、という考えもあるかもしれない)。けれど、いまどきのページは、動的に生成されることが多い。特にファイルが千、二千、あるいは万単位であるようなサイトでは、完全にスタティックに管理するのは、まず無理だろう。概念的にいうと、ブラウザからは小さなHTMLファイルにしか見えなくても、裏方では、アーカイブ(データベース)から最近の記事だけを読み出して、相対リンクを適切に書き換えたうえで、トップページを構築する、といった、けっこうややこしい処理が行われていることが多い(特にフローティングスレッドな掲示板とか)。
このように動的に作られているページは、つねにリクエストされた瞬間に新たに作られるのだから、見るたびに異なる可能性があって、素朴にいえば「最終更新時刻」という概念がない。実際、デフォルトではアパッチは Last-Modified ヘッダを返さない(ので、つねに全文GETされる結果になる)。しかし、アパッチには XBitHack というハックがあるので、SSIなページに Last-Modified をつけることは簡単だ。CGIな掲示板でも、新規書き込みがないかぎり、自分で更新されてないと言って良い。PHP でも同様だ。
表紙を index.cgi にしているかたが「5分おきにWWWCをかけるのは、やめてください」と書いているのをみたことがあるが、これなども、Last-Modified をつけないのでいちいちぜんぶの読み込みファイルがオープンされて同じ料理を何度も作らされるハメになるのだろう。NPHスクリプトを使わなくても、Content-Type: Text/html のあたりに Last-Modified: Fri, 07 Sep 2001 04:07:00 GMT のような、RFC1123 のスタンプを入れておけば、だいたいうまくいく。
ひるがえって、ページを生成するとなると、けっこう時間がかかる。複雑な処理が入ると、1ページの生成に0.1秒オーダのコストがかかることもある。サーバでは複数のプロセスを平行的に処理できるとは言うものの、典型的な環境では、ちいさなサーバでも、毎秒、数十から百単位のリクエストにこたえなければいけないのであって、0.1秒といえば、ばくだいなコストなのだ。転送量のほうは、大したことなくても、0.1秒も排他処理をしていると、アクセスがたてこんだときにどんどん待ち行列が伸びて、ロックエラーが起きたりするだろう。
このような場合、自分で自分をキャッシュする(静的なHTMLに書き出す)とか Last-Modified をつけるか、少なくとも、なんの手間もかからない XBitHack の使用は検討に値する。このへんの話はウェブ上を検索すればいくらでも情報が得られる。
それにしても、ちょっと誤字をなおすとかのつまらないことで、「更新」と認識されてしまう。この場合、更新チェッカ等の報告で「あ!更新された」とよろこんで来てくださる訪問者の期待を裏切ることになるばかりか、上記のように、サーバリソースの無駄遣いにもつながる。とは言うものの、ウェブは気づいたそばからどんどん書き足し書き換えるところ、つまり動的なところに大きな意味がある。現時点では、まだ過渡期で、サーバリソースも帯域も不足気味なので、こんなつまらないことで悩まざるを得ないのだろう。
ともあれ、HTTPレスポンスヘッダを自分で作ってしまえば、更新チェッカの一般的なスキャンに「更新」が映らないようにすることもできる。
まず、更新チェッカ側もラクをしたいので、HEAD リクエストで Last-Modified が返れば、それを採用するのがふつうだ。これを「ごまかす」のは容易だ。
少し難しいのは、GET リクエストに if-modified-since がくっついてきた場合で、304 Not Modified とこたえてヘッダだけでうち切ってしまえばステレス自体は簡単だが、そうやってサーバからコントロールをうばったからには、自分で後始末をしないといけない。つまり、更新されたと認識されてもいい更新のときは、200 OK でファイルを転送すべきなのだが、その場合、いつも 200 OK と本文を送り返しては本末転倒だろう―― if-modified-since (わたしは、これこれの時点のキャッシュを持っています。それ以降に更新されていたら送ってください)と向こうからクールなリクエスト(お互いラクしましょう)をしてるのに、つねに全文転送するのは、いかにもよろしくないし、本来の趣旨にも反している。
通常、サーバが if-modified-since を解釈してうまく制御してくれるわけだが、その制御を乗っ取る以上(NPH にしてヘッダをこちらでコントロール)、なるべくサーバがやるのと同じに見えるように、スクリプト側でエミュレートしなければいけない。
この場合、ブラウザは(技術的には少しややこしいが実際のところ)通常は rfc1123 形式のスタンプを送ってくる。こちらも、Last-Modified ヘッダ用の同じ形式のスタンプが手元にある。問題は、
Fri, 07 Sep 2001 04:07:00 GMT
Sun, 26 Aug 2001 09:55:59 GMT
のような2つのヘッダを比較して、未来になっているかどうかの判定だ。
がむしゃらにやれば判定できないことは、ないが、とりあえず次のように考えるのが賢明だろう。すなわち、ブラウザが持っているキャッシュと手元のタイムスタンプが一致するなら、サーバとクライアントのコンテンツは同期しているのだから、更新されてないと判定して良いだろう。次に、両者が一致しない場合は、いちおう更新されていると判断してかまわない。サーバ側より未来のキャッシュがクライアント側にあるわけないので、時間が一致しないとしたら、サーバ側のほうが未来、と考えるわけだ。
天文学というか暦学の「ユリウス日」を使うと、もっとちゃんと判定することもできる。このような判定が必要なのは、if-modified-since の基準時点として、キャッシュのスタンプの代わりに「前回おれさまがそこを訪問したのは、いついつだが、あれから更新されとるかい」という感じで時刻を送られた場合で、この場合、ずっと更新していなければ、相手の時刻のほうが最終更新より未来という形で不一致になるので、それを認識できなければならない。
sub parse_rfc1123() { local($_) = $_[0]; my %MONTH = ( 'Jan'=>1, 'Feb'=>2, 'Mar'=>3, 'Apr'=>4, 'May'=>5, 'Jun'=>6, 'Jul'=>7, 'Aug'=>8, 'Sep'=>9, 'Oct'=>10, 'Nov'=>11, 'Dec'=>12 ); my($D, $M, $Y, $h, $m, $s) = (split(/[ |:]/))[1..6]; $M=$MONTH{$M}; return (0) unless $M; my($_Y, $_M, $_D); if( $M==1 || $M==2 ) { $_Y = $Y-1; $_M = $M + 12; } else { $_Y = $Y, $_M = $M; } $_D = $D + $h/24 + $m/1440 + $s/86400; my $A = int($Y/100); my $B = 2 - $A + int($A/4); return ( int(365.25*($_Y+4716)) + int(30.6001*($_M+1)) + $_D + $B - 1524.5, $Y, $M, $D, $h, $m, $s ); }
このコードは位置天文学の基本中の基本とも言える「JD公式」を使ってRFC1123形式のタイムスタンプを通算の数値に変換し、長さ7の配列を返す(これはスケルトン。実装ではパラメータの正当性をチェックしたほうが良い)。どこまでもウェブに迷惑をおよぼすネスケ4の
Fri, 07 Sep 2001 04:07:00 GMT; length=28527
といった変なリクエストヘッダも扱えるロジックのハズだ。
返る配列の一番めは小数点以下を含むユリウス日で、UNIXエポックの限界(1970年)を超えて1582年までさかのぼれるので、工夫しだいでほかの用途にも使える。未定義値を渡されると0を返す。2番め以下は、年、月、日、時、分、秒で、ここでは使わないが、同じ手間なので返しておく。そんなわけで、
my ($your_jd) = &parse_rfc1123( $ENV{'HTTP_IF_MODIFIED_SINCE'} ); my $my_jd = 0; if($your_jd) { ($my_jd) = &parse_rfc1123( $last_modified ); } my $status = ($your_jd && $my_jd <= $your_jd )? "304 Not Modified" : "200 OK"; print "$proto $status\r\n"; print "Date: $date\r\n"; print "Server: $ENV{'SERVER_SOFTWARE'}\r\n"; print "Last-Modified: $last_modified\r\n"; # ほか必要なヘッダを入れる print "Connection: close\r\n"; print "Content-Type: text/html\r\n"; print "\r\n";
‥‥のようにすれば、いちおう「正しい」反応ができる。そのうえで、場合によっては更新されているけれど304を返して接続を切る、というのもお互いのためだろう。
帯域やリソースに比べてアクセスの多めなサイト(言い換えれば、アクセスに比べてリソースが不足気味なサイト)では、「更新」と認識されたとたん、多くのかたがいっせいに更新チェッカをクリックしてブラウザにロードすることによるトラフィックの山が無視できないと思われる。実際におもしろい記事をうちあげたのならさっそく読んでもらうのは良いことかもしれないが、ほんのちょっと引用符が抜けてたのを修正したとかで同じようなアクセス集中があると、サーバ側に負担になるのはもとより、せっかく見に来ても「あれれ、どこが更新されたんだ?」というつまらない思いをすることになってしまうであろう。
準備中のファイルを、アップロードしていてもパーミションを落として見えなくしておくことは広く行われている。同様に、更新とは言えないような更新は更新チェッカから見えなくするのは、あるいは許されるのでは、ないか。もちろん、その場合でも、前回の「更新と言えるような更新」より古いキャッシュを持っている人が if modified と言ってきたら「更新されてますよ~」と最新コンテンツを送り返すのは当然であるが、相手が最新版と実質的に変わらない直近のものをキャッシュしている場合には「変わってない」ことにするのも、ある意味、お互いのためでは、ないだろうか。とりわけ、毎日何度も定期チェックをかけてくる相手だと分かっていれば、次にちゃんとした更新があったときにまとめて新規取得してもらえばいい、という考えもありえよう。
なお、このメモの発想は、いつものように版権フリーですが、逆の意味で乱用したりするとあなたのサイトの訪問者やウェブが混乱する原因にもなりえるので、充分にご注意ください。ここに書いたようにしなければならない、するべきだ、という意味でなく、単にこういうこともやればできる、という話であって、予期可能な、あるいは予期せぬネガティブな副作用がありえることにご注意ください。
初期状態は、こんな感じであろう。持ち主に隠し事をしつつ、1 個へそくってますと告白するとは、ういやつめ。
ひみつの部分をもっと見たい‥‥日本の恥部は国会議事堂。そんなあなた! 物事の見え方を変えるには「表示」メニューをいじくるのが宇宙の真理と言えよう。
「表示」メニューのフォルダオプションのなかに、さらに「表示」タブがあって、ひだが深いが、こういうところをいじくって何が起きるか試すのが良いハッカーへの第一歩と言えよう。「表示しない」などというイケズなチェックを外したら、さらに下のほうに手を伸ばし、「すべてのファイルを表示」させよう。You've got to help yourself...
「すべて大文字の名前を使用する」にもついでにチェックを入れておこう。この項目は Windows 2000 には、ない。ふしぎなことだが、Windows 95 の初期状態では「ABC.TXT」というファイルを作ると「ABC.TXT」と表示されないのだ。
「.css」「.gif」「.exe」とかの拡張子(かくちょうし)が表示され、隠れていた cw3220.dll も姿をあらわした。これでそこらの記事と話があうでしょう‥‥。
なお、Windows 2000 の「フォルダオプション」はコントロールパネル内にあります。
「個人が出版社や新聞社と対等の情報発信源になれるのだ」などと言われて、従来のメディアのようなものにならなければいけないのだと無意識に仮定する人々も多いかもしれない。
当初は――それでものを考える習慣がついてしまっているなどの理由で――多くの数が「支持」しなければ成り立たない従来のマスベース、採算ベース、商業ベースでしか「価値」を測れない人々もいるかもしれない。
しかし、ごく少数の者しか良いと思わないような、つまり、ほとんどの人々が否定するような、「つまらない」ものが、等しくパブリックに(求める者には見えるところに)存在しうる、という「価値」もある。「みんな」がおもしろいと思うものは「みんな」自身が作ればいい。それは悪くないが、それがすべてでもない。必ずしも数の論理に従って自分を捨てる必要は、ない――そうしては、いけないということでは決してないが。
見られる君であらねばならぬ日々をうしろに、ついにただ見る君であるために。さらには見るとも見られるとも願わずただ流れる水、渡る鳥や風のようにあるために。
カウンターを捨てて、「あなた」に帰ろう――君の、そして君だけのふるさとである、名づけられぬ国へ。そこでこそ、そこでだけ出会えるものと、者たちのために。
多数決で良いと決めるより、みんなにほめてもらうことを目的にするより、少数決で良いと決めてみたら、どうなるだろう。むしろ“個人決”でいいのだから‥‥。
たぶん多くの人々は、じつは多くの人々が良いと思う「共通」なものだけではないものと出会いたいと思っているし、それを「自分自身」と呼びもするかもしれない。
本当に本当につまらない、みんなが否決するような、でたらめで、くだらないものをなすのは簡単だ、と思うかもしれない。けれど、「でたらめ」なものは、たいてい、あなたがたのコチコチの価値観の裏返しをやっているだけで、ひわいなことばを叫ぶのが良くないと言われているから、叫んでみる程度だろう。社会のタブーを守ることも社会のタブーに挑戦することも、同じコインの裏表にすぎない。「みんなが否定する」とは「みんなが良いと思う」というキソクに逆向きに従っているにすぎない。ちょうど性転換者が、だれよりも性別にしばられているように。それも悪くないが、それだけでもない。
そして、ウナギイヌやレレレのおじさんを描いた人の机のうえに『火の鳥』がのっている写真をみて、ぎょっとしたりするのかもしれない。
あるいは「ぐしゃまら」を謙遜(けんそん)のことばだと思ったり、「天才」を尊大なことばだと思ったりするのかもしれない。
リモートSSTPとは、ネット越しにあなたのデスクトップの何かをしゃべらせたりする方法のことです。以下の実験を行うには、何かが必要です。さもないと、必ず「接続に失敗」と言われます。また、何かが動いている環境でも、リモートから実IPのポート9801が見えない場合は失敗します(インターネット環境しだいなので、ある程度、やってみないと分からない)。
TinkerBell はプラグインでなく単なる JavaScript ですので、サーバサイドで、アップロードする HTMLファイルのソースに直接、うめこむことで、リモートSSTPを実行できます。発想的には、PHPやASPやColdFusionにも似た「HTMLタグベースのSSTP」です。→概念図
TinkerBell/0.3 は、HTMLページをブラウザがロードすることで自動実行される「通常」の(スタティックな)リモートSSTPのほか、クリックなどのイベントによって駆動されるダイナミックSSTPをサポートします。また、ランダム性をサポートする予定です。ネット上に「ミニゴースト」を構築して、リモートのシェルに憑依することができます。
TinkerBell/0.31 は、MagicQuote をサポートします。「¥¥n、¥¥s[数字]、¥¥w数字一桁」の3パターンに関しては、¥¥を単に¥とタイプしても自動的に補完する機能です。¥¥と二重打ちしても同じ意味になります。
<script type="text/javascript"><!-- Self("¥s[7]こんにちは!"); Kero("¥s[10]何、怒ってるの?¥w8¥w8‥‥ねえ?"); Self("¥n¥n¥s[0]なんでもない!"); TinkerMagic();//--></script>
このように、Self() と Kero() を任意の順序で任意回呼び出すことで、スクリプトはバッファに蓄積されます。関数の戻り値は、その時点でのバッファの内容です。TinkerMagic() を呼び出すと、バッファはCGI側にフラッシュされて、SSTPが起動されます。
sstp() は、呼び出された時点で、その引数をCGIに渡すので、イベント駆動型の関数としてべんりです。
<a href="#" onclick="sstp('¥¥h¥¥s[0]こんにちは、ティンク!¥¥uそればっかりや');return false;"> クリックすると実行</a>
<a href="#" onclick="h('¥s[7]ぷんぷん。¥n¥n許せない!'); u('へらへら');h('¥n¥n‥‥¥w8へらへらするなっ!'); u('¥n¥nえんいー。');e();return false;">ためす</a>
見ての通りです。h() と u() は内部的にバッファにつけくわえるだけで、それだけでは何もしません。e() を呼んだ時点で、バッファの内容がSSTPされます。
onclick の属性値は、実際には改行を入れずに一行で書いてください。
http://www.faireal.net/software/nanika/tb031.htm