让ActiveResource使用HTTP Digest验证

ActiveResource可以使用HTTP Basic验证方式,如果ActiveResource可以使用Digest验证方式,那么就不需要https安全连接访问资源了。这样一方面服务器配置简单,另一方面,速度上更有优势。
在服务器端,我们使用了 httpauth这个库实现了服务器端的HTTP Digest验证,具体实现请gem install httpauth后查看examples中的两个文件。客户端如果不需要ActiveResource的功能,直接参考httpauth例子就可以实现。
ActiveResource对于HTTP Digest的验证的支持我参考了日本人 taslamBasic認証以外に対応させる里面对于WSSE支持的实现。同样重新构造connection,并对于401的第一次失败作重新处理。这里不能使用ruby的http库,而需要使用mongrel作者Zed Shaw编写的全功能http client库 rfuzz

taslam对于 ActiveResource的功底非常深厚,还有一篇文章,讲到如何提高ActiveResource处理数据的速度。 ActiveResourceが遅い→JSONならパースが速いよ

下面是activerecord 支持http digest authentication需要的代码和model示例。
AuthenticationCache代码来自于httpauth的例子程序,为节省篇幅就不再附上。
#lib/active_resource/digest.rb
require 'httpauth'
require 'rfuzz/client'
require 'authentication_cache'

module ActiveResource

  class Connection::Digest < Connection
    include HTTPAuth::Digest

    def initialize(site, format = ActiveResource::Formats[:xml])
      super
      @cache = AuthenticationCache.new
    end

    def site=(site)
      super
      @client = RFuzz::HttpClient.new @site.host, @site.port
    end

    private
    def authorization_header
      {}
    end

    def request(method, path, *arguments)
      RFuzz::HttpResponse.send(:define_method, :code, Proc.new(){ self.http_status })
      RFuzz::HttpResponse.send(:define_method, :body, Proc.new(){ self.http_body })

      args = {}
      args[:head] = arguments.pop
      args[:body] = arguments.pop

      uri = URI.parse path

      # If credentials were stored, use them. Otherwise do a normal get
      credentials = @cache.get_credentials
      if credentials
        args[:head].update({"Authorization" => credentials.to_header})
      end

      result = nil
      time = Benchmark.realtime { result = @client.send(method, path, args) }
      if result.http_status == '401' and !result['WWW_AUTHENTICATE'].nil?
        challenge = Challenge.from_header(result['WWW_AUTHENTICATE'])
        (stale = challenge.stale) rescue NoMethodError
        unless stale
          username, password = site.user, site.password
        else
          username = credentials.username
          password = credentials.password
        end

        credentials = Credentials.from_challenge(
          challenge,
          {:uri => path, :username => username, :password => password, :method => method.to_s.upcase }
        )
        args[:head].update({"Authorization" => credentials.to_header})
        @cache.set_credentials_for uri.path, credentials
        time = Benchmark.realtime { result = @client.send(method, path, args) }
      end

      if @cache.get_credentials
        if result['AUTHENTICATION_INFO']
          auth_info = AuthenticationInfo.from_header(result['AUTHENTICATION_INFO'])
          @cache.update_usage_for uri.path, auth_info.h[:nextnonce]
        else
          @cache.update_usage_for uri.path
        end
      end

      handle_response(result)
    rescue Timeout::Error => e
      raise TimeoutError.new(e.message)
    end
  end

  class Digest < Base
    def self.connection(refresh = false)
      @connection = Connection::Digest.new(site, format) if refresh || @connection.nil?
      @connection
    end
  end
end

class UserResource < ActiveResource::Digest
  self.site = "http://user:pass@localhost:3000"
  self.element_name = "user"
  self.format = :ex_json
end

同时我发现rails 2.1新生成的new_default需要修改,否则json格式解析不会正确生成ActiveResource对象,我在这里也花了好长时间,因为我以为是json lib解析的问题。
if defined?(ActiveRecord)
  # 原先为true,要改成false。
  ActiveRecord::Base.include_root_in_json = false 
end

你可能感兴趣的:(json,cache,Ruby,ActiveRecord,Rails)