[关闭]
@elibinary 2016-12-25T04:58:49.000000Z 字数 5790 阅读 1484

了解 Rack (上、下)

Rails


Rack provides a minimal interface between webservers that support Ruby and Ruby frameworks.

To use Rack, provide an "app": an object that responds to the call method, taking the environment hash as a parameter, and returning an Array with three elements:

  • The HTTP response code
  • A Hash of headers
  • The response body, which must respond to each

以上描述来自 rack doc
不看不知道,一看吓一跳,rack 的包容之大,应用之广出乎我的想象。
rack doc
对 WEBrick, Mongrel, FCGI, Thin, Puma等等还有很多的 web server 都进行了支持,rack-base 的框架也是多的不行。

  1. These frameworks include Rack adapters in their distributions:
  2. Camping
  3. Coset
  4. Espresso
  5. Halcyon
  6. Mack
  7. Maveric
  8. Merb
  9. Racktools::SimpleApplication
  10. Ramaze
  11. Rails
  12. Rum
  13. Sinatra
  14. Sin
  15. Vintage
  16. Waves
  17. Wee
  18. and many others.

'Any valid Rack app will run the same on all these handlers, without changing anything.' -- doc

Rack 是什么

Rack provides a minimal, modular and adaptable interface for developing web applications in Ruby.

rack 对 HTTP 请求和响应进行了封装,并对上层提供了一套统一的接口。它需要一个能够响应(response)call 方法的对象,该call方法接收一个 hash 类型的参数 env,并且返回一个三元素的数组
[code, headers, body]

一个简单的例子:

  1. require "rack"
  2. require "awesome_print"
  3. class HelloWorld
  4. def call(env)
  5. ap env
  6. [200, {'tp' => 'tp'}, ['hello world~']]
  7. end
  8. end
  9. Rack::Handler::WEBrick.run HelloWorld.new, Port: 3333

很简单吧

根据文档说明 app 只要是一个拥有一个可以接收 env 参数并返回三元素数组的 call 方法的对象就可以,那么就有了更简单的写法。

  1. app = Proc.new do |env|
  2. ['200', {'Content-Type' => 'text/html'}, ['hello world~']]
  3. end
  4. Rack::Handler::WEBrick.run app, Port: 3333

它其实就是处于这样一个位置:
rack-1

Middleware

Rack middleware is a way to filter a request and response coming into your application.

废话不多说,先看个例子:

  1. require "rack"
  2. require "awesome_print"
  3. class Clock
  4. def initialize(app)
  5. @app = app
  6. end
  7. def call(env)
  8. puts "Current Time: #{Time.now}"
  9. code, headers, body = @app.call(env)
  10. [code, headers, body << "Current Time: #{Time.now}"]
  11. end
  12. end
  13. app = Proc.new do |env|
  14. ['200', {'Content-Type' => 'text/html'}, ['hello world~']]
  15. end
  16. Rack::Handler::WEBrick.run Clock.new(app), Port: 3333

从代码可以非常清晰的明白其思路就是对 app 的一层包装,先一步获取请求并对其进行修饰,获取 response 后也可进行修饰后返回。
简单的说,Rack 的 一个 middleware 就是一个类,这个类的实例对象符合 rack 对 'app' 的所有要求。也就是说它的实例本身就可以作为一个 rack app 传递给 Rack::Handler::WEBrick.run,那么 Middleware 也就是可以一层一层的嵌套下去的。
就像这样:
middleware-1
(看它的结构像什么,我的第一感觉是这玩意就像一个俄罗斯套娃)

再来看一下其工作流:
rack-2
有没有很熟悉,处理方式有没有很像 pipeline design pattern

rackup

在接着往下探索之前,先让我们来看一个强大的工具:rackup

rackup is a useful tool for running Rack applications, which uses the Rack::Builder DSL to configure middleware and build up applications easily.
-- rack doc

使用 rackup 可以轻松构建并运行一个 rack app
比如这样:

  1. # config.ru
  2. class Clock
  3. def initialize(app)
  4. @app = app
  5. end
  6. def call(env)
  7. puts "Current Time: #{Time.now}"
  8. code, headers, body = @app.call(env)
  9. [code, headers, body << "Current Time: #{Time.now}"]
  10. end
  11. end
  12. class BodyUpper
  13. def initialize(app)
  14. @app = app
  15. end
  16. def call(env)
  17. status, head, body = @app.call(env)
  18. upcased_body = body.map{|chunk| chunk.upcase }
  19. [status, head, upcased_body]
  20. end
  21. end
  22. app = Proc.new do |env|
  23. ['200', {'Content-Type' => 'text/html'}, ['hello world~']]
  24. end
  25. use Clock
  26. use BodyUpper
  27. run app

