Class to handle connections to remote web services. This class is used by ActiveResource::Base to interface with REST services.
The site
parameter is required and will set the
site
attribute to the URI for the remote resource service.
# File lib/active_resource/connection.rb, line 34 def initialize(site, format = ActiveResource::Formats::JsonFormat) raise ArgumentError, 'Missing site URI' unless site @user = @password = nil self.site = site self.format = format end
# File lib/active_resource/connection.rb, line 27 def requests @@requests ||= [] end
Sets the auth type for remote service.
# File lib/active_resource/connection.rb, line 65 def auth_type=(auth_type) @auth_type = legitimize_auth_type(auth_type) end
Executes a DELETE request (see HTTP protocol documentation if unfamiliar). Used to delete resources.
# File lib/active_resource/connection.rb, line 87 def delete(path, headers = {}) with_auth { request(:delete, path, build_request_headers(headers, :delete, self.site.merge(path))) } end
Executes a GET request. Used to get (find) resources.
# File lib/active_resource/connection.rb, line 81 def get(path, headers = {}) with_auth { request(:get, path, build_request_headers(headers, :get, self.site.merge(path))) } end
Executes a HEAD request. Used to obtain meta-information about resources, such as whether they exist and their size (via response headers).
# File lib/active_resource/connection.rb, line 111 def head(path, headers = {}) with_auth { request(:head, path, build_request_headers(headers, :head, self.site.merge(path))) } end
Sets the password for remote service.
# File lib/active_resource/connection.rb, line 60 def password=(password) @password = password end
Executes a PATCH request (see HTTP protocol documentation if unfamiliar). Used to update resources.
# File lib/active_resource/connection.rb, line 93 def patch(path, body = '', headers = {}) with_auth { request(:patch, path, body.to_s, build_request_headers(headers, :patch, self.site.merge(path))) } end
Executes a POST request. Used to create new resources.
# File lib/active_resource/connection.rb, line 105 def post(path, body = '', headers = {}) with_auth { request(:post, path, body.to_s, build_request_headers(headers, :post, self.site.merge(path))) } end
Set the proxy for remote service.
# File lib/active_resource/connection.rb, line 50 def proxy=(proxy) @proxy = proxy.is_a?(URI) ? proxy : URI.parse(proxy) end
Executes a PUT request (see HTTP protocol documentation if unfamiliar). Used to update resources.
# File lib/active_resource/connection.rb, line 99 def put(path, body = '', headers = {}) with_auth { request(:put, path, body.to_s, build_request_headers(headers, :put, self.site.merge(path))) } end
Set URI for remote service.
# File lib/active_resource/connection.rb, line 42 def site=(site) @site = site.is_a?(URI) ? site : URI.parse(site) @ssl_options ||= {} if @site.is_a?(URI::HTTPS) @user = URI.parser.unescape(@site.user) if @site.user @password = URI.parser.unescape(@site.password) if @site.password end
Hash of options applied to Net::HTTP instance when site
protocol is 'https'.
# File lib/active_resource/connection.rb, line 75 def ssl_options=(options) @ssl_options = options end
Sets the number of seconds after which HTTP requests to the remote service should time out.
# File lib/active_resource/connection.rb, line 70 def timeout=(timeout) @timeout = timeout end
Sets the user for remote service.
# File lib/active_resource/connection.rb, line 55 def user=(user) @user = user end
# File lib/active_resource/connection.rb, line 186 def apply_ssl_options(http) http.tap do |https| # Skip config if site is already a https:// URI. if defined? @ssl_options http.use_ssl = true # Default to no cert verification (WTF? FIXME) http.verify_mode = OpenSSL::SSL::VERIFY_NONE # All the SSL options have corresponding http settings. @ssl_options.each { |key, value| http.send "#{key}=", value } end end end
# File lib/active_resource/connection.rb, line 262 def auth_attributes_for(uri, request_digest, params) [ %Q(username="#{@user}"), %Q(realm="#{params['realm']}"), %Q(qop="#{params['qop']}"), %Q(uri="#{uri.path}"), %Q(nonce="#{params['nonce']}"), %Q(nc="0"), %Q(cnonce="#{params['cnonce']}"), %Q(opaque="#{params['opaque']}"), %Q(response="#{request_digest}")].join(", ") end
Builds headers for request to remote service.
# File lib/active_resource/connection.rb, line 206 def build_request_headers(headers, http_method, uri) authorization_header(http_method, uri).update(default_header).update(http_format_header(http_method)).update(headers) end
# File lib/active_resource/connection.rb, line 250 def client_nonce Digest::MD5.hexdigest("%x" % (Time.now.to_i + rand(65535))) end
# File lib/active_resource/connection.rb, line 176 def configure_http(http) apply_ssl_options(http).tap do |https| # Net::HTTP timeouts default to 60 seconds. if defined? @timeout https.open_timeout = @timeout https.read_timeout = @timeout end end end
# File lib/active_resource/connection.rb, line 201 def default_header @default_header ||= {} end
# File lib/active_resource/connection.rb, line 236 def digest_auth_header(http_method, uri) params = extract_params_from_response request_uri = uri.path request_uri << "?#{uri.query}" if uri.query ha1 = Digest::MD5.hexdigest("#{@user}:#{params['realm']}:#{@password}") ha2 = Digest::MD5.hexdigest("#{http_method.to_s.upcase}:#{request_uri}") params.merge!('cnonce' => client_nonce) request_digest = Digest::MD5.hexdigest([ha1, params['nonce'], "0", params['cnonce'], params['qop'], ha2].join(":")) "Digest #{auth_attributes_for(uri, request_digest, params)}" end
# File lib/active_resource/connection.rb, line 254 def extract_params_from_response params = {} if response_auth_header =~ /^(\w+) (.*)/ $2.gsub(/(\w+)="(.*?)"/) { params[$1] = $2 } end params end
Handles response and error codes from the remote service.
# File lib/active_resource/connection.rb, line 131 def handle_response(response) case response.code.to_i when 301, 302, 303, 307 raise(Redirection.new(response)) when 200...400 response when 400 raise(BadRequest.new(response)) when 401 raise(UnauthorizedAccess.new(response)) when 403 raise(ForbiddenAccess.new(response)) when 404 raise(ResourceNotFound.new(response)) when 405 raise(MethodNotAllowed.new(response)) when 409 raise(ResourceConflict.new(response)) when 410 raise(ResourceGone.new(response)) when 422 raise(ResourceInvalid.new(response)) when 401...500 raise(ClientError.new(response)) when 500...600 raise(ServerError.new(response)) else raise(ConnectionError.new(response, "Unknown response code: #{response.code}")) end end
Creates new Net::HTTP instance for communication with the remote service and resources.
# File lib/active_resource/connection.rb, line 164 def http configure_http(new_http) end
# File lib/active_resource/connection.rb, line 275 def http_format_header(http_method) {HTTP_FORMAT_HEADER_NAMES[http_method] => format.mime_type} end
# File lib/active_resource/connection.rb, line 279 def legitimize_auth_type(auth_type) return :basic if auth_type.nil? auth_type = auth_type.to_sym auth_type.in?([:basic, :digest]) ? auth_type : :basic end
# File lib/active_resource/connection.rb, line 168 def new_http if @proxy Net::HTTP.new(@site.host, @site.port, @proxy.host, @proxy.port, @proxy.user, @proxy.password) else Net::HTTP.new(@site.host, @site.port) end end
Makes a request to the remote service.
# File lib/active_resource/connection.rb, line 117 def request(method, path, *arguments) result = ActiveSupport::Notifications.instrument("request.active_resource") do |payload| payload[:method] = method payload[:request_uri] = "#{site.scheme}://#{site.host}:#{site.port}#{path}" payload[:result] = http.send(method, path, *arguments) end handle_response(result) rescue Timeout::Error => e raise TimeoutError.new(e.message) rescue OpenSSL::SSL::SSLError => e raise SSLError.new(e.message) end
# File lib/active_resource/connection.rb, line 210 def response_auth_header @response_auth_header ||= "" end
# File lib/active_resource/connection.rb, line 214 def with_auth retried ||= false yield rescue UnauthorizedAccess => e raise if retried || auth_type != :digest @response_auth_header = e.response['WWW-Authenticate'] retried = true retry end