sinatra 0.3.0 源码学习

声明

本文系 sinatra 源码系列第 5 篇。系列的目的是通过 sinatra 学习 ruby 编程技巧。文章按程序运行的先后顺序挑重点分析,前一篇文章分析过的略去不说。水平很有限,所写尽量给出可靠官方/讨论链接,不坑路人。

重要提醒

一定要先安装 1.8 版本的 ruby ,因为 1.9+ 的 ruby ,String 的实例是不响应 each 方法的,这会直接导致 rack 报错。可以使用 rvm 安装 1.8.7 版本的 ruby ,如果使用 rvm ,请先升级到最新版本,否则安装 1.8.7 的 ruby 时也会报错。

列一下本人运行 sinatra 0.3.0 用到的 ruby 和关键 gem 的版本:

  • ruby-1.8.7-p374
  • rack 1.4.1
  • mongrel 1.1.5

本文主要内容

  • routes splat
  • ResponseHelpers
  • middleware

routes splat

上一篇文章说到 sinatra 没有保存路由中由通配符 * 捕获的参数,这个版本用一个数组保存下来,可以用 params['splat'] 来访问。具体的实现代码:

PARAM = /(:(#{URI_CHAR}+)|\*)/.freeze unless defined?(PARAM)
SPLAT = /(.*?)/
#...
splats = 0
regex = @path.to_s.gsub(PARAM) do |match|
  # match 匹配 /(:(#{URI_CHAR}+)|\*)/
  if match == "*"
    @param_keys << "_splat_#{splats}"
    splats += 1
    SPLAT.to_s
  else
    # 如 /(.)(.)(\d(\d))/.match("THX1138.").captures => ["H", "X", "11", "1"]
    # $1 捕获 (:(#{URI_CHAR}+))
    # $2 捕获 (#{URI_CHAR}+)
    @param_keys << $2
    "(#{URI_CHAR}+)"
  end
end
#...
path_params = param_keys.zip($~.captures.map{|s| unescape(s)}).to_hash
params.merge!(path_params)
splats = params.select { |k, v| k =~ /^_splat_\d+$/ }.sort.map(&:last)
unless splats.empty?
  params.delete_if { |k, v| k =~ /^_splat_\d+$/ }
  params["splat"] = splats
end

ResponseHelpers

这里定义的几个方法,可以在路由时使用,如 redirect

get '/' do
  redirect '/home'
end

get '/home' do
  'welcome!'
end

last_modifiedentity_tag 旨在节省网络流量(和节省计算资源,注释是这样说的),假如客户端请求的资源没有发生变化,就返回 304 Not Modified 。

last_modified 大体的实现就是在定义路由时,把响应资源的最后修改时间通过响应头传到浏览器,浏览器再次访问时会在请求头带有上一次请求时得到的时间字段,这时再判断响应资源的最后修改时间与传过来的时间时否一致,如果一致则直接抛出异常,返回 304 。

entity_taglast_modified 差不多,只不过比较的不是时间,而是更细粒度、更精确的标记。这个标记可以是用散列函数对资源求值得到哈希值,也可以是硬编码的版本号。

以上两个方法只设置响应头和比较请求头,把时间和标记交由用户管理,这不是很智能。 Rails 用户无需在意某个请求涉及到的一系列资源有没有更新,只要它们都没有更新,前端再次请求时就会得到 304 ,只要更新了一个资源( partial 或者 layout ),再次请求就会得到最新的响应。

middleware

中间件的概念比较模糊,可以看这里。简单来说,中间件可以帮你处理比如验证授权、缓存、打 log 等等的事情,这样你可以专心写业务逻辑。

如果你还是觉得困惑,强烈推荐你看这篇文章,它用简单明了的代码在实现中间件的层层调用时,还说清楚了中间件的原理。

上一个版本的 sinatra 就已经用到中间件:

def build_application
  app = application
  app = Rack::Session::Cookie.new(app) if Sinatra.options.sessions == true
  app = Rack::CommonLogger.new(app) if Sinatra.options.logging == true
  app
end

上面的 Rack::Session::CookieRack::CommonLogger 都是中间件。中间件有如下特征:

  • 能响应 new 方法, new 方法的参数是下一个中间件或者是应用;
  • 能响应 call 方法,call 方法的参数是 env ,即 rack 的环境变量。

这个版本的 sinatra 维护一个数组变量 middleware

def middleware
  optional_middleware + explicit_middleware
end

optional_middleware 是由 sinatra 提供的可选的中间件,处于 middleware 的前面位置, explicit_middleware 是由用户自定义的中间件,用户每次调用 use 都会往这个数组的末尾插入新增的中间件。

每一个请求到来时,最终会调用: pipeline.call(env)pipeline 是把所有中间件以及业务处理器层层串连起来得到的新应用:

def pipeline
  @pipeline ||=
    middleware.inject(method(:dispatch)) do |app,(klass,args,block)|
      klass.new(app, *args, &block)
    end
end

@pipeline ||= ... 的写法使调用 use 时重置 pipeline 变得很简单,只要写 pipeline = nil 就行。

injectreduce 一样,根据给出的初始值,遍历处理数组的元素,记住每次处理的结果,并把它传到下一次处理中。

method(:dispatch) 是业务处理器,请求经过一系列中间件最后会到达此处。它作为 Method 的实例对象,能响应 call 方法。

可以看到,请求最先被用户自定义的中间件处理,然后是 sinatra 提供的中间件,最后是业务处理器。处于 middleware 数组末尾的中间件最先起作用。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,937评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,503评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,712评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,668评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,677评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,601评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,975评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,637评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,881评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,621评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,710评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,387评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,971评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,947评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,189评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,805评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,449评论 2 342

推荐阅读更多精彩内容