直接使用rackup命令就可以运行这个样例。
可以看出它使用了一系列方法,约定以及配置帮忙简化了 stack 的生成以及 app 的运行。这些方法来自于 Rack::Builder ,让我们来看一下这个类

Rack::Builder implements a small DSL to iteratively construct Rack applications.
-- comments

其核心方法如下:

  1. # All requests through to this application will first be processed by the middleware class.
  2. # The +call+ method in this example sets an additional environment key which then can be
  3. # referenced in the application if required.
  4. def use(middleware, *args, &block)
  5. if @map
  6. mapping, @map = @map, nil
  7. @use << proc { |app| generate_map app, mapping }
  8. end
  9. @use << proc { |app| middleware.new(app, *args, &block) }
  10. end
  11. # Takes an argument that is an object that responds to #call and returns a Rack response.
  12. def run(app)
  13. @run = app
  14. end
  15. # Creates a route within the application.
  16. #
  17. # The +use+ method can also be used here to specify middleware to run under a specific path:
  18. #
  19. # Rack::Builder.app do
  20. # map '/' do
  21. # use Middleware
  22. # run Heartbeat
  23. # end
  24. # end
  25. #
  26. # This example includes a piece of middleware which will run before requests hit +Heartbeat+.
  27. #
  28. def map(path, &block)
  29. @map ||= {}
  30. @map[path] = block
  31. end
  32. def to_app
  33. app = @map ? generate_map(@run, @map) : @run
  34. fail "missing run or map statement" unless app
  35. app = @use.reverse.inject(app) { |a,e| e[a] }
  36. @warmup.call(app) if @warmup
  37. app
  38. end
  39. def call(env)
  40. to_app.call(env)
  41. end

可以看出 to_app 方法最终生成了完整的 app stack ,它已经将 app 以及 middlewares 全部串在了一起, 并且保证一次调用call, 就会按规则经过所有的 middlewares 。

Rails on Rack

Rails.application is the primary Rack application object of a Rails application. Any Rack compliant web server should be using Rails.application object to serve a Rails application.
-- rails_on_rack

Rails::Server 加载中间件的方式是这样的:

  1. def middleware
  2. middlewares = []
  3. middlewares << [Rails::Rack::Debugger] if options[:debugger]
  4. middlewares << [::Rack::ContentLength]
  5. Hash.new(middlewares)
  6. end

Rails::Application 通过 ActionDispatch::MiddlewareStack 把内部和外部的中间件组合在一起,形成一个完整的 Rails Rack 程序。

同时rails也提供了命令来查看正在使用的中间件链

  1. # rake middleware
  2. use Rack::Sendfile
  3. use ActionDispatch::Static
  4. use Rack::Lock
  5. use #<ActiveSupport::Cache::Strategy::LocalCache::Middleware:0x000000029a0838>
  6. use Rack::Runtime
  7. use Rack::MethodOverride
  8. use ActionDispatch::RequestId
  9. use Rails::Rack::Logger
  10. use ActionDispatch::ShowExceptions
  11. use ActionDispatch::DebugExceptions
  12. use ActionDispatch::RemoteIp
  13. use ActionDispatch::Reloader
  14. use ActionDispatch::Callbacks
  15. use ActiveRecord::Migration::CheckPending
  16. use ActiveRecord::ConnectionAdapters::ConnectionManagement
  17. use ActiveRecord::QueryCache
  18. use ActionDispatch::Cookies
  19. use ActionDispatch::Session::CookieStore
  20. use ActionDispatch::Flash
  21. use ActionDispatch::ParamsParser
  22. use Rack::Head
  23. use Rack::ConditionalGet
  24. use Rack::ETag
  25. run MyApp::Application.routes

添加一个自己的中间件:

  1. # config/application.rb
  2. # Push Rack::BounceFavicon at the bottom
  3. config.middleware.use My::SelfMiddle

rails 提供了几个方法来更加方便进行这个操作:


Rails on Rack — Ruby on Rails Guides

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注