前言
sinatra是一个基于ruby语言,能够快速构建web应用的轻量级框架。它既可以构建一个完整的web应用,也可以当作一个中间件 来使用,在cloud foundry中大量地使用sinatra来处理组件之间的HTTP交互,就是将sinatra作为一个中间件来实现的。
DSL
- 路由
sinatra的路由机制看起来非常的简单直观,一般说路由都会从最简单的sinatra应用说起:
require 'sinatra'
get '/' do
'Hello world!'
end
get表示是http的请求形式,它是基于REST的消息机制,一般http的请求形式包括get(获取),put(更新), post(创建),delete(删除)等常用的四种形式。‘/’表示是路由选项,它通过匹配url来选择执行,block 里面的“hello world!”是返回的字符串。
参数:sinatra支持正则表达式匹配路由,如:
get '/hello/:name' do
# matches "GET /hello/foo" and "GET /hello/bar"
# params[:name] is 'foo' or 'bar'
"Hello #{params[:name]}!"
end
get '/say/*/to/*' do
# 匹配 /say/hello/to/world
params[:splat] # => ["hello", "world"]
end
get %r{/hello/([\w]+)} do
"Hello, #{params[:captures].first}!"
end
当然上述参数也可以通过给block传入参数来实现。
条件:所谓condition就是sinatra相应请求的条件,你可以为其指定agent(浏览器),host_name(访问 主机)等路由匹配条件。
返回值:一个合理的Rack响应都是被支持的,但是一般都是使用String来做返回值。来看一个Array返 回的例子:
class Stream
def each
100.times { |i| yield "#{i}\n" }
end
end
get('/') { Stream.new }
- 模板
如果是静态的网页放置在./public目录中,如果想要修改可以通过set指令来修改:public_folder,而模板 则功能强大得多,它能够支持在模板中插入变量的形式来定制模板,template位于./views中。当然也可以 通过set来修改:views从而制定模板文件夹,来看一则erb的模板文件:
require 'erb'
get '/' do
erb :index
end
它能够返回./views/index.erb文件。
下面我们来看如何在模板中嵌入变量:
get '/' do
@index = 0
erb :template,:locals => {:index => @index}
end
上面会把template上面的index变量替换成具体的值。
- 过滤器
顾名思义,过滤器就是在路由请求之前或者之后对请求进行修改,一般有两种形式:before和after
before do
@note = 'Hi!'
request.path_info = '/foo/bar/baz'
end
after do
body “#{response.status}”
end
在before中设置的变量是在request的整个生命周期都是可用的。
- 辅助方法
可以使用顶层的helpers来定义方法,这些方法在路由处理的过程中可用
helpers do
def bar(name)
"#{name}bar"
end
end
get '/:name' do
bar(params[:name])
end
上例中就可以在get中使用helpers定义的方法。
- 重定位
当request匹配到一个路由时,有时需要将其重定位到另外一个路由处理器中,有多种方法可以实现:
get '/guess/:who' do
pass unless params[:who] == 'Frank'
'You got me!'
end
get '/foo' do
status, headers, body = call env.merge("PATH_INFO" => '/bar')
[status, headers, body.map(&:upcase)]
end
get '/foo' do
redirect to('/bar')
end
当然你也可以在before中就指定路径
request对象
request.body # 被客户端设定的请求体(见下) request.scheme # "http" request.script_name # "/example" request.path_info # "/foo" request.port # 80 request.request_method # "GET" request.query_string # "" request.content_length # request.body的长度 request.media_type # request.body的媒体类型 request.host # "example.com" request.get? # true (其他动词也具有类似方法) request.form_data? # false request["SOME_HEADER"] # SOME_HEADER header的值 request.referrer # 客户端的referrer 或者 '/' request.user_agent # user agent (被 :agent 条件使用) request.cookies # 浏览器 cookies 哈希 request.xhr? # 这是否是ajax请求? request.url # "http://example.com/example/foo" request.path # "/example/foo" request.ip # 客户端IP地址 request.secure? # false(如果是ssl则为true) request.forwarded? # true (如果是运行在反向代理之后) request.env # Rack中使用的未处理的env哈希
中间件
我们有时候并不需要全部的sinatra框架来帮我们完成一些无需界面的交互过程,这个时候使用Sinatra: Base就是最佳的选择。我们先来看一个实例:
require 'sinatra/base'
class LoginScreen < Sinatra::Base
enable :sessions
get('/login') { haml :login }
post('/login') do
if params[:name] = 'admin' and params[:password] = 'admin'
session['user_name'] = params[:name]
else
redirect '/login'
end
end
end
class MyApp < Sinatra::Base
# 在前置过滤器前运行中间件
use LoginScreen
before do
unless session['user_name']
halt "Access denied, please <a href='/login'>login</a>."
end
end
get('/') { "Hello #{session['user_name']}." }
end
因为每个请求进入都会有相应的应用实例被创建,而set是类层面的方法,所以在使用变量的时候得注意 请求变量作用域,一般在本请求中可见。
在使用sinatra作为中间件的时候得注意区分应用变量域与请求变量域,应用变量域是类层面的,请求变 量域是实例层面的,这与ruby中类和多个实例的作用域类似,需要理解它才能在使用的时候不犯错误。