なんかどうも OmniAuth を使おうと思ったらまず Session を有効にしろやと言われたり言われなかったりするので久しぶりにがっつりコードリーディングしてみた。
以下のバージョンで確認した。
- Sinatra 1.4.7
- Rack 1.6.4
- OmniAuth 1.3.1
まとめ
- rack middlewareは記述順に依存する
- 具体的には OmniAuth の前に Rack::Session を use しておかないとダメ
- なぜなら OmniAuth は session を利用できる前提で書かれているから
- rack middlewareの組み立てられ方
- より正確にはアプリケーション本体をいちばん内側に包むたまねぎ構造
- sinatraのset :sessions, trueはどこに書いてもよい
- set :sessions, true は書く位置を自由にできるが、use Rack::Session::Cookie は明らかに上の方に書かないとダメな理由
1. rack middlewareは記述順に依存する
具体的には OmniAuth の前に Rack::Session を use しておかないとダメ。やっておかないと以下のようなエラーになる。
OmniAuth::NoSessionError at /
You must provide a session to use OmniAuth.
エラーを吐いてるのはここ。
def call!(env)
unless env['rack.session']
error = OmniAuth::NoSessionError.new('You must provide a session to use OmniAuth.')
fail(error)
end
なぜなら OmniAuth は session を利用できる前提で書かれているから。まぁそりゃそうかという気がするけど、そもそも rack middleware の順番てどういうことなのか?
2. rack middlewareの組み立てられ方
rack middleware の組み立て方の正解は Rack 付属のサンプルアプリである Lobster の中身を見てみるとよく分かる。
lobster.ru
require 'rack/lobster'
use Rack::ShowExceptions
run Rack::Lobster.new
この rack/lobster の中身はどうでもよく、いちばん最後、
if $0 == __FILE__
require 'rack'
require 'rack/showexceptions'
Rack::Server.start(
:app => Rack::ShowExceptions.new(Rack::Lint.new(Rack::Lobster.new)),
:Port => 9292
)
end
こういう記述がある。lobster.ru の
use Rack::ShowExceptions
run Rack::Lobster.new
が
Rack::ShowExceptions.new(Rack::Lobster.new)
と等価であることが分かる。つまり、middleware がアプリケーション本体をくるんでいる状態。
分かる。って書いたけど、じゃあ実際に自分で書いたアプリがどう解釈されるのかも見ておこう。ちょっと長いけど、良い子は我慢してつきあってくれ。本当は別にそれっぽい知識はすでにあるのでまったく読み込む必要はなかったんだけど、改めて追いかけてみようかなと
Rack のソースコード読んでる - 大学生からの Web 開発
を見て「そういや読もうと思えば読めるじゃん」と思ったので、use と run がどのように処理されるかを辿ってみた。
実際にはこの辺りは Rack::Server#start の中の wrapped_app から build_app app を呼んでる辺り、
def wrapped_app
@wrapped_app ||= build_app app
end
def build_app(app)
middleware[options[:environment]].reverse_each do |middleware|
middleware = middleware.call(self) if middleware.respond_to?(:call)
next unless middleware
klass, *args = middleware
app = klass.new(app, *args)
end
app
end
この app が実引数の名前もメソッドの名前も同じなのが混乱してしまうが、
def app
@app ||= options[:builder] ? build_app_from_string : build_app_and_options_from_config
end
で、rackup 起動時に何も与えなかった場合はこっちが呼ばれる。
def build_app_and_options_from_config
if !::File.exist? options[:config]
abort "configuration #{options[:config]} not found"
end
app, options = Rack::Builder.parse_file(self.options[:config], opt_parser)
self.options.merge! options
app
end
Rack::Builder.parse_file は最後
def self.new_from_string(builder_script, file="(rackup)")
eval "Rack::Builder.new {\n" + builder_script + "\n}.to_app",
TOPLEVEL_BINDING, file, 0
end
を呼んで、豪快に eval の結果を Rack::Builder.new
def initialize(default_app = nil,&block)
@use, @map, @run, @warmup = [], nil, default_app, nil
instance_eval(&block) if block_given?
end
に渡す手法を見せ、ここで Rack::Builder#use, Rack::Builder#run が呼ばれる。
def use(middleware, *args, &block)
if @map
mapping, @map = @map, nil
@use << proc { |app| generate_map app, mapping }
end
@use << proc { |app| middleware.new(app, *args, &block) }
end
use の中では先ほどの initialize で初期化した @use という Array に middleware が順番に突っ込まれているのが分かる。run はこれだけ。
def run(app)
@run = app
end
さて、Rack の仕様で、 とにかく実行時には call が呼ばれる のは知ってるよね? call を見てみよう。
def call(env)
to_app.call(env)
end
to_app はこう。
def to_app
app = @map ? generate_map(@run, @map) : @run
fail "missing run or map statement" unless app
app = @use.reverse.inject(app) { |a,e| e[a] }
@warmup.call(app) if @warmup
app
end
ここに先ほどの @use も @run も出てくる。ポイントは実にあっさり書かれてるここ。
app = @use.reverse.inject(app) { |a,e| e[a] }
ここで use したものが逆順に wrap されていってる。わーあっさり。
確認していこう。
- e は use のところで proc {} で生成されている Proc オブジェクト
- a は予想通り middleware オブジェクト
e[a]
で Proc オブジェクトが実行される1んだけど、最初の app は run されるもの、例えば Sinatra の classic style なら Sinatra::Application で、inject なので e に渡るのはすべて実行結果の app になる。つまり
2回目の e[a] は最後に use された middleware に本体の app を与えた結果のアプリ
であり、順番に遡っていくので、結果
Rack::ShowExceptions.new(Rack::Lobster.new)
こういうことになる。
これで call というメソッドを持つ middleware オブジェクトで wrap されるたまねぎ構造が実現される。
実に面倒な処理だが、これは先ほども見たように Rack::Server の app にメモ化されているので、最初の一回だけ実行される。
3. Sinatraのset :sessions, trueは記述順を緩和してくれる
Sinatra::Baseの中にこんな記述があって、
def build(app)
builder = Rack::Builder.new
setup_default_middleware builder
setup_middleware builder
builder.run app
builder
end
- setup_default_middleware
- setup_middleware
の順番がキモっぽいなというのが分かる。
def setup_default_middleware(builder)
builder.use ExtendedRack
builder.use ShowExceptions if show_exceptions?
builder.use Rack::MethodOverride if method_override?
builder.use Rack::Head
setup_logging builder
setup_sessions builder
setup_protection builder
end
def setup_middleware(builder)
middleware.each { |c,a,b| builder.use(c, *a, &b) }
end
setup_middleware の方は中で use を呼んでいるので例のアレ。
setup_default_middleware の中の setup_sessions がそれっぽいので読むと
def setup_sessions(builder)
return unless sessions?
options = {}
options[:secret] = session_secret if session_secret?
options.merge! sessions.to_hash if sessions.respond_to? :to_hash
builder.use Rack::Session::Cookie, options
end
まさにここで use OmniAuth の前に use Rack::Session::Cookie が呼ばれている。
ちなみに sessions? って何?って感じだけど、これは set :sessions, true に関係している。
def set(option, value = (not_set = true), ignore_setter = false, &block)
(snip)
define_singleton("#{option}=", setter) if setter
define_singleton(option, getter) if getter
define_singleton("#{option}?", "!!#{option}") unless method_defined? "#{option}?"
self
end
どうもここで sessions? っていうメソッドが定義されてるっぽい? define_singleton を読むと、
def define_singleton(name, content = Proc.new)
# replace with call to singleton_class once we're 1.9 only
(class << self; self; end).class_eval do
undef_method(name) if method_defined? name
String === content ? class_eval("def #{name}() #{content}; end") : define_method(name, &content)
end
end
わーい黒い黒ーい。値を返すメソッドが定義されてる。つまり
set :sessions, true
は
def sessions
true
end
def sessions?
true
end
が定義される。 setup_sessions にもう一度戻ると set :sessions, true が実行されていれば use Rack::Session::Cookie が OmniAuth など通常の middleware よりも先に実行される。なぜなら default middleware だから。
です。
なるほどね!
おまけ - OmniAuthのproviderをuseしてないのでは?
ちなみに
Sinatra Recipes - Middleware - Twitter Authentication With Omniauth
にある
use OmniAuth::Builder do
provider :twitter, ENV['CONSUMER_KEY'], ENV['CONSUMER_SECRET']
end
この書き方って OmniAuth::Builder は use してるけど provider は use してなくね? って気がしたんだけど、
def provider(klass, *args, &block)
if klass.is_a?(Class)
middleware = klass
else
begin
middleware = OmniAuth::Strategies.const_get("#{OmniAuth::Utils.camelize(klass.to_s)}")
rescue NameError
raise(LoadError.new("Could not find matching strategy for #{klass.inspect}. You may need to install an additional gem (such as omniauth-#{klass})."))
end
end
args.last.is_a?(Hash) ? args.push(options.merge(args.pop)) : args.push(options)
use middleware, *args, &block
end
てことでちゃんと最後に OmniAuth::Strategies::Twitter を普通に use してました。名前空間を提供しつつ use するだけの人は楽をできる DSL ステキ。ステキだけど、分からないとこわい。そんな感じ。
ただ、読んでみて思ったけど Sinatra も Rack も面倒な処理してて遅そうだなーという気がするね…。
参考
- GitHub - rack/rack: a modular Ruby webserver interface
- GitHub - omniauth/omniauth: OmniAuth is a flexible authentication system utilizing Rack middleware.
- Sinatra
- File: SPEC — Documentation for rack/rack (master)
- Rack のソースコード読んでる - 大学生からの Web 開発
- Sinatra Recipes - Middleware - Twitter Authentication With Omniauth
- CGI を rackup してみた - あーありがち(2008-12-06)
これは Rack の機能ではなく単に Ruby の Proc オブジェクトの [] メソッドを呼んでるだけ ↩
まとめ
npm に便利ツールが揃ってきてる。
果たして JavaScript という言語が構文チェッカに向いているのか自分には分からないんだけど1、昔と違って全部 JavaScript で済むという環境が本当に揃ってきていて、かつては Perl などで書かれていたツールが今は JavaScript に集約されつつあるみたい。
とりあえず jshint, csslint をうまく使えるようにしていきたい。
背景
ここ数年は Firebug などのブラウザ上の強力なデバッガ、チェッカが活躍する機会が多かったけれども、とは言え
いちいち目的のページを手で開いてチェッカのレポート、あるいは警告やエラーの出ないことをを目視確認するのはバカバカしい
とずっと思っていた。また手動&目視は作業者のレベルに大きく左右されてしまうのも問題。「ちゃんとチェックしてよ」と言わずに済むならその方が嬉しい。言う手間も含めて省力化できるし、漏れも防げる。
手作りの HTML についてはだいぶ前に
なんてものを作って
- HTML tidy
- linkchecker
を利用していた2んだけど、やはり
- JavaScript
- CSS
も対象にしたい。もちろん
The W3C Markup Validation Service
はあるんだけど、
- ローカルだけで
- できるだけサクっと利用できる
ものが欲しい。ということで改めて探してみると最近は JavaScript 製で揃ってきているみたい。
リスト
JSLint
最初に JavaScript 製のツールがキテるっぽいと思ったのはこの名前を聞いてから。
もちろんサイトが有名なんだけど、
npm install -g jslint
するとコマンドがインストールできる(もちろん今のところ Un*x 系の環境が必要)。
最近 Emacs 22 + js2-mode から Emacs 23 + js-mode に移行したので以前よりチェックが甘くなってしまって IE のバグを踏んで苦しんだのもこれを使おうと思ったきっかけ。
ただし jslint はちょっと不評な面もあるらしい。
JSHint
ということで fork したのがこれ。
同じく
npm install -g jshint
でインストールでき、実行ファイルが入る。これについては
Rubygems で入れられるバージョンを作ってみたので Windows の人も試してみてね。
JavaScript Lint
Win, Mac, Linux 用バイナリがあるんだけど、止まってるっぽいなぁ、これ。
csslint
npm search すればイロイロ出てくるんだなと試して見つけたのがコレ。
あ、今見ると IDEA*IDEA で紹介されてるのか。知らなかった。
動かしてみると selector に ID 使うんじゃないとか言われる。ん? 最近はそういう常識なの? メンテナンス性の項目に入ってるみたい。なるほど…。
JS[LH]int ほどにはこうやって使ってみてる、みたいな日本語記事を見かけないけど、評判はそれなりにいいみたい。 CSSTidy ってのもあって最適化もしてくれるんだけど、今後は期待できそうにないかなぁ。
あるいは保管目的のログファイルの整理ポリシーを変えましたの話。
結論
.tar.gz -> .gzs.tar
にします。
- before
- 複数のログファイルを tar でまとめて gzip などで固める
- after
- それぞれのログファイルを gzip などで縮めて tar で固める
背景
保管するログファイルの扱いに困っていました。いや、実際には困っていたというよりはなんとなくあんまりうまくないなぁという程度の感覚でやっていたわけですが。
複数のファイルを保管目的でバックアップする場合、多くはディスク容量の節約をするため tar + gzip などの方法で圧縮していると思います。しかし
この方法はけっこう贅沢なディスクの使い方だ
ということに気がつきました。
問題
多くの、そして古い Un*x の教科書的なドキュメントにはバックアップに tar + gzip を利用する方法が書かれています。しかしこの方法では
tar + gzip を実行するタイミングまではログは生のサイズのまま
なのです。
例えば「月1回ログをまとめて圧縮する」というスケジュールにすると1ヶ月間はログは生のサイズのままです。けっこうな大きさになります。
しかしこの1ヶ月という期間の設定はよくあるパターンかなと思います。あとで DVD などに退避させる際にはこれくらいの期間で置いてあると何かと使い勝手がよいです。
個人では 1TB がヨユーで買えてしまうこのご時世ですが、お仕事的には経費が掛けられないものすごく厳しい状況が続いていますので、生ログのサイズのままハードディスクに置いておくのはできるだけやりたくありません。
試行
そこで
tar.gz のアーカイブにログファイルを append できたら便利じゃね?
と思いつきました。でも結論から言うとどうもこれはできないらしい。まぁ普通に考えてかなり無駄というか無茶な処理ですよね。tar はなぜ失敗したのか分からないけど失敗、cpio の場合は圧縮と append は同時にできねーよ、とはっきり怒ってくれます。
そこで、
rotate と同じ要領で先に gzip しといて tar にまとめりゃいいのか
と気づきました。
解決
何を言ってるか分からないかもしれないのでテストスクリプトを書きました。
- まずダミーのファイルを作って途中までをそれぞれgzipで圧縮してtarアーカイブを作る
- その段階でのアーカイブの中身を表示
- 残りのファイルをgzip圧縮してtarballにappend
- アーカイブの中身を表示
しています。ちゃんと追加されている様子が分かります。
これで、
- アーカイブファイルの名前を年月などから自動決定する
- そのファイルがすでにあればそこに append なければ新規作成
するスクリプトに仕立て上げれば全自動でディスクを節約するアーカイブのできあがりでしょうか。
これなら時期を見て DVD などに退避させる際にも対象のファイルはすでに圧縮してアーカイブ済みなので、えっちらおっちら圧縮する手間と時間も削減できます。
一石二鳥。
前からやろうやろうとは思っていながら放置になっていた ploticus ドキュメントの和訳なんだけど、とりあえず FAQ については見つけた。助かる。
ploticus は非対話的に二次元グラフを作成するツール。まぁ簡単に言うと Excel とか OOo とか使わなくてもサーバ上のログなんかからグラフを生成できるものだと自分は解釈している。
サーバ上のデータを使ってグラフを作るっていうのは mrtg, Webalizer なんかを見れば分かるように今のところプログラムがそれぞれ別個にグラフ作成をやっちゃってて、独自のグラフを起こすことができない。いやまぁ独自に GD とかとお喋りすればいいわけですけど。
そんなわけでこれは割と使えるんじゃないかなーと以前から目をつけてはいるんだけど、ちっともブレイクする気配がないのでそういう需要ってないのかなーとまたいつものように「どうせオレの趣味はマイナーだよ、いじいじ」モードになっていたところ、ふと FAQ の日本語訳を見つけたので少し機嫌がよくなりましたまる。
見た。
威張って言うことではないが、実は今年までスターウォーズは一つもまともに見たことがなかった。テレビでデジタルリマスターを何回もやるので 4, 5 を見た。吹き替え。先が気になったのでビデオを借りて 6 を見た。これも吹き替え。
ファンの人には大変申し訳ないが、感想としては「なんだこのショボイ設定の映画は」であった。正直、この映画が世界中で大ヒットしている理由がさっぱり分からない。どういうことだ、自分の感性はそんなにも鈍いのか。いやいやいや、だってダースヴェイダーはどう考えても単に部下を恐怖で縛ったうえに見殺しにするダメ中間管理職だし、フォースを侮ったやつにはすぐ手を出すし、組織の中に居るべき人間じゃない。
フォースはフォースで、できることはライトセイバーを使うこととサイコキネシスと、意識の低いものの気をそらせるくらいで、思ったほどすごい能力のように見えない。まぁ身体能力の補助に使うことで超人的な活躍はできるみたいだけど、ラスボスの最終攻撃がなんか電撃みたいなやつで「うわー」って。え、これがダークサイドの必殺技?って。
おまけに後だし後だしで実は父ちゃんとか実は双子とか、そんなんありかと。明らかに最初は年齢差のある設定じゃなかったか、おいと。双子ってどないやねんと。
ところが。
ところがだね。
EPISODE III は面白かったんだな、これが。
そこで思った。まず自分は映画には映像美を求めていると。自分の中では SF ものの古い映像はどうもまったくおいしくないらしい。映像のアラに目がいくのと同時に設定のアラがどんどん気になってきてしまう。いやー CG 万歳。
あと、ハリウッド映画にありがちな「いがみあってた5秒後にチュー」のような男女の絡め方がきらいなので、そういう意味でも 4, 5, 6 はダメなんだなぁ。
ただ、最初っから全部字幕で見てたら、あるいはまだマシだったかもしれないと少しだけ思う。というのも、今回 EPISODE III は劇場で字幕版を見たわけだけど、これがセリフ回しに Web 上というか IT 系の人が好んで使うネタがゴロゴロしてて「あぁ、これが元ネタか」という発見があったり、ヨーダの喋りはオリジナルの語順じゃないと雰囲気が出ないと感じたりしたから。
最後に一つだけ。ダースヴェイダーだろうがアナキンだろうが、彼は力がずば抜けているだけでバカだと思うんだがどうか? いや面白かったんですよ、今回は。悲劇はきらいじゃないし。(SEVEN とか好き。)でもね。やっぱこいつはバカだなと。
ちなみに、1 と 2 は見ていない。見る必要ないなーって思ったんでたぶんこの先も見ない。
Re:この高音質 (オンキョーがCD以上の高品質で音楽配信に参入 /.-j)
PC の劣悪な環境でノイズを減らし、クリアな音を楽しむための機器をオンキョーが扱っていると。音楽配信として見ると?だけど、製品のアピールにはなるのか。なるほどなぁ。毛色の違う iTunes Music Store + iPod 戦略か。
ircd はこれを書いてほどなくして動いたんだけど、そのあとハマっているのは
- PHP
- Zebedee
PHP はなんか ports の build 方法が変わっちゃったのでまだまともな状態で動かせてない。
Zebedee は ports で入れたら 2.5.2 が入ったんだけど、どうも 2.5.1 以前と互換性がなくなったらしい。互換性のためのオプションを有効にしてみても 2.4.1 と繋がらない。2.4.1 を野良 build するかなぁ。と思いながら思案中。幸い面倒な依存関係はないしな。
FreeBSD 4.10R を入れて3時間ほどでどうにか ssh は繋がるようになる。sshd_config の設定をバックアップしていたものに戻したつもりだったが、なぜか上書きできていなくて password 認証が始まったときにはちょっと焦ったが、それも今は大丈夫。
もっと早いかと思っていたが、これくらいのスペックのマシンだとどうしても細かい ports の build に時間を取られる。最初にハマったのは cvsup. 長いよこれ。実際に長いのは cvsup じゃなくて ezm3 や周辺ツールだけどさすがに長いと感じる。しかもこれを make してるときの手元の ports ツリーは最新じゃないんだから、わざわざ ports であるメリットはなかったのかも。まぁ「待っているだけでいい」っていう気楽さはあるんだけど。
で、初回の cvsup 完了までがまた長い。FreeBSD は最初に必要なバイナリをすべて CD-ROM 1枚で入れることができるのでインストールは速いが、それ以降のセットアップはバイナリで芋づるインストールできる Debian の方が早いな、こりゃ。
今ハマっているのは ircd. 前回も ircd はしばらく悩んだが、今回は今まで使っていた ircd-hybrid のバージョンが上がって設定ファイルの書き方が完全に別物になってしまった。うげーと思って他のものを試すがうまく動かず。なんでだーと思ってあちこち調べると、どうも ircd の設定ファイルは IP v6 絡みで解釈に失敗するみたいだ。: でデータを区切るなと。つーことで諦めて新しい ircd-hybrid の設定ファイルを攻略しますか。慣れればこの書き方の方がはるかに人間にやさしい。
http://www.mainichi.co.jp/digital/network/archive/200308/06/1.html
現場で運用している人間(少なくとも実情がパーフェクトに分かっている人間。管理者とは名ばかりで実態が把握できていないことがこれまで何度も起きてきた問題なので。)と、総務省側の運用担当が本音で語ったうえで内部告発しないとダメなんじゃないかな。これ。
まぁものすごく非現実的ですが。アングラ掲示板とか(それこそ Winny か?)なら可能なんじゃないかな。でもアングラ掲示板が利用できるレベルの人が運用しているなら、そこからちゃんと勉強したと仮定すればそれなりに信用できるかもしれない。
あと、桜井さんはネックワークについてそれほど強くないのが戦術から見てとれるので、喋りが立たなくてもいいから、もっと詳しい人を連れてきた方がいいと思う。繋がっていることは確かに問題だが、運用次第でどうにかなるのは事実。繋がっていることだけ叩き続けてもラチがあかない。いちばんの問題は運用がまったくもって信用できないってことでしょ。
この手の話は喋りのうまい目立つ人同士がやり合ったって、テレビで片山さんががなり立てていたのと大差ないと思うんだな。
- チェックさせている(自分はチェックしていない)
- 危険性がある。可能性がある。(具体的で詳細な危険を列挙できていない)
これで議論として成り立つわけがない。言い方は悪いが熊さん八っつぁんレベルですよ。知らない人と分からない人の議論は徒労。
振り出しに戻していいのなら、住基ネットのいちばんの問題は、総務省は認めていないがまず嘘をついていたこと。これで国民の信頼を裏切ったわけだから、まず信頼回復を図るのが正しい手順。なのに我々のやっていることは正しく、セキュリティは万全だと言い張っている。
そんなやり方で納得できる大人が居るわけないだろ。
まず謝罪。片山さんは当然クビでしょ。あんな声のでかいだけの人が大臣で居るだけで問題だっての。で、それから国民を納得させるものを出せ。それができないのならそれについても謝罪だ。
あと、現場の運用担当者はセキュリティ関係のオープンな試験を受けるべき。(受験者の名簿から担当者がバレないようにしないといけないけど。)みんなが納得できるレベルであることを示さなければいけない。研修? そんなもの形だけで役に立たないことなんて、みんなが知ってますよ。ばかにするのもたいがいにしなさいっての。
それから、自治体の端末から被害が他に拡大することはないなんて言うけど、そういう問題じゃ……
切りがないのでこの辺で。