在服务器端,我们使用了 httpauth这个库实现了服务器端的HTTP Digest验证,具体实现请gem install httpauth后查看examples中的两个文件。客户端如果不需要ActiveResource的功能,直接参考httpauth例子就可以实现。
ActiveResource对于HTTP Digest的验证的支持我参考了日本人 taslam的 Basic認証以外に対応させる里面对于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