unzip引起disk full问题(旧)

很早的记录了,以前瞎整过很多东西,还好很多资料有整理,所以看上去还有点小用的就迁移过来吧。:)

因为184解压一个oracle dmp文件的时候出错,导致数据无法同步。今天尝试解决这个问题,在日志中看到是解压的时候, 出现write error (disk full?).continue?(y/m/^C)”。发现db.dmp原大小是2.05g,压缩后400m,但是使用unzip解压到2g的时候,就出现上述错误。 但是通过df也没有看到磁盘空间不足。这就有几样可能,有可能是系统限制了或者解压工具限制了。使用ulimit发现文件大小是没有做特别限制的。 搜索一番,有可能是出现unzip的对大文件解压的限制,参考http://osde.info/HowToUnzipLargeFiles,对unzip进行重新编译,结果就可以了。 后来使用which的时候,发现oracle的bin自带了一个unzip,而这个版本也是不支持大文件解压的,但是有人却把这个路径放到path上去了。 最后嘛,我担心改动path路径对oracle造成影响,就修改了恢复脚本的unzip路径。结果总算搞定了。

简略翻译修改版:

# 从http://www.info-zip.org/下载源代码
cd unzip-5.52
vi unix/Makefile

# 使用:/Linux on查找到下面这段描述
# Linux on 386 platform, using the assembler replacement for crc32.c. (-O4 and
# -fno-strength-reduce have virtually no effect beyond -O3. Add "-m486
# -malign-functions=2 -malign-jumps=2 -malign-loops=2" for Pentium [Pro]
# systems.)
linux: unix_make

# 在这段的下面可以找到并进行替换
CF="-O3 -Wall -I. -DASM_CRC $(LOC)"\
CF="-O3 -Wall -I. -DASM_CRC -DLARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 $(LOC)"\

# 保存后下面就是一些编译替换的后续工作了
make -f unix/Makefile linux
which unzip
cp unzip /usr/bin/unzip

关于gcc -D_FILE_OFFSET_BITS=64的描述(找不到原始出处了)

In a nutshell for using LFS you can choose either of the following: Compile your programs with “gcc -D_FILE_OFFSET_BITS=64”. This forces all file access calls to use the 64 bit variants. Several types change also, e.g. off_t becomes off64_t. It’s therefore important to always use the correct types and to not use e.g. int instead of off_t. For portability with other platforms you should use getconf LFS_CFLAGS which will return -D_FILE_OFFSET_BITS=64 on Linux platforms but might return something else on e.g. Solaris. For linking, you should use the link flags that are reported via getconf LFS_LDFLAGS. On Linux systems, you do not need special link flags. Define _LARGEFILE_SOURCE and _LARGEFILE64_SOURCE. With these defines you can use the LFS functions like open64 directly. Use the O_LARGEFILE flag with open to operate on large files. A complete documentation of the feature test macros like _FILE_OFFSET_BITS and _LARGEFILE_SOURCE is in the glibc manual (run e.g. “info libc ‘Feature Test Macros’”).

学习和理解servlet(旧)

08年写的笔记,看上去很不是太烂:)

简单应用服务器

经常会听到应用服务器的说法,主要作用就是能够根据http请求来分配资源。在java里边,就是跟java.net.*相关的socket编程。 更详细的内容需要查看相关内容。这里只是不想上当受骗,以为是某某魔术来着。

什么是规范

为了方便开发,应用服务器开发商只要实现相关的规范,使用者在使用这些api的时候根本不用考虑对应的服务器是怎样的。 简单来说,就是定义好的接口。而servlet和jsp就是这样一类规范。tomcat6.0.18是支持servlet2.5和jsp2.1的规范的。在源码里边实现了这套规范,相关的包是javax.servlet.*.

学习和理解servlet

servlet是一套比较简单的规范,单从tomcat的实现代码就可以知道。从可以得到的资料上看, 也没有多少复杂的内容。窃以为从书上就可以获取到基本的内容了,有个感性印象的基础读一遍 api doc就基本ok了。再多的东西,具体的应用实现,查阅cookbook之类的guice。 这部分内容相当重要,是java web最重要的基础

