学习和理解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,这样就可以输出自定义的错误页面了。

Linux经验随手记(旧)

这是10年的老文了。:)

其实谈不上什么经验,自己也是在使用Linux的过程中学习到不少东西。在使用Linux的过程中, 常常会冒出“自己好弱”的感觉,与人交流的过程中学到新东西,总会感觉很兴奋 ,而跟人讲解的时候,也总是希望可以得到一点反馈从而加深自己的理解~~(感觉)

工作会促使你不断去学习,而学习的效果会反应在你的工作上。 如果你对学习,工作的内容感兴趣的话,那肯定会让学习工作的效果更明显。应了那句话,兴趣是最好的老师~~(学习与工作)

学习linux,需要抛弃windows那套惯性思路。每个系统都有它的长处和短处,重要的是扬长避短,而不是取长补短。 相对windows来说,一开始会觉得Linux麻烦,使用不方便,很多工作要绕一个大弯才能解决问题。只有坚持学习,坚持使用linux,才能逐渐体会linux的好处。 就现在而言,linux主要的领域还是服务器方面,而windows的桌面一直都是非常棒的~~(linux与windows)

和linux打交道,基本是有两条路的,要么就是linux开发,要么是linux管理;linux开发嘛,几乎都是c的天下了。 咱们不是专门搞这个的,是其他语言+linux管理的搭配方式,主要用的也是shell和python,ruby,perl等等(我说的也是这个方向的)。 两者的区别还是比较大的,所以学习linux,最好还是好好考虑选择哪个方向把~~(关于linux学习的方向)

我第一次接触linux是在大学的时候,看到隔壁宿舍一大牛同学在玩linux,一个redhat8的发行版。 又上网去查查看,觉得蛮有趣的样子,借了光盘自己也装了双系统。不过好景不长呀,玩桌面的劲头很快就过去了,很快我就遗忘了linux, 重新回到windows。到了后来硬盘空间不够了(60g的盘),删除linux的时候,还把整个硬盘都给格式化了, 后来给网易的人取笑了一番。现在都觉得自己当时好糗呀~~(ps:我想很多人和我一样,linux的门找错了)

到后来要走上工作岗位了,才第一次听到李老大提到centos,fedora这些发行版(我真是孤陋寡闻呀,汗~~)。不过这个时候学习的方向总算不会偏离太多了, 而且这个时候空闲的时间也比较多(我是被遗忘的一族)。为什么说方向没有偏离太多,这是因为我根据网上一份优秀的教程来学习的,每天有空就在机器上捣鼓捣鼓。 虽然我不是很清楚学这些东西以后可不可以用上,但直觉告诉我,总不会没有价值的。这个时候我使用的是fedora8,教程就是鸟哥的私房菜了。 虽然说鸟哥的私房菜是很有用,但是熟能生巧,很多东西没用到,忘记也得特别快,体会也不会非常深刻。即使如此,我还是蛮有热情继续学下去,至少找到一点点门道了,在有一年春节回家前, 去书店买了本Linux技术管理手册(第二版),那个时候还没有中文版出来,咬咬牙在春节的时候把主要部分都读了一遍。 这个时候基本上是个人兴趣啦,毕竟工作上基本没什么机会使用到(ps:好的教材有必要,学习需要坚持)

后来也不知什么时候了,在工作中接触到越来越多的linux相关的东西,例如环境搭建,应用部署,安全与监控,分析与调优等。 这个时候也是个不断探索,加深印象的过程。这段时间看到自己学习的东西能够派得上用场,的确是很幸福的事情。 现在把工作环境都搬到centos,也没觉得有什么不习惯的。从我看来,熟悉linux的命令和目录结构是有效使用Linux的重要标志了。 呵,装个系统,然后开始linux之旅吧。下面的推荐书籍应该可以让你搭上linux 的“贼船”了。进阶的话,还是要不断通过充实理论和实践经验来获得的,linux博大精深,各自修炼,加强交流吧~~

推荐书籍:

  • Linux新手管理员指南
  • Linux系统管理员指南
  • Linux网络管理员指南
  • Linux操作系统之奥秘
  • Linux系统架构与目录解析(这本书我没看过,看邱老师的书另外一本感觉不错)
  • 高级shell 脚本编程指南
  • 鸟哥的Linux私房菜
  • Linux技术管理手册
  • Linux 目录结构简介(鄙人的,见笑见笑~~)