rails的异常处理

2009-12-23 21:52:07 +0800

当在本地开发模式下发生异常的时候,rails会将错误发生的点、错误桟以及请求和应答的内容显示在浏览器上,并且在console下面打印出错误桟,这些使得调试web应用变得更容易。那rails内部是如何处理异常的呢?

rails把与ActionController相关的异常处理都定义在了ActionController::Rescue里面。其中最关键的是

alias_method_chain :perform_action, :rescue

perform_action是ActionController处理http请求的方法,它负责根据不同的请求调用相应的action。而上面这句话则为perform_action添加了处理异常的功能,看看具体的实现

def perform_action_with_rescue #:nodoc:
  perform_action_without_rescue
rescue Exception => exception
  rescue_action(exception)
end

可以看到,当perform_action执行发生异常时,通过rescue_action方法来处理异常。

整个过程的实现非常优雅,通过alias_method_chain为原来的perform_action增加了异常处理功能,却完全不用修改也不用关心perform_action原来的实现。rails的实现中大量使用了alias_method_chain,将功能点从方法的实现中剥离出来,有点AOP的思想。

最后看看本地和远程处理的不同

if consider_all_requests_local || local_request?
  rescue_action_locally(exception)
else
  rescue_action_in_public(exception)
end

还记得development.rb中有一句话是config.action_controller.consider_all_requests_local = true,就是在这里起作用的。如果是local_request,会显示所有的错误桟和请求应答消息,不然就只是显示404或500的静态页面。除非你重写rescue_action_in_public方法(比如exception_notification插件)

ruby gserver源码阅读

2009-12-21 22:48:23 +0800

gserver作为通用服务器的实现,最关键的就是start方法

  def start(maxConnections = -1)
    raise "running" if !stopped?
    @shutdown = false
    @maxConnections = maxConnections if maxConnections > 0
    @@servicesMutex.synchronize  {
      if GServer.in_service?(@port,@host)
        raise "Port already in use: #{host}:#{@port}!"
      end
      @tcpServer = TCPServer.new(@host,@port)
      @port = @tcpServer.addr[1]
      @@services[@host] = {} unless @@services.has_key?(@host)
      @@services[@host][@port] = self;
    }
    @tcpServerThread = Thread.new {
      begin
        starting if @audit
        while !@shutdown
          @connectionsMutex.synchronize  {
             while @connections.size >= @maxConnections
               @connectionsCV.wait(@connectionsMutex)
             end
          }
          client = @tcpServer.accept
          @connections << Thread.new(client)  { |myClient|
            begin
              myPort = myClient.peeraddr[1]
              serve(myClient) if !@audit or connecting(myClient)
            rescue => detail
              error(detail) if @debug
            ensure
              begin
                myClient.close
              rescue
              end
              @connectionsMutex.synchronize {
                @connections.delete(Thread.current)
                @connectionsCV.signal
              }
              disconnecting(myPort) if @audit
            end
          }
        end
      rescue => detail
        error(detail) if @debug
      ensure
        begin
          @tcpServer.close
        rescue
        end
        if @shutdown
          @connectionsMutex.synchronize  {
             while @connections.size > 0
               @connectionsCV.wait(@connectionsMutex)
             end
          }
        else
          @connections.each { |c| c.raise "stop" }
        end
        @tcpServerThread = nil
        @@servicesMutex.synchronize  {
          @@services[@host].delete(@port)
        }
        stopping if @audit
      end
    }
    self
  end

第5-13行检查host的port是否被占用,如果没有的话,则根据host和port实例化TCPServer,并通过@@services[@host][@port]记录。

第14-65行将服务器的服务交由一个单独的线程处理,这样当当前进程退出的时候,该线程也会被强制退出。

第18-22行和第35-38行是用来管理线程池的,当要加入一个新的线程之前,检查当前的线程数量,如果达到线程数最大值,则挂起当前线程;当删除一个线程时,通知某个被挂起的线程继续执行。