接下去的部分,基本上是api doc和tomcat实现的内容的梳理。即使再怎样,我都要经常翻阅资料,这是免不了的。 可以注意到servlet一共有两个包,javax.servlet和javax.servlet.http,明显就是一个是基础接口,一个是http协议的实现, 从里边的类名也可以看出,http包的类前面基本都加上Http前缀的。先对他们进行更详细的分类, 大概就是各个基础组件(context,request,response,servlet,filter,stream,listener),还有一系列对应的listener,几个http特殊的东西(session,cookie).下面分别来说,就怕有些乱,请见谅:

ServletConfig

servlet初始化的时候,web容器传递的一个servlet configuration object。

ServletContext

我们知道,每个jvm里边一个web应用(通常是一个war包,具有特定的部署结构)就对应一个context, 这个接口就是定义了context和servlet容器交互的一些方法。context是web应用全局可见的,因此ServletConfig就有个方法getServletContext来获取对应的context。

Servlet

所有的Servlet必须实现Servlet接口。一个servlet是一个跑在web服务器的java程序,用来接受请求,并做出响应, 对应的请求和响应就是ServletRequest和ServletResponse了。这个生命周期主要有三部分,对于一个jvm来说, servlet初始化的时候会调用init方法,容器负责把ServletConfig作为参数传递给这个方法,这个步骤对于一个生命周期来说只会做一次。 初始化之后,任何请求调用都会调用service方法,而ServletRequest和ServletResponse就作为两个参数传递进去了。 在容器关闭的时候,servlet将会给销毁,这个时候就会调用destroy方法,这个步骤跟init一样,也是调用一次的。 为了方便使用,已经GenericServlet和HttpServlet做了部分的实现,特别是HttpServlet,根据Http请求方法,分发给doGet,doPost等方法, 这就是为什么继承HttpServlet做servlet实现的时候,一般只是覆盖doGet或doPost方法就可以的原因,

ServletRequest,ServletResponse

在调用Servlet的service的方法时,会涉及ServletRequest和ServletResponse,这两个object是 由容器负责生成并传递进来的。简单的解释,ServletRequest是用来提供客户端的请求信息的,例如请求方法,参数,ip等等, 而ServletResponse就是用来返回给客户端的响应信息,一般来说,我们会通过getWriter(字符数据)或者getOutputStream(二进制)来输出信息。 当然,character encoding和content type也是放在ServletResponse的,你可以进行改变。一般来说,我们处理浏览器请求,几乎都是http协议, 所以几乎都是HttpServletRequest和HttpServletResponse。

FilterConfig,Filter,FilterChain

过滤器,在web.xml经常看到元素,它就是实现Filter的一些类而已。。可以注意到Filter接口跟Servlet有些像, 在初始化的时候,容器传递一个FilterConfig对象作为init方法的参数,而在销毁的时候会调用destroy方法。当有请求过来的时候,容器会拦截符合要求的请求(根据请求路径), 并调用doFilter方法,可以注意到除了平时看到的request和response参数,还有个FilterChain参数。这个对象是由容器负责维护,可以看做是这次请求需要经过的filter链。一般来说

before_filter :dosoming with request and response
chain.doFilter(request,response);//进入其他Filter
after_filter :dosoming with request and response

上面前后两部分不一定都会存在,大家有机会看看例子就知道了。filter的作用范围很大,一般用来做验证权限,记录日志,响应内容压缩,内容加密,特殊参数过滤,转换编码格式等等

Cookie,HttpSession

Http协议是一种无状态协议,所以有必要做点其他东西来保留状态。cookie带有简单信息的东东(key/value对,看起来就像个Map), 用来保存在浏览器的,然后每次请求的时候,又把cookie发送到服务器。可以通过ServletResponse的addCookie方法加入cookie。 需要注意的是,cookie具有一些特殊性,可以设置保留时间,是否加密等等,鉴于安全性考虑,一般只用来对付那些不是很重要的数据。 HttpSession说的是session,他的数据其实是保存在服务器的,服务器根据每个sessionid,保留一份对应的数据,客户端就是根据这个sessionid来作为钥匙的。 这个钥匙也是要像cookie那样传来传去的,这个时候的sessionid一般有两种交互方式,基于cookie(作为cookie的key/value对) 和基于url rewrite(在url后边带上个参数而已),常用的方法:setAttribute,getAttribute(跟ServletRequest的方法差不多),invalidate等等

