ActiveResource可以使用HTTP Basic验证方式,如果ActiveResource可以使用Digest验证方式,那么就不需要https安全连接访问资源了。这样一方面服务器配置简单,另一方面,速度上更有优势。
在服务器端,我们使用了
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