第23-41行是一个tcp服务的全过程,获取client的连接,启动一个线程来处理当前的连接,执行serve方法,最终关闭当前的连接。

第46-63行是tcp服务器关闭时执行的过程,服务器关闭有两种方法:

  一是shutdown,tcp服务器会等待所有的线程都执行完毕再关闭

  二是close,tcp服务器会中断所有的线程,强制关闭

其它的方法,如shutdown, close都写得很简单,不再一一叙述。

ruby通用服务器gserver

2009-12-17 22:45:51 +0800

之前随手翻了几页Programming Ruby 1.9,看到了gserver,查看ruby-doc,原来gserver是一个通用服务器的实现,提供了线程池管理,简单的日志和多服务器管理。

先看看怎么使用吧

require 'gserver'

class TimeServer < GServer
  def initialize(port=10001, *args)
    super(port, *args)
  end

  def serve(io)
    io.puts(Time.now.to_i)
  end
end

server = TimeServer.new
server.audit = true

['INT', 'TERM'].each { |signal|
  trap(signal) { server.stop }
}
server.start.join

上面这段代码就是一个简单的tcp服务器,返回系统当前时间与1970年之间的秒数差。

其中3-11行是定义服务器,继承GServer,必须实现serve方法,来实现服务器的行为。

14行启动服务器的日志功能。

16-18行定义当前进程收到中断信号时,关闭服务器。

最后一行,启动服务器,并且保持当前进程active,直到服务器线程被关闭为止。

下一章将介绍gserver的源代码

enable ssl for tomcat

2009-12-06 21:39:53 +0800

昨天参加barcamp会议,介绍了contactlist,有不少人质疑服务的安全性,希望使用https来增强安全性。

想起研究生阶段研究的就是web service安全,https属于最简单的实现,赶紧加上吧。

首先是在本地生成keystore,

keytool -genkey -alias tomcat -keyalg RSA

按照提示,输入密码,姓名等等信息,就会在HOME目录下生成.keystore文件,其中包括了你的公私钥。

接下来,就是开启tomcat的8443端口,

最后重启tomcat,并将访问地址由原来的http://mysite.com:8080改成https://mysite.com:8443即可。

oauth for twitter

2009-12-02 20:37:39 +0800

twitter是Twitter的ruby gem,它提供了两种身份验证的方法,一是oauth,二是http auth。http auth非常简单,只要提供账号和密码就可以了,而oauth就稍微复杂一些了。

首先,你需要到http://twitter.com/oauth_clients去注册你的应用,并得到相应的consumer token和consumer secret。

接着就可以使用twitter gem了

def oauth
  oauth = Twitter::OAuth.new(consumer_token, consumer_secret)
  request_token = oauth.set_callback_url 'http://www.huangzhimin.com/'
  session[:rtoken] = request_token.token
  session[:rsecret] = request_token.secret
    
  redirect_to request_token.authorize_url
end

注意,这里除了传入consumer_token和consumer_secret之外,还设置了callback_url,它表示twitter身份验证完毕之后返回的页面。

然后就是跳转到authorize_url,你会进入twitter的验证页面,点击Allow之后,将跳转到之前设置的callback_url页面

def callback
  oauth = Twitter::OAuth.new(consumer_token, consumer_secret)
  oauth.authorize_from_request(session[:rtoken], session[:rsecret], params[:oauth_verifier])
  session[:rtoken] = nil
  session[:rsecret] = nil
  session[:atoken] = oauth.access_token.token
  session[:asecret] = oauth.access_token.secret
end

可以看到通过返回的oauth_verifier,我们就完成了twitter的身份验证,在记录了session[:atoken]和session[:asecret]之后就可以使用twitter的提供的接口,比如

oauth = Twitter::OAuth.new(consumer_token, consumer_secret)
oauth.authorize_from_access(session[:atoken], session[:asecret])
client = Twitter::Base.new(oauth)
client.update('twitter oauth test')