|
||
FeedBurnerにGoogle Analyticsと連携する機能が追加されて、FeedBurnerのアクセス解析機能を使っていた人が、自動的にこのオプションを利用するように設定変更されています。
http://blog.fkoji.com/2009/11141538.html
で、特になにも影響がないなら勝手に便利な機能は勝手に有効にしてくれれば良いと思うのですが、影響があります。
http://feedproxy.google.com/~r/*****/*******/hogehoge.html
というアクセス解析用のリダイレクトURLの飛び先が
http://example.com/blog/hogehoge.html?utm_source=feedburner&utm_medium=feed&utm_campaign=...&utm_content=...
みたいに変更されています。このため
という現象が起こります。
こんな具合です。
http://www.google.co.jp/search?q=inurl%3Autm_source%3Dfeedburner
http://adsenseforfeeds.blogspot.com/2009/11/afternoon-frank-hey-howdy-george.html
コメント欄のやりとり
Chris said...
Question: does this explain why anyone who clicks through to my site via my feed hits a URL with the suffix utm_source=feedburner&utm_medium=feed&utm_campaign=Feed%3A%20
etc?
Seems like a sloppy way to implement this. Any way I can get rid of that extra baggage?
November 16, 2009 8:50 PM
担当者っぽい人の返事
Steve Olechowski said...
... 前略 ...
@Chris - this post shows you how to turn it off
everyone - else - yes we hear you and are working on it!
November 17, 2009 7:52 AM
「この記事で無効化する方法を説明しているよ」と一蹴されている。多分この担当者は意味が分かってないか、あるいは意図的に問題を無視してる。
何でわざわざユーザーが操作して無効にしないといけないんだ?勝手に有効にしておいて。utm_***というのは、少なくともGoogle Analyticsを使ってない人には全く無意味なクエリで、そのような人にとってはURLが汚くなるだけで完全に無駄な機能だ。デフォルトはオフであるべきで、この機能を有効にしたい人が「デメリットを承知した上で」有効にすべきだろう。なぜこのような大きな副作用の伴う変更をユーザーの同意を得ずに勝手に行うのか疑問に思う。恐らくデフォルトオンになっているのは、Analyticsの管理画面を見ている人に新機能が登場したことに気付かせるためだろう。デフォルトオンでも全然かまわないよ、URLが汚れないなら。
クエリにトラッキング用のパラメータを含めるというのは、簡単だが、悪く言えば「手抜き」の方法だ。URLに余計なクエリを付けずに同等の機能を提供できるかと言えば、出来ると思う。なぜならFeedBurnerもAnalyticsもGoogleが運営するサービスだからだ。FeedBurnerのアクセス解析結果をGoogle Analyticsに反映させるならアカウントを紐づけてアクセス解析結果を交換するようにすればいい。精度は下がるかも知れない、が、クエリ付きの状態でコピーアンドペーストされて様々なサービスに伝搬してしまう方が、よっぽど精度を下げることになると思う。
追記
Benchmark: timing 100 iterations of libxml, parser, regexp, treebuilder... regexp: 1 wallclock secs ( 0.97 usr + 0.01 sys = 0.98 CPU) @ 102.04/s (n=100) parser: 1 wallclock secs ( 1.66 usr + 0.00 sys = 1.66 CPU) @ 60.24/s (n=100) libxml: 3 wallclock secs ( 2.56 usr + 0.02 sys = 2.58 CPU) @ 38.76/s (n=100) treebuilder: 75 wallclock secs (74.19 usr + 0.08 sys = 74.27 CPU) @ 1.35/s (n=100)
use strict; use File::Slurp qw(slurp); use HTML::TreeBuilder::XPath; use HTML::Selector::XPath 'selector_to_xpath'; use HTML::Parser; use HTML::TreeBuilder::LibXML; use Benchmark qw(:all); my $xpath = selector_to_xpath('.hatena-asin-detail-info'); my $hatena1 = slurp("motemen.txt"); # motemen/20080926/1222434809 warn parser($hatena1); warn treebuilder($hatena1); warn libxml($hatena1); timethese 100, { regexp => sub { html2text($hatena1) }, treebuilder => sub { treebuilder($hatena1) }, libxml => sub { libxml($hatena1) }, parser => sub { parser($hatena1) }, }; sub html2text { my $str = shift; $str =~ s/<.*?>//g; $str =~ s/\s//g; $str; } sub parser { my $html = shift; my $out; my $ignore; my $tag_opened = 0; HTML::Parser->new( start_h => [ sub { my ( $tag, $attr, $text ) = @_; if ( $attr->{class} =~ /hatena-asin-detail-info/ ) { $ignore = 1; } $tag_opened++ if $ignore; }, 'tag,attr,text' ], end_h => [ sub { $tag_opened-- if $ignore; if ($tag_opened == 0 && $ignore) { $ignore = 0; } } ], default_h => [ sub { $out .= shift unless $ignore }, "text" ], comment_h => [""], )->parse($html); $out =~ s{\s}{}g; return $out; } sub treebuilder { my $html = shift; my $p = HTML::TreeBuilder::XPath->new; $p->parse($html); my $nodes = $p->findnodes($xpath); for my $n (@{$nodes}) { $n->delete; } my $t = $p->as_text(""); $t =~ s{\s}{}g; return $t; } sub libxml { my $html = shift; utf8::decode($html) unless utf8::is_utf8 $html; my $p = HTML::TreeBuilder::LibXML->new; $p->parse($html); my $nodes = $p->findnodes($xpath); for my $n (@{$nodes}) { $n->delete; } my $t = $p->as_text(""); $t =~ s{\s}{}g; return $t; }
前回の記事が投げっぱなしジャーマンなので、親切丁寧にgadgets.io.makeRequestで署名付きリクエストを使った場合に付加されるOAuthの署名の検証方法を書きます。
Perlの場合です。
http://developer.mixi.co.jp/appli/pc/lets_enjoy_making_mixiapp/require_servers
ここに書いてあるのは「-----BEGIN CERTIFICATE-----」で始まるX.509証明書で、Crypt::OpenSSL::RSAでそのままでは読めなかったので変換する。
use strict; use Crypt::OpenSSL::CA; my $mixi_ca =<<END; -----BEGIN CERTIFICATE----- .... mixiのページからコピペ .... -----END CERTIFICATE----- END my $ca = Crypt::OpenSSL::CA::X509->parse($mixi_ca); warn $ca->dump; # 何か色々表示される print $ca->get_public_key->to_PEM; # 公開鍵を取り出す
なんかCrypt::OpenSSL::CAのテストがこけまくってたけどまあ動いたので良しとする。
use strict; use Net::OAuth; use URI; use Crypt::OpenSSL::RSA; my $mixi_pubkey =<<END; -----BEGIN PUBLIC KEY----- .... 手順1で取り出した公開鍵 .... -----END PUBLIC KEY----- END my $public_key = Crypt::OpenSSL::RSA->new_public_key($mixi_pubkey); my $url = URI->new("mixiアプリから届いたリクエストのURL"); my $request = Net::OAuth->request("protected resource")->from_url( $url, request_method => "GET", request_url => $url, consumer_secret => "dummy", # 使わない token_secret => "dummy", # 使わない ); warn $request->signature_base_string; # signature base stringの確認 if ( $request->verify($public_key) ) { warn "OK" } else { die "Signature verification failed"; }
validationで弾かれるので使用しないパラメータも入れないとダメだった。
use strict; use URI; use URI::QueryParam; use OAuth::Lite::Util qw(create_signature_base_string); use OAuth::Lite::ServerUtil; my $mixi_pubkey = <<END; -----BEGIN PUBLIC KEY----- .... 手順1で取り出した公開鍵 .... -----END PUBLIC KEY----- END my $url = URI->new("mixiアプリから届いたリクエストのURL"); my $util = OAuth::Lite::ServerUtil->new; $util->support_signature_method("RSA_SHA1"); warn create_signature_base_string("GET", $url, $url->query_form_hash); # signature base stringの確認 if ( $util->verify_signature( method => "GET", url => $url, params => $url->query_form_hash, consumer_secret => $mixi_pubkey, ) ) { warn "OK"; } else { die $util->errstr; }
特定のアプリケーションの脆弱性の指摘とかではなく一般論として手短に書きます。外部サーバーと連携するようなOpenSocialアプリを作る際に、ユーザーから送られてきたデータを信頼してはいけません。特にアプリケーション内で「自分が誰であるか」を取得して、それをパラメータとして外部サーバーに送信するようなケースでは注意が必要です。
そのようなケースでは、署名による認証つきのリクエストを使うべきです。 → 署名の検証方法書いた http://subtech.g.hatena.ne.jp/mala/20091028/1256710692
http://code.google.com/intl/ja/apis/opensocial/docs/0.8/reference/gadgets/#gadgets.io.makeRequest
opt_params[gadgets.io.RequestParameters.AUTHORIZATION] が gadgets.io.AuthorizationType.SIGNED に設定されると、コンテナは相手のサーバーに対してユーザーの身元を証明することが必要になります。そのためにコンテナでは、次のようにします。
このときにQUERY_STRINGに付加される、opensocial_viewer_idを使用するべきです。opensocial_viewer_idは閲覧者のidで、opensocial_owner_idは見てるページの所有者のidです。ガジェットタイプのアプリの場合だとviewerと異なることになるでしょう。
重要なことはブラウザ内で実行されているコードが改変されていないことを保障することは出来ない、ということです。これはウェブアプリケーションにおけるセキュリティの基本だと思います。こんな基本的なことが分かってなくても200万人が利用するアプリケーションは作れる!!!!
これは大事なことだと思うのでmixiは開発ガイドにでも書いておいたほうが良いと思われます。
例えばmixiアプリにおいて、OpenSocialのJavaScript APIを利用して、マイミクの一覧を取得して、gadgets.io.makeRequestを使用して外部サーバーに送信するとします。この時に送られてくるマイミクの一覧は必ずしも正しいとは限りません。ユーザースクリプトやデバッグ用のproxyなどで上書きされている可能性があります。
署名付きリクエストによって、確かにOpenSocialコンテナから送信されたリクエストであることと、コンテナが付加したopensocial_viewer_idなどのパラメータが正しいことは保障されていますが、ユーザーが生成したデータは必ずしも正しいとは限りません。Aさんが送ってきたマイミク一覧はあくまで「自称」のデータです。いくらでも書き換えられます。
AさんとBさんがマイミクであることを条件に何か特別な操作を可能にするようなアプリを作る場合は注意が必要になるでしょう。例えば、Aさんの申告してきたマイミク一覧と、Bさんの申告してきたマイミク一覧の双方を一定期間キャッシュしておいて照合することで、改変されている可能性を検出することが可能でしょう。が、確実ではないし面倒くさい。
どうにもならないのかというと、そういうわけでもないです。JavaScriptが使えないモバイル版の場合では。2-legged OAuthを使え、ということになってます。
http://developer.mixi.co.jp/appli/appli_mobile/lets_enjoy_making_mixiappmobile/2-legged-oauth
http://d.hatena.ne.jp/lyokato/20080819/1219116960
https://sites.google.com/site/oauthgoog/2leggedoauth/2opensocialrestapi
とても簡単に言うと、ユーザーによる承認画面をスキップしたOAuthです。JavaScriptのAPIで取得できるのと同等のデータをサーバーサイドで取得することが出来ます。アプリ作成者がインストールしたユーザーのidをクエリに加えた上でOAuthのリクエストを出します。認可はアプリをインストールした時点で許諾を得たと見なします。
クライアントサイドから送られるデータが信頼できないということが「アプリケーションの性質上、深刻な問題になる場合は」こういった方法でサーバーサイドでマイミクの一覧を受け取ったりすればいいんじゃないですかね。ただし、PC版の開発ガイドには2-legged OAuthには触れてませんし、使えるようになる予定かどうかも知りません。
終わり。
近年(でもないか)、言語内に実装された軽量スレッド/軽量プロセスを利用することで、OSが提供するスレッド機能の限界を超えた数の並列処理を行うことが容易に出来るようになって来ています。PerlのCoro、RubyのFiber、Erlangのプロセスなどです。Perlについては少し詳しく解説しますが、他の言語については知らないことが多いですので、適当だったり大雑把だったりするでしょう。
M:Nスレッドというのはカーネルレベルスレッドとユーザーレベルスレッドの数が非対称なスレッドモデルを指す。4コアのCPUを使っている場合、10個のユーザーレベルスレッドを4個のカーネルレベルスレッドで実行する、とか。
http://www.hyuki.com/yukiwiki/wiki.cgi?TheC10kProblem#i17
スレッドライブラリを実装するのには選択肢がある: 全てのスレッド処理をカーネルで行う (これを 1:1 スレッドモデルという.) か, 一部をユーザ空間に持ってくるかだ. (これを M:N スレッドモデルという.) ある時点では M:N スレッドの方が高性能だと考えられていた. しかし正しく動かすにはあまりに複雑だったため, やがて人々は離れていった.
などなど。アプリケーションや言語処理系は(余計なことを考えずに)OS標準のスレッドモデル(ネイティブスレッド)を使用し、OSが「より効率の良いスレッドモデル」を実装するというアプローチがあります。一時期、M:Nスレッドモデルは多数のスレッドを生成する際に高性能だと考えられていましたが、今は廃れています。もちろんネイティブスレッドを改良する試みがありますが、1:1のスレッドモデルが主流となりました。現行のOSではM:Nスレッドモデルは主流ではありません。なので「ネイティブスレッド」と言った場合は、1スレッド=1カーネルスレッドというスレッドモデルを指すと考えて良いでしょう。
現実問題として現在のカーネルスレッドは、大量に生成した際にパフォーマンスが劣化します。カーネルスレッドを作り放題という未来は当面やってこないでしょう。なので数千のスレッドを作成するようなアプリケーションの場合、ユーザーレベルで実装された軽量スレッドが必要になります。OSのレイヤーでのM:Nスレッドへの取り組みは廃れましたが、それに代わって言語処理系やアプリケーションのレイヤーでのM:Nスレッドのアプローチは普及しているのではないか、と考えています。(実際そういうことをやっているので)
カーネルレベル <-> ユーザーレベルを行ったりきたりしないので、一般的にはユーザーレベルで実装されたスレッドのほうが切り替えコストが軽い、と考えられているが、LLのインタプリタレベルで実装されているスレッドはそんなに効率が良くなかったりする。ネイティブスレッドを使用するとマルチコア性能を生かせるはずだが、何か色々理由があって上手くいかなかったりする。そんな現状である。個人的にはLLを使ってる人はマルチコア使い切りたいとか、あんまり考えてないと思うし、そんな必要もないと思う。何も考えずに使えるほうが重要。
GILを無くす試み
http://www.loveruby.net/ja//rhg/book/thread.html
以上がrubyのスレッド切り替えの実装だ。どう考えても軽くはない。大量に malloc() realloc()して大量にmemcpy()してsetjmp() longjmp()した挙句スタックをのばすために関数を呼びまくるのだから「死ぬほど重い」と表現しても問題あるまい。しかしその代わりにOS依存のシステムコール呼び出しもなければアセンブラもSparcのレジスタウィンドウ関連のみだ。これならば確かに移植性は高そうである。
http://www.infoq.com/jp/news/2009/08/future-ruby-gc-gvl-gil
これはハードウェアレベルでの話を指していますが、マルチスレッドプログラミングの手法においても同じ概念が通用します。非対称型では、役割によって余っているCPUが出てくるため、究極的には対称型の方がCPUを使いきれるということになります。
サーバーを書く場合には次のように区別できるでしょう。
対称型と非対称型では、必要となるスレッド数が異なります。
内部的には非対称型のスレッドモデルを使用して、アプリケーション開発者から見た場合には対称型のモデルでコードを書けばよい、というアプローチもあるでしょう。この場合、アプリケーション開発者は全てのスレッドが同じように振舞うようにコードを書き、言語処理系やライブラリが特定の機能を別のスレッドで行うようになります。
これはI/Oのパフォーマンスに関する話になります。例えばファイルディスクリプタにデータを書き込む場合だと
AIO_*システムコールを使います。
どちらが優れているか?
が、発生することになります。
このコストが無視できないほど大きい場合、Proactorパターンの優位性が大きくなる、ということになるでしょう。
AnyEventを使ったI/O処理は基本的にReactorパターンです。が、IO::AIO + AnyEvent::AIO or Coro::AIOを用いることで、Proactorパターンを用いることも可能です。
AIOシステムコールはすぐに完了しますが、処理が実際にいつ完了するのか分かりません。IO::AIOではIO::AIO::poll_filenoを監視することで、AIOが完了したタイミングを知ることが出来ます。
という仕組みになっているようです。AnyEvent::AIOがやってくれます。IO::AIOはaio_*を発行するための専用のスレッド(Perlのスレッドではなく、ネイティブスレッド)を立ち上げて、そこで処理をします。このスレッドはPerlからは見えません。
これは、手続き型言語と関数型言語の対立に近いと思います。いわゆる通常のマルチスレッドは前者です。後者はErlangやScalaのActor、メッセージパッシングを用いた並列処理を指します。
メッセージパッシングを用いた並行処理は「スレッドが物理的に別のマシンであっても」同様に動作します。
自分はOS開発者でも言語開発者でもないので、現状でもっとも現実的な選択肢を選ぶようにしています。
そんなわけで現在は、次のようなアプローチを取っています。
これらを目的に応じて適度に使い分けていけばいいんじゃないですかね。長文乙。