What have you found for these years?

2009-02-07

rack middleware

我一直搞不懂他 middleware 到底是怎麼處理的,
use 和 run 之間又有什麼差別?再一次證明:



也許正是因為所有的奧秘都在 source 裡面,所以才沒有任何人
出來講解到底 use 和 run 之間究竟有什麼差異?每每查詢 rack,
又只能找到 how but why. how 還要你說嗎?隨便找個 config.ru 就知道了吧...

就如同誰說的忘記了,rack's interface is deadly simple.
重點應該還是 middleware 是怎麼串接起來的。
當然,其實這種事也是寥寥幾行就能述盡,或許並不值得拿出來大叔吧。

重點就在這,這是 use:

def use(middleware, *args, &block)
@ins << if block_given?
lambda { |app| middleware.new(app, *args, &block) }
else
lambda { |app| middleware.new(app, *args) }
end
end

把 middleware 用 lambda 包一層放進 @ins 裡。接著是 run:
def run(app)
@ins << app #lambda { |nothing| app }
end

這裡就可以看出,use 要丟 class 進去,而 run 要丟 instance 進去。
而且 use 所丟進去的 class 的 constructor 的第一個 argument,
必須接受某一個 middleware! 這樣也可以猜測出,middleware 必定是串接的。
最後就是 to_app, 也就是真正的重點:
def to_app
@ins[-1] = Rack::URLMap.new(@ins.last) if Hash === @ins.last
inner_app = @ins.last
@ins[0...-1].reverse.inject(inner_app) { |a, e| e.call(a) }
end

先不要看那個 URLMap 是什麼鬼,重點只在那個 inject(fold),
他會把你所有的 middileware 做成一個遞迴結構(inductive type?),
大概像是:

(Mid (Mid (Mid (Mid (App args))))


那個 App 就是 use 的 App, 而 Mid 就是任何的 middleware.
事實上,App 和 Mid 是差不多的東西,只差在 App 不需要串 Mid,
所以他的 constructor 並沒有受到限制。記得嗎?我們是丟 instance 進去。

上面的 a 就是某個 App/Mid, 而 e 則是 Mid constructor(廣義的 c'tor)
以顛倒的順序串起,表示當我們寫:
use Rack::Deflater
use Rack::CommonLogger

run MyApp.new

時,request 會這樣跑:
request => MyApp => CommonLogger => Deflater => client
也就是說我們需要注意 middleware 串起來的順序,沒有 side-effect 的
middleware 如 CommonLogger 當然不用在意,但 Deflater 這種會修改
response body 與 http header 的東西就必須小心其串接順序。
(不過這邊有個 trick, 就是 body 不能當成 String, 要當 Enumerator...)
(這可能跟之前提到的 EventMachine 有關,必須分段傳送,這點千萬小心。)

也就是說,上面的 use/run 是可以改寫成一個 run:
run Rack::CommonLogger.new(Rack::Deflater.new(MyApp.new))

而轉換成這種模式,則是靠 Rack::Builder 來達成。這是一個 App,
其 call 只是這樣定義:
def call(env)
to_app.call(env)
end

建造出上述的遞迴結構,然後 call 起來。

我個人認為 rack 的程式沒有寫得很好,大概只比 rails 好一點吧?
不過設計倒是滿巧妙的,確實是個很不錯的解決辦法,只是實作可能要加強一下。
而且這個做法,在 middleware 很多的情況下,不知道會不會影響效能?
畢竟串成這種遞迴結構,變成你要呼叫 MyApp 必須先把前面的 middleware 展開。
這樣會使得 call stack 變得有點巨大。當然這只是我的推測而已,
沒有實際測過,也懶得測...

反正沒有用到二三十個 middleware 的話,這應該也不會是什麼問題?
只是覺得照這樣繼續發展下去,用到一堆 middleware 是還滿有可能的。

==
我個人覺得做網頁的話,rack 會是個一定要認識的東西。
先不要管效能問題,他的彈性真的是超級大。這介面設計得相當漂亮。
大抵上只有三個概念:Handler, Middleware, Application.
前者銜接 server, 中間就是上面在說的東西,app 則是 middleware 的簡化。

0 retries:

Post a Comment

All texts are licensed under CC Attribution 3.0