Listener,Event

经常会在web.xml里边看到元素,对应的类是实现ServletContextListener接口的。其实这类Listener有不少,在servlet2.5规范里边有8个,而且有对应的Event类

ServletContextListener,ServletContextEvent 这是用来监听servlet context的,是在初始化和销毁的时候会触发相应的事件,并会有个ServletContextEvent对象 参数,通过这个参数的getServletContext方法就可以获取相应的ServletContext对象了。

ServletContextAttributeListener,ServletContextAttributeEvent 这个也是用来监听servlet context的,关注点是它里边的attribute的改变。一个类如果实现了 这个接口,那么在attribut发生变化的时候,就会触发相应的方法。

ServletRequestListener,ServletRequestEvent,ServletRequestAttributeListener,ServletRequestAttributeEvent 这些都是类似的,只不过这里是ServletRequest

HttpSessionListener,HttpSessionEvent,HttpSessionAttributeListener,HttpSessionEvent 类似,就是这两个listener用的是同一个Event

HttpSessionBindingListener,HttpSessionBindingEvent 一个类如果实现了这个接口,那么在他和session绑定或者解绑的时候就会触发相应的方法。

HttpSessionActivationListener,HttpSessionEvent 注意这里使用的也是那个Event。此接口处理从一个服务器移动到另一个服务器的会话,貌似是用在分布式环境的。

监听器还是很多类型的,注意区分,貌似需要用到的时候也不是很多,感觉没filter用得多

RequestDispatcher,SingleThreadModel

貌似这个dispatcher经常用来做请求转发(forward)或者资源内嵌(include)的工作。可以通过ServletRequest来获得一个转发对象。 使用forward可以确保attribute里边的信息不丢失,实现信息传递。 SingleThreadModel,因为Servlet并非线程安全,所以才有这么个东东来保证一个jvm只有一个线程可以同时访问。具体的看资料,这个东西是不推荐使用的, 理由很简单,性能不好罗。既然非线程安全,那么就要保留servlet的无状态性。。。。

要点:区分各个对象的生命周期和作用

其他讨论

  • 能否在一个ServletRequest对象中获取ServletConfig对象,为什么?
  • 讨论何时使用context,cookie,session,request传递信息。他们的存活期是怎样的?
  • SingleThreadModel的弊端和解决办法?
  • 上传文件和提交普通表单的处理方式?如何避免重复sumbit form?
  • 重定向和转发的区别,在servlet的里边的使用方法?

小议研究生系统升迁(旧)

09年底写的小文,记录一下。回想起来,别有一番滋味。再描述一下,项目小组共5人,3个写代码1个整理需求1个迁移数据,除了我还有2个女孩是写代码。 老系统大约20w行c sharp,改造后1w行不到的ruby。这么少的代码主要得益于ruby的简洁和rails高开发效率。现在认为,在数据库方面没有做大多努力, 有些sql效率需要改进(不过一般的表就也不是百万级的),有信心可以让整体性能提升一个级别。后来,我对oracle印象有改观(我本不愿意使用oracle), 另一同事对ruby印象也大为改观(他认为ruby只能小打小闹),总的来说,这次项目收获很多。

虽然我们还在忙乎着这个系统的升级工作,还在为客户没完没了的需求做努力,我们真是又累又烦了。真是因为这样,我们大多数人(我想应该说团队所有人)都希望逃离这个泥塘。 项目现状虽然是这样,但是咱们也要摆脱这些东西,来说说技术方面的东西。

有幸在项目中担任小小架构师的角色,应该说,我们在这次升级过程中技术上遇到了许多问题,但最终都算解决了,应该不会有什么不满吧? 其实对这样的项目,一开始也是徘徊在所谓强大的j2ee技术和所谓前卫的ruby on rails之间,也不知道最后是哪个领导拍板上ruby on rails了。 对于这种升级性系统(从.net到xx),需求方面还是比较稳定的,用所谓j2ee貌似也是可行的,至少我在前期也的确看不到ruby on rails有太明显的优势。 可是我还是对选择ruby on rails没什么意见,也正好是可以证明一下并非架构只此一家,这也算一个不错的机会吧。

