webrick源码分析──路由

25 Jan 2010

webrick的路由是由WEBrick::HTTPServer::MountTable定义的

MountTable由@tab和@scanner组成,@tab是一个由script_name到Servlet的Hash,@scanner一个可以匹配所有script_name的正则表达式。其定义如下:

class MountTable
  def initialize
    @tab = Hash.new
    compile
  end

  def [](dir)
    dir = normalize(dir)
    @tab[dir]
  end

  def []=(dir, val)
    dir = normalize(dir)
    @tab[dir] = val
    compile
    val
  end

  def delete(dir)
    dir = normalize(dir)
    res = @tab.delete(dir)
    compile
    res
  end

  def scan(path)
    @scanner =~ path
    [ $&, $' ]
  end
end

MountTable只提供了四个方法:

[] 根据script_name获取相应的Servlet []= 定义scrpt_name与Servlet的对应关系 delete 删除script_name到Servlet的映射 scan 根据request的path返回相应的script_name和path_info

另外normalize和compile是MountTable的私有方法,normalize会删除url最后的'/',compile生成可以匹配所有script_name的正则表达式

看完定义之后,先来看看我们是如何定义路由的

1. 定义根目录

doc_root = '/home/flyerhzm'
server.mount("/", WEBrick::HTTPServlet::FileHandler, doc_root, {:FancyIndexing=>true})

2. 定义任意目录

cgi_dir = '/home/flyerhzm/cgi-bin'
server.mount("/cgi-bin", WEBrick::HTTPServlet::FileHandler, cgi_dir, {:FancyIndexing=>true})

上面定义了两个由FileHandler处理的路由,当path为/'时,在'/home/flyerhzm'目录下查找相应的文件,当path为'/cgi-bin'时,在'/home/flyerhzm/cgi-bin'目录下查找相应的文件,选项:FancyIndexing=true表示,在path对应为某个目录时,显示目录下的所有文件。对应到MountTable的@tab为

""=>[WEBrick::HTTPServlet::FileHandler, ["/home/flyerhzm", {:FancyIndexing=>true}]],
/cgi-bin=[WEBrick::HTTPServlet::FileHandler, [/home/flyerhzm/cgi-bin, {:FancyIndexing=true}]]

3. 定义Servlet路径

class GreetingServlet  WEBrick::HTTPServlet::AbstractServlet
  def do_GET(req, resp)
    if req.query['name']
      resp.body = #{@options[0]} #{req.query['name']}. #{@options[1]}
      raise WEBrick::HTTPStatus::OK
    else
      raise WEBrick::HTTPStatus::PreconditionFailed.new(missing attribute: 'name')
    end
  end
  alias do_POST do_GET
end
server.mount('/greet', GreetingServlet, 'Hi', 'Are you having a nice day?')

当path为'/greet'时,由GreetingServlet来处理,选项options = ['Hi', 'Are you having a nice day?'],其对应到MountTable的@tab为

"/greet"=>[GreetingServlet, ["Hi", "Are you having a nice day?"]]

4. webrick还可以mount一个proc

server.mount_proc('/myblock') {|req, resp|
  resp.body = a block mounted at #{req.script_name}
}

当path为'/myblock'时,执行这个proc,其对应到MountTable的@tab为

"/myproc"=>[#WEBrick::HTTPServlet::ProcHandler:0x5ce54 @proc=#Proc:0x00026c8c@webrick_test.rb:18>>, []]

接下来,让我们看看webrick是如何执行mount操作的

在httpserver初始化的时候,执行

@mount_tab = MountTable.new
if @config[:DocumentRoot]
  mount(/, HTTPServlet::FileHandler, @config[:DocumentRoot],
        @config[:DocumentRootOptions])
end

初始化MountTable,同时检查DocumentRoot参数是否设置,如果设置的话,就mount到根目录

mount, mount_proc和unmount方法定义如下

def mount(dir, servlet, *options)
  @logger.debug(sprintf(%s is mounted on %s., servlet.inspect, dir))
  @mount_tab[dir] = [ servlet, options ]
end

def mount_proc(dir, proc=nil, block)
  proc ||= block
  raise HTTPServerError, must pass a proc or block unless proc
  mount(dir, HTTPServlet::ProcHandler.new(proc))
end

def unmount(dir)
  @logger.debug(sprintf(unmount %s., dir))
  @mount_tab.delete(dir)
end
alias umount unmount

非常简单,只是调用MountTable提供的方法。

然后来看看webrick是如何根据url来找到相应的servlet。其关键是search_servlet方法

def search_servlet(path)
  script_name, path_info = @mount_tab.scan(path)
  servlet, options = @mount_tab[script_name]
  if servlet
    [ servlet, options, script_name, path_info ]
  end
end

参数path就是request的path,经过MountTable#scan解析,分解为script_name和path_info,而通过script_name就能从MountTable中获取servlet类型和选项,WEBrick再根据这个servlet类型和选项,实例化一个servlet,执行用户请求。

Tags 


blog comments powered by Disqus