经验大分享:Sinatra源码学习

简介: 经验大分享:Sinatra源码学习

Sinatra是一个ruby的轻量级Web框架,这这个框架总共就1000多行代码,非常简洁,值得一读。

一、Sinatra组成

Sinatra的主要实现的代码在base.rb中,主要有以下几个部分:

Request:继承于Rack::Request,用于描述一个请求,通过这个类可以很方便获取到请求 中的各个CGI参数

Response:继承于Rack::Response,用于描述一个响应

CommonLogger:继承于Rack::CommonLogger,日志系统

Helpers:一些辅助方法,可以在用户编写的路由、前置∕后置过滤器或者视图中使用,用 户可以使用这里面的方法实现修改响应的status、header、body等,而且还可以进行重定 向,上传文件,访问session,操作缓存等功能

Templates:主要用于模板渲染

Base:Sinatra的主要实现的部分,是所有Sinatra应用程序和中间件的基类,这个类实现了call方法,符合Rack应用程序的标准

Application:Base的子类

Delegator:Application的代理,通过将其混入到main object中,我们可以使用诸如get、 post等Application中的方法

Wrapper:对整个Sinatra的一个包装

二、Sinatra应用的启动流程

Sinatra应用的启动分为这么几步: 路由注册——>用户的运行参数设置——>启动服务 我们接下来一块一块看

1、路由注册

这一步就是将用户写的类似于下面这样的路由代码注册进来:

get('/') do

'this is a simple app'

end

这些方法对应于HTTP的各个方法,对于get方法,他会同时定义GET和HEAD这两个句柄:

def get(path, opts = {}, block) conditions = @conditions.dup route('GET', path, opts, block)

@conditions = conditions

route('HEAD', path, opts, block) end

def put(path, opts = {}, bk) route 'PUT',

def post(path, opts = {}, bk) route 'POST',

def delete(path, opts = {}, bk) route 'DELETE', path, opts, bk end

def head(path, //代码效果参考:http://www.zidongmutanji.com/bxxx/317124.html

opts = {}, bk) route 'HEAD', path, opts, bk end

def options(path, opts = {}, bk) route 'OPTIONS', path, opts, bk end

def patch(path, opts = {}, bk) route 'PATCH', path, opts, bk end

def link(path, opts = {}, bk) route 'LINK', path, opts, bk end

def unlink(path, opts = {}, bk) route 'UNLINK', path, opts, bk end

由上面可以看到这些方法都调用了route这个方法,所以我们重点看下这个方法的实现

def route(verb, path, options = {}, block)

# Because of self.options.host

host_name(options.delete(:host)) if options.key?(:host)

enable :empty_path_info if path == "" and empty_path_info.nil?

signature = compile!(verb, path, block, options)

(@routes【verb】 ||= 【】) [ signature

invoke_hook(:route_added, verb, path, block) signature

end

这个方法的参数介绍如下:

verb:HTTP方法

path:URL路径

option:用户的路由中的设置的匹配条件

block:路由中的代码块

route方法主要就是生成路由的签名然后保存在routes对应的http方法的数组中,所以这段代码中关键的一处是:

signature = compile!(verb, path, block, options)

我们进去看下compile!方法:

def compile!(verb, path, block, options = {})

options.each_pair { |option, args| send(option, args) }

method_name = "#{verb} #{path}"

unbound_method = generate_method(method_name, block)

pattern, keys = compile path

conditions, @conditions = @conditions, 【】

wrapper = block.arity != 0 ?

proc { |a,p| unbound_method.bind(a).call(p) } :

proc { |a,p| unbound_method.bind(a).call }

wrapper.instance_variable_set(:@route_name, method_name)

【 pattern, keys, conditions, wrapper 】

end

这个函数主要有两个作用,一个就是根据verb path 以及block做了一个以”#{verb} #{path}”为 函数名,block为函数体的非绑定的方法,然后将这个方法和proc对象wrapper绑定起来,另 一个作用相信大家也看到了:

pattern, keys = compile path

这个compile方法是用来解析URL匹配范式,根据我们写的URL得出匹配符用于将来处理请求时 正则匹配对应的路由,而且将我们URL中的具名参数解析了出来,将具名参数名作为键,方便 我们直接访问这些参数。

2、用户运行参数设置、服务启动

这块的实现主要在main.rb中

module Sinatra

class Application [span style="color: rgba(0, 0, 0, 1)">; Base

# we assume that the first file that requires 'sinatra' is the

# app_file. all other path related options are calculated based

# on this path by default.

set :app_file, caller_files.first || $0

set :run, Proc.new { File.expand_path($0) == File.expand_path(app_file) }

if run? ; ARGV.any?

require 'optparse'

OptionParser.new { |op|

op.on('-p port', 'set the port (default is 4567)') { |val| set :port, Integer(val) }

op.on('-o addr', "set the host (default is #{bind})") { |val| set :bind, val }

op.on('-e env', 'set the environment (default is development)') { |val| set :environment, val.to_sym }

op.on('-s server', 'specify rack server/handler (default is thin)') { |val| set :server, val }

op.on('-x', 'turn on the mutex lock (default is off)') { set :lock, true }

}.parse!(ARGV.dup)

end

end

at_exit { Application.run! if $!.nil? Application.run? }

end

这里就是利用ruby类的开放性,动态的给Application增加了一些代码,当Application被加载 时,就会执行这里面的代码,这块代码就做了两件事:设置命令行参数,启动服务,命令行参 数这块很简单,我们直接看启动服务这块:

at_exit { Application.run! if $!.nil? Application.run? }

大家注意到这里用到了at_exit这个方法,这个方法作用就是在整个ruby程序结束后再执行 block中的代码,为什么要这么做呢?原因很简单,这里就相当于Sinatra将我们所写的代码执 行完毕(路由注册,各种过滤器处理等等)后再启动服务。我们主要看下run!方法:

def run!(options = {}, block)

return if running?

set options

handler = detect_rack_handler

handler_name = handler.name.gsub(/.::/, '')

server_settings = settings.respond_to?(:server_settings) ? settings.server_settings : {}

server_settings.merge!(:Port => port, :Host => bind)

begin

start_server(handler, server_settings, handler_name, block)

rescue Errno::EADDRINUSE

$stderr.puts "== Someone is already performing on port #{port}!" raise

ensure

quit!

end

end

这块代码主要就是确定我们起服务所要用的服务器句柄(rack应用程序就是通过这些服务器句 柄来启动服务器的),然后设置好端口,IP,最后启动服务器。

三、请求处理

前面说了很多,不过都是相当于Sinatra的初始化,当我们服务端接收到请求后,Sinatra会怎 么做呢?

对于rack应用程序,当收到请求后,就会执行rack程序的call方法,前面我们看到了Base类中 就实现了call接口,我们进去看下这里面做了什么:

def call(env)

dup.call!(env)

end

def call!(env)

# :nodoc:

@env = env

@request = Request.new(env)

@response = Response.new

@params = indifferent_params(@request.params)

template_cache.clear if settings.reload_templates force_encoding(@params)

@response【'Content-Type'】 = nil

invoke { dispatch! }

invoke { error_block!(response.status) } unless @env【'sinatra.error'】

unless @response【'Content-Type'】

if Array === body and body【0】.respond_to? :content_type

content_type body【0】.content_type

else

content_type :html

end

end

@response.finish

end

我们直接看call!方法就行了,这里面一开始根据环境参数实例化request,然后实例化 response,接着获取具名参数对应的值。

@env = env

@request = Request.new(env)

@response = Response.new

@params = indifferent_params(@request.params)

前面准备工作完毕后,接下来就是真正执行我们请求的地方了

invoke { dispatch! }

我们进去看下dispatch!方法:

def dispatch!

invoke do

static! if settings.static? (request.get? || request.head?)

filter! :before

route!

end

rescue ::Exception => boom

invoke { handle_exception!(boom) }

ensure

begin

filter! :after unless env【'sinatra.static_file'】

rescue ::Exception => boom

invoke { handle_exception!(boom) } unless @env【'sinatra.error'】

end

end

这个方法其实就是调度请求,执行请求所对应的程序,具体实现在route!方法中:

def route!(base = settings, pass_block = nil)

if routes = base.routes【@request.request_method】

routes.each do |pattern, keys, conditions, block|

returned_pass_block = process_route(pattern, keys, conditions) do |args|

env【'sinatra.route'】 = block.instance_variable_get(:@route_name)

route_eval { block【args】 }

end

# don't wipe out pass_block in superclass

pass_block = returned_pass_block if returned_pass_block

end

end

# Run routes defined in superclass.

if base.superclass.respond_to?(:routes)

return route!(base.superclass, pass_block)

end

route_eval(pass_block) if pass_block

route_missing

end

前面我们说过,路由注册进来后呢,保存的数据结构是类似于这样的:

{‘GET’ => 【【pattern, keys, conditions, block】, 【pattern, keys, conditions, block】......】, ‘POST’ => 【......】}

所以下面这部分代码就是遍历查找匹配的路由,然后执行我们所写的那些语句块

routes.each do |pattern, keys, conditions, block|

returned_pass_block = process_route(pattern, keys, conditions) do |args|

env【'sinatra.route'】 = block.instance_variable_get(:@route_name)

route_eval { block【*args】 }

end

......

end

大家可能注意到,正是因为他这个遍历,所以我们所写的路由他的匹配顺序是有先后的,先定义的路由若能匹配上,那就会执行先定义的路由。

route!这个方法后面他还会执行我们这个应用的父类的对应的请求的路由,为什么会这么做 呢?我的理解可能是:如果我们利用Sinatra编写模块化应用,我们可能会有多个继承 Sinatra::Base的类,我们如果将其中一个类作为代理,那么我们真正的应用应该是代理的父 类,这样的话的,Sinatra就会先执行代理中的路由然后再执行我们应用中的路由,当然我也没 有这样写过哈,这仅仅是我的一些看法,如有不同想法,欢迎随时交流。

相关文章
|
3天前
|
缓存 中间件 调度
经验大分享:Sinatra源码学习
经验大分享:Sinatra源码学习
|
3天前
|
关系型数据库 应用服务中间件 PHP
程序员必知:学习Walle(一)
程序员必知:学习Walle(一)
|
4天前
技术经验分享:GMU简单使用一
技术经验分享:GMU简单使用一
|
12月前
|
安全 Java 编译器
Golang面试前三夜准备:细节沉淀
Golang面试前三夜准备:细节沉淀
|
2月前
|
存储 测试技术 开发工具
软件测试/测试开发|GitHub怎么用,这篇文章告诉你
软件测试/测试开发|GitHub怎么用,这篇文章告诉你
68 0
|
安全 前端开发 索引
谈一谈|MkDocs介绍及应用
谈一谈|MkDocs介绍及应用
220 0
|
Java 程序员
理论:第十一章:大厂程序员如何使用GitHub快速开发学习
理论:第十一章:大厂程序员如何使用GitHub快速开发学习
理论:第十一章:大厂程序员如何使用GitHub快速开发学习
|
调度 Ruby
Sinatra源码学习【下】
Sinatra是一个ruby的轻量级Web框架,这这个框架总共就1000多行代码,非常简洁,值得一读。 一、Sinatra组成 Sinatra的主要实现的代码在base.rb中,主要有以下几个部分: Request:继承于Rack::Request,用于描述一个请求,通过这个类可以很方便获取到请求
129 0
|
缓存 中间件 Ruby
Sinatra源码学习【上】
Sinatra是一个ruby的轻量级Web框架,这这个框架总共就1000多行代码,非常简洁,值得一读。 一、Sinatra组成 Sinatra的主要实现的代码在base.rb中,主要有以下几个部分: Request:继承于Rack::Request,用于描述一个请求,通过这个类可以很方便获取到请求
253 0
|
开发者 Python
编程人员必须入手的一些软件 | 手把手教你入门Python之三
XMind是绘制思维导图,也叫“脑图”的一个软件,对于我们学习任何东西都是非常有用的,推荐下载!
编程人员必须入手的一些软件 | 手把手教你入门Python之三