再说到具体的点上,假如你见过原来系统数据库设计的蜘蛛网,就会明白我们在数据迁移方面的工作有多么艰难。 还有其他一些原因,我们没有选择耳熟能详的mysql,而是选择了oracle。也正因为数据方面的原因,我们不得不在程序方面做出让步(即使这样,ruby还算比较优雅)。 至少,我们让程序兼容了数据。ruby on rails+oracle的探索也是比较漫长的,前前后后也修改了几次适配器(如数据类型识别,一些数据库选项), 增加了一些插件来增加特性(如存储过程)。逆其道而行,真是吃力不讨好。虽然相对java来说,这些做法就像无聊的笑话一样,但是至少我们顺利的解决问题了。

随着项目深入带来的多如雨点的细节方面的调整,ruby on rails的优势才逐渐凸显出来。至少从我这个javaer来,区别是很明显的(俺也做过java项目维护,知道那是怎样一种体力活动)。 你不要好奇为什么细如牛毛的调整会这么多,可是事实就是这样了,咱也不解释了。在语言层面的强大真的可以节省不少精力,算起来也有n多功能,也才1w行代码。 按照我的经验,如果不幸用的是java,在这样战乱纷纷,弹尽粮绝的情况下,我们肯定会给java这门大炮压垮了。 在需求变更处理方面,动态语言的优势是相当明显呀,感谢MATZ,感谢DHH,感谢C-C-A-V。

现在项目逐渐稳定下来,我们考虑更多的是如果让我们的系统高效稳定的运行,能够及时处理bug。 关于性能方面,我们的项目是基于流行的lighttpd+fastcgi部署的,也算一个LAMP架构(最大的区别就是oracle代替mysql了), 而作为业务系统,流量到现在最多也是20w动态pv那样子。我们也没有使用什么特别的缓存策略,运行还是相当流畅的(这还是在内存从原来16g减少一半的情况下做到的)。 基本繁忙的时候,都是oracle在那忙活着,这也是普遍情况,瓶颈大多首先出现在数据库IO。 所以,这样的架构性能方面也是没有什么问题,稳定性也很好。还有一次好处,我们的更新非常频繁,而每次更新只用短短3s左右,几乎是无痛更新。 得益于Linux平台,我们在监控,流量分析方面也是游刃有余(不得不赞叹一下shell),另外得益于ruby on rails架构的灵活性, 可以做些调整方便我们监视运行状况(例如在top中查看真实URL,在单独的log中记录exception),这些都大大提高我们的效率。 这方面,大体上是windows和linux之间在服务器端的比较优势了。

现在回过头来看,在一开始打造的时候,我还是有些担心的,担心oracle和ruby的兼容,担心性能,担心处理ms格式(还好,excel和sqlserver至少是没什么大问题的)。 这也是一步步走过来的,现在就淡定多了。在项目前期,任何东西都不会有太大优势,只有到了中后期,才会有强烈的感觉。 另外,也杂七杂八的把以前自学的东西用上了一些,算是厚积薄发了一下,学以致用吧,感觉还是不错的。呵呵,感谢xy,感谢hnjk,感谢C-C-A-V :)

话说图形报表(旧)

这个是09年底写的。这几年更多看到关于html5、css3、javascript的图形展现显示,效果更好,功能也很强大, 随着html5、css3的普及,以后会有更多的应用出现。例如淘宝团队可视化JavaScript库[Datav.js][2],在数据分析展示方面效果就很不错。

在javaeye上看到有个新闻Highcharts:非常漂亮的图表API,自己也做过图形报表相关的工作,就此对图形报表发表一点自己的想法:

图形报表非常醒目

图形报表是一种富格式,粗粒度的报表表现方式。表格样式的报表容纳的信息比较细,没法像图形那样直观。 在一些不需要非常详细统计的情况下,图形报表的优势是非常明显的。在业务系统中融合图形报表,几乎就变成了一个必备需求。 一个普遍的可以想象的场景是,能够简单明了的展现统计信息趋势等,又能在必要的时候得到详细的统计数据,这就使得系统拥有混合多种报表表现形式的能力。

服务端与客户端的图形报表

我认为图形报表可以分成两部分,数据和渲染引擎。渲染引擎就主要是提供接口接受数据并把数据显示出来,或许还提供了一些接口让用户参与交互。 现在这个引擎的实现方式可谓多种多样,有些是语言相关的,例如birt,jreport等,他们大体上是通过数据源定制描述文件(例如通用的xml格式),然后自己解析并展现。 这种方式的通用性不是很强,因为他比较依赖于服务端环境,就是说你必须要在添加一套组件,多跑一套应用才能使用。 另外还有一些是基于客户端技术的,例如flash,css/js等,对于服务端来说只要提供数据就可以了,非常方便。因为这个原因,我更加推荐使用客户端技术。

flash图形报表的可用性

看了一些基于客户端的实现方式,个人是比较赞赏flash实现方式的。我是看重flash表现力非常强大,组件化,不懂flash也可以轻松弄出非常漂亮的报表。 如果要推荐一个的话,我觉得基于flash技术的FusionChart可以满足绝大多数的需求。像什么直线图,曲线图、区域图、区域曲线图、柱状图、饼图都可以轻松实现。 如果使用收费版本的话还有更加强大的功能(当然你也可以选择在网上找破解版),这类工具还可以轻松打印,提供多种格式转换,还可以动态改变报表。 这样你就发挥你的创造力了,例如配合ajax来展示个股分时图等等。

集成不是个问题

接上面的flash报表的话题。如果我们选择flash报表的话,我们可以预见的是,它本身就提供了一套适用性非常强的接口。 再加上这本来就是服务端语言无关的实现方式,从我的工作经验上看,单单这点不会是什么难题的。我觉得flash报表能解决90%的问题, 但是如何补充flash报表在功能上的缺陷?这才是我们要考虑的问题吧。我的想法是,一套flash报表不行,我用多一套其他的总可以了吧? 现在服务端主要的功能是提供数据,而客户端做的是接受数据并展示。如果使用多套客户端技术的话,那么服务端的数据再经过一套适配器转换即可。 管你使用的是flash,还是css/js,你只要定义数据适配器即可。虽然现在这些只是一些想法,而FusionChart也的确是满足了我所有的需求,但是我还是觉得这是完全可行的。

sinatra分析(旧)

这是10年初写的了:)

sinatra简介

  • Sinatra is a DSL for quickly creating web applications in Ruby with minimal。
  • Fewer classes, less inheritance
  • controller object mapping & routes vs. URLs—Dont’s fear the URLs
  • Exposed Simplicity instead of hidden complexity
  • Small things, loosely joined, written fast

sinatra分析

Rack机制

sinatra 作为一个web框架,是基于rack规范的。rack规范和Java的servlet规范有点类似,Rack中间件和filter机制有些类似, 都是能够拦截request/response做一些事情。所谓的rack兼容的中间件无非是一个可以执行 call(env) 的对象,详细关于rack的内容可以参考rack官网,还有这个rack入门文档也很好。 在源码中可以看到,sinatra的Request和Response都是基于rack扩展的,并对Rack::Request和Rack::Response分别做了一些调整。 sinatra是通过Application.run!来启动服务器的

         def run!(options={})
set options
handler = detect_rack_handler
handler_name = handler.name.gsub(/.*::/, '')
puts "== Sinatra/#{Sinatra::VERSION} has taken the stage " +
"on #{port} for #{environment} with backup from #{handler_name}" unless handler_name =~/cgi/i
handler.run self, :Host => bind, :Port => port do |server|
trap(:INT) do
## Use thins' hard #stop! if available, otherwise just #stop
server.respond_to?(:stop!) ? server.stop! : server.stop
puts "\n== Sinatra has ended his set (crowd applauds)" unless handler_name =~/cgi/i
end
set :running, true
end
rescue Errno::EADDRINUSE => e
puts "== Someone is already performing on port #{port}!"
end

其中detect_rack_handler是通过 Rack::Handler.get来检测rack处理器的,默认的server有thin/mongrel/webrick,绑定的地址是 0.0.0.0,端口是4567

    module Sinatra
class Base
set :server, %w[thin mongrel webrick]
set :bind, '0.0.0.0'
set :port, 4567
end
end
注意到handler.run self, :Host => bind, :Port => port do server ,这个self指的是Sinatra::Base,根据rack规范,最终的请求的入口就是 Sinatra::Base.call(env)方法
          def prototype
