首先,Rack 就是两个东西:
Rack - 网络服务器界面
机架的基础是一个简单的约定。每个机架兼容的网络服务器总是会在你给他的对象上调用一个调用方法,并提供该方法的结果。 Rack 准确地指定了这个调用方法的外观,以及它必须返回的内容。那是架子。
让我们试一试。我将使用 WEBrick 作为机架兼容的网络服务器,但它们中的任何一个都可以。让我们创建一个返回 JSON 字符串的简单 Web 应用程序。为此,我们将创建一个名为 config.ru 的文件。 config.ru 将由 rack gem 的命令 rackup 自动调用,该命令将简单地在机架兼容的网络服务器中运行 config.ru 的内容。因此,让我们将以下内容添加到 config.ru 文件中:
class JSONServer
def call(env)
[200, {"Content-Type" => "application/json"}, ['{ "message" : "Hello!" }']]
end
end
map '/hello.json' do
run JSONServer.new
end
按照惯例,我们的服务器有一个名为 call 的方法,该方法接受环境散列并返回一个格式为 [status, headers, body] 的数组供网络服务器服务。让我们通过调用 rackup 来尝试一下。默认的机架兼容服务器,可能是 WEBrick 或 Mongrel 将启动并立即等待请求服务。
$ rackup
[2012-02-19 22:39:26] INFO WEBrick 1.3.1
[2012-02-19 22:39:26] INFO ruby 1.9.3 (2012-01-17) [x86_64-darwin11.2.0]
[2012-02-19 22:39:26] INFO WEBrick::HTTPServer#start: pid=16121 port=9292
让我们通过 curl 或访问 url http://localhost:9292/hello.json 来测试我们的新 JSON 服务器,瞧:
$ curl http://localhost:9292/hello.json
{ message: "Hello!" }
它有效。伟大的!这是每个 Web 框架的基础,无论是 Rails 还是 Sinatra。在某些时候,他们实现了一个调用方法,遍历所有框架代码,最后以典型的 [status, headers, body] 形式返回响应。
例如,在 Ruby on Rails 中,机架请求命中 ActionDispatch::Routing.Mapper 类,如下所示:
module ActionDispatch
module Routing
class Mapper
...
def initialize(app, constraints, request)
@app, @constraints, @request = app, constraints, request
end
def matches?(env)
req = @request.new(env)
...
return true
end
def call(env)
matches?(env) ? @app.call(env) : [ 404, {'X-Cascade' => 'pass'}, [] ]
end
...
end
end
所以基本上 Rails 会检查,如果有任何路由匹配,则依赖于 env 哈希。如果是这样,它会将 env 哈希传递给应用程序以计算响应,否则它会立即以 404 响应。因此,任何符合机架接口约定的 Web 服务器都能够为完全成熟的 Rails 应用程序提供服务。
中间件
Rack 还支持创建中间件层。他们基本上拦截一个请求,用它做一些事情并传递它。这对于多功能任务非常有用。
假设我们想向我们的 JSON 服务器添加日志记录,该服务器还测量请求花费的时间。我们可以简单地创建一个中间件记录器来执行此操作:
class RackLogger
def initialize(app)
@app = app
end
def call(env)
@start = Time.now
@status, @headers, @body = @app.call(env)
@duration = ((Time.now - @start).to_f * 1000).round(2)
puts "#{env['REQUEST_METHOD']} #{env['REQUEST_PATH']} - Took: #{@duration} ms"
[@status, @headers, @body]
end
end
当它被创建时,它会为自己保存一份实际机架应用程序的副本。在我们的例子中,这是我们的 JSONServer 的一个实例。 Rack 自动调用中间件上的 call 方法,并期望返回一个 [status, headers, body] 数组,就像我们的 JSONServer 返回一样。
所以在这个中间件中,以起点为起点,然后使用@app.call(env) 进行对JSONServer 的实际调用,然后记录器输出记录条目,最后返回响应为[@status, @headers, @body]。
要让我们的小 rackup.ru 使用这个中间件,像这样添加一个 use RackLogger:
class JSONServer
def call(env)
[200, {"Content-Type" => "application/json"}, ['{ "message" : "Hello!" }']]
end
end
class RackLogger
def initialize(app)
@app = app
end
def call(env)
@start = Time.now
@status, @headers, @body = @app.call(env)
@duration = ((Time.now - @start).to_f * 1000).round(2)
puts "#{env['REQUEST_METHOD']} #{env['REQUEST_PATH']} - Took: #{@duration} ms"
[@status, @headers, @body]
end
end
use RackLogger
map '/hello.json' do
run JSONServer.new
end
重新启动服务器,瞧,它会在每个请求上输出一个日志。 Rack 允许您添加多个按添加顺序调用的中间件。这只是在不更改机架应用程序核心的情况下添加功能的好方法。
机架 - 宝石
虽然 rack - 首先 - 是一种约定,但它也是一个提供强大功能的 gem。其中之一我们已经用于我们的 JSON 服务器,rackup 命令。但还有更多! rack gem 为许多用例提供了很少的应用程序,例如提供静态文件甚至整个目录。让我们看看我们如何提供一个简单的文件,例如一个位于 htmls/index.html 的非常基本的 HTML 文件:
<!DOCTYPE HTML>
<html>
<head>
<title>The Index</title>
</head>
<body>
<p>Index Page</p>
</body>
</html>
我们可能想从网站根目录提供这个文件,所以让我们将以下内容添加到我们的 config.ru:
map '/' do
run Rack::File.new "htmls/index.html"
end
如果我们访问http://localhost:9292,我们会看到我们的 html 文件完美呈现。这很容易,对吧?
让我们通过在 /javascripts 下创建一些 javascript 文件并将以下内容添加到 config.ru 来添加整个 javascript 文件目录:
map '/javascripts' do
run Rack::Directory.new "javascripts"
end
重新启动服务器并访问http://localhost:9292/javascript,您将看到现在可以从任何地方直接包含的所有 javascript 文件的列表。