@prototype ||= new
end

# Create a new instance of the class fronted by its middleware
# pipeline. The object is guaranteed to respond to #call but may not be
# an instance of the class new was called on.
def new(*args, &bk)
builder = Rack::Builder.new
builder.use Rack::Session::Cookie if sessions?
builder.use Rack::CommonLogger if logging?
builder.use Rack::MethodOverride if method_override?
builder.use ShowExceptions if show_exceptions?
middleware.each { |c,a,b| builder.use(c, *a, &b) }

builder.run super
builder.to_app
end

def call(env)
synchronize { prototype.call(env) }
end

从call方法可以看到,是通过生成一个Sinatra::Base实例对象来运行的,最终会调用的是call(env) -> call!(env), 接下去的工作就是等客户端发送请求过来就可以了。在生成这个实例对象@prototype的时候,直接引入rack中间件机制, 同样,sinatra允许你使用use方法来增加新的中间件(use只是把中间件加入@middleware变量中去而已)。这样sinatra就已经启动起来了。

路由机制

sinatra 的路由机制和rails不大一样,sinatra是在controller里边用get/post path这样来指定的。 而rails是把controller和map分开处理,通过map来找到对应的controller和action。 rails当初这么搞主要是为了兼容controller和路由不匹配的情况,个人觉得sinatra的写法是非常直观的,也非常的灵活。

    delegate :get, :put, :post, :delete, :head, :template, :layout,
:before, :after, :error, :not_found, :configure, :set, :mime_type,
:enable, :disable, :use, :development?, :test?, :production?,
:helpers, :settings

看 main.rb可以看到include Sinatra::Delegator,可以把get/post等众多方法代理给Sinatra::Application去执行, 在后面使用get ‘/’ do xxx end的时候其实会调用Sinatra::Application(即Sinatra::Base)的get方法。

      require 'rubygems'
require 'sinatra'
get '/' do
'Hello world!'
end

例如这样一个简单的web应用就可以响应’/’的请求路径,那么Sinatra::Base是怎么识别到这个路由的呢?我们继续来看看上面的get方法做了什么事情, 可以看到最终是调用route方法的(同时,从代码可以看到sinatra支持get/post/put/post/delete/head几种method的请求)。 按照我们的大概思路,在看到某个请求方法的时候,sinatra会把{请求类型_路径 => 代码块}放到一个专门放路由的地方上去,然后在每一次请求调用call(env)的时候, 根据“请求类型_路径”来获得需要执行的代码块。好,继续看看 route的代码是怎么实现的?

          def route(verb, path, options={}, &block)
# Because of self.options.host
host_name(options.delete(:bind)) if options.key?(:host)
options.each {|option, args| send(option, *args)}

pattern, keys = compile(path)
conditions, @conditions = @conditions, []

define_method "#{verb} #{path}", &block
unbound_method = instance_method("#{verb} #{path}")
block =
if block.arity != 0
proc { unbound_method.bind(self).call(*@block_params) }
else
proc { unbound_method.bind(self).call }
end

invoke_hook(:route_added, verb, path, block)

(@routes[verb] ||= []).
push([pattern, keys, conditions, block]).last
end

这个代码处理的事情比较多,我们来仔细分析分析,前面两句代码是用来记录能够处理的请求的约束(例如特定的host_name,user_agent), 然后compile(path)的工作是把path换成一个正则表达式(这样通过match就可以获得匹配的组),还有提取keys(例如*的就变成 splat,:name就变成name)。 重要的是把get ‘/’ do xxx end动态生成一个”#{verb} #{path}”的方法并最终封装成一个带有上下文状态的proc对象, 最终是把[pattern, keys, conditions, block]加入@routes[verb]里边去。而call(env)能够处理请求就得靠这个@routes来实现。

先来看看call(env) -> call!(env),最重要的部分是invoke { dispatch! },可以看到dispatch!的整个流程是 判断并处理static文件 -> before_filter! -> route! -> after_filter!,主要的处理过程是route!方法

       def route!(base=self.class, pass_block=nil)
if routes = base.routes[@request.request_method]
original_params = @params
path = unescape(@request.path_info)

routes.each do |pattern, keys, conditions, block|
if match = pattern.match(path)
values = match.captures.to_a
params =
if keys.any?
keys.zip(values).inject({}) do |hash,(k,v)|
if k == 'splat'
(hash[k] ||= []) < values}
else
{}
end
@params = original_params.merge(params)
@block_params = values

pass_block = catch(:pass) do
conditions.each { |cond|
throw :pass if instance_eval(&cond) == false }
route_eval(&block)
end
end
end

@params = original_params
end

首先sinatra先从@routes里边取得符合请求类型的[pattern, keys, conditions, block]列表,然后逐个扫描, 通过pattern来match路径,如果符合的话,取得通配符,命名参数的值并封装到params去(得益于 compile(path)的工作)。 接下去判断conditions是否符合,如果都符合,则执行业务,即block。整个流程处理完之后,把 params恢复为原本的状态。

拦截器

在上面已经提到,sinatra的拦截器是通过before_filter!和after_filter!来执行的,如下所示:

        def before_filter!(base=self.class)
before_filter!(base.superclass) if base.superclass.respond_to?(:before_filters)
base.before_filters.each { |block| instance_eval(&block) }
end

配置过滤器也非常简单,定义一个前置过滤器,例如

      before do
@note = 'Hi!'
request.path_info = '/foo/bar/baz'
end

sinatra通过Sinatra::Base的before把block加入到@before_filters中去,这个应该很容易明白的。 不过,这个拦截器功能比起rails那个显得简陋了,毕竟不能直接针对某些路径进行拦截处理。

模板渲染

sinatra通过Tilt实现多模板的渲染机制,生成页面的过程是在业务代码块那里注明的,例如

      require 'erb'
get '/' do
erb :index
end

sinatra的模板方法是在Sinatra::Templates模块里边定义的,能够支持erb,erubis,haml,sass,less,builder,具体的实现如下:

        def render(engine, data, options={}, locals={}, &block)
# merge app-level options
options = settings.send(engine).merge(options) if settings.respond_to?(engine)

# extract generic options
locals = options.delete(:locals) || locals || {}
views = options.delete(:views) || settings.views || "./views"
layout = options.delete(:layout)
layout = :layout if layout.nil? || layout == true

# compile and render template
template = compile_template(engine, data, options, views)
output = template.render(self, locals, &block)

# render layout
if layout
begin
options = options.merge(:views => views, :layout => false)
output = render(engine, layout, options, locals) { output }
rescue Errno::ENOENT
end
end

output
end

具体的流程是先找到template engine,通过template的render方法渲染子页面,然后在把子页面的内容作为一个block参数放到渲染layout的render方法上去, 这样在父页面里边的yield就会被子页面的内容所取代,从而实现整体页面的渲染。

错误及状态处理

sinatra在这方面的处理,我觉得非常巧妙,还认识了一些从来没用过的api。几个重要的特性:

halt:

halt 410
halt 'this will be the body'
halt 401, 'go away!'
error:

error do
'Sorry there was a nasty error - ' + env['sinatra.error'].name
end
error MyCustomError do
'So what happened was...' + request.env['sinatra.error'].message
end
error 400..510 do
'Boom'
end

error的实现很简单,只是把error code和block记录到@errors上去,而not_found其实就是404的error了。halt从代码实现上看,它是throw一个halt的异常。 这些处理方式在sinatra最终是怎么处理的呢?我们先回到dispatch!这个主方法,从源码中可以看到如果是静态页面,会抛出halt(line 173),到了route!方法的时候,如下

                pass_block = catch(:pass) do
conditions.each { |cond|
throw :pass if instance_eval(&cond) == false }
route_eval(&block)
end

catch(args,&block) 这个方法是会忽视在遇到pass异常的时候忽略异常并跳出block的运行,所以conditions验证不通过的时候, 就会转入下一个pattern验证,而在验证通过后到了route_eval(&block) 就会抛出halt从而跳出循环,表示已经匹配成功。 抛出异常之后会在dispatch!通过rescue来处理。error_block!(*keys) 就是用来处理error的,@errors根据error code来获取block,这样就可以输出自定义的错误页面了。