lib/wordnik/request.rb in wordnik-4.07 vs lib/wordnik/request.rb in wordnik-4.08

- old
+ new

@@ -14,11 +14,11 @@ validates_presence_of :format, :http_method # All requests must have an HTTP method and a path # Optionals parameters are :params, :headers, :body, :format, :host - # + # def initialize(http_method, path, attributes={}) attributes[:format] ||= Wordnik.configuration.response_format attributes[:params] ||= {} # Set default headers @@ -30,48 +30,48 @@ # api_key from headers hash trumps the default, even if its value is blank if attributes[:headers].present? && attributes[:headers].has_key?(:api_key) default_headers.delete(:api_key) end - + # api_key from params hash trumps all others (headers and default_headers) if attributes[:params].present? && attributes[:params].has_key?(:api_key) default_headers.delete(:api_key) attributes[:headers].delete(:api_key) if attributes[:headers].present? end - + # Merge argument headers into defaults attributes[:headers] = default_headers.merge(attributes[:headers] || {}) - + # Stick in the auth token if there is one if Wordnik.authenticated? attributes[:headers].merge!({:auth_token => Wordnik.configuration.auth_token}) end - + self.http_method = http_method.to_sym self.path = path attributes.each do |name, value| send("#{name.to_s.underscore.to_sym}=", value) end end # Construct a base URL # - def url(options = {}) + def url(options = {}) u = Addressable::URI.new( :scheme => Wordnik.configuration.scheme, :host => Wordnik.configuration.host, :path => self.interpreted_path, :query => self.query_string.sub(/\?/, '') ).to_s - + # Drop trailing question mark, if present u.sub! /\?$/, '' - + # Obfuscate API key? u.sub! /api\_key=\w+/, 'api_key=YOUR_API_KEY' if options[:obfuscated] - + u end # Iterate over the params hash, injecting any path values into the path string # e.g. /word.{format}/{word}/entries => /word.json/cat/entries @@ -89,33 +89,33 @@ unless ['.json', '.xml', '{format}'].any? {|s| p.downcase.include? s } p = p.sub(/^(\/?\w+)/, "\\1.#{format}") end p = p.sub("{format}", self.format.to_s) - + URI.encode [Wordnik.configuration.base_path, p].join("/").gsub(/\/+/, '/') end - + # Massage the request body into a state of readiness # If body is a hash, camelize all keys then convert to a json string # - def body=(value) + def body=(value) if value.is_a?(Hash) value = value.inject({}) do |memo, (k,v)| memo[k.to_s.camelize(:lower).to_sym] = v memo end end @body = value end - + # If body is an object, JSONify it before making the actual request. - # + # def outgoing_body body.is_a?(String) ? body : body.to_json end - + # Construct a query string from the query-string-type params def query_string # Iterate over all params, # .. removing the ones that are part of the path itself. @@ -125,51 +125,72 @@ next if self.path.include? "{#{key}}" # skip path params next if value.blank? && value.class != FalseClass # skip empties key = key.to_s.camelize(:lower).to_sym unless key.to_sym == :api_key # api_key is not a camelCased param query_values[key] = value.to_s end - + # We don't want to end up with '?' as our query string # if there aren't really any params return "" if query_values.blank? - + # Addressable requires query_values to be set after initialization.. qs = Addressable::URI.new qs.query_values = query_values qs.to_s end - - def make - request = Typhoeus::Request.new(self.url, + + def make(attempt = 0) + # url is calculated, so we need to compute it once. + u = self.url + #Wordnik.logger.debug "Making attempt #{attempt}; now fetching #{u}" if attempt > 0 + request = Typhoeus::Request.new(u, :headers => self.headers.stringify_keys, :method => self.http_method.to_sym) - - # Make request proxy-aware + + # Make request proxy-aware if Wordnik.configuration.proxy.present? request.proxy = Wordnik.configuration.proxy request.proxy_username = Wordnik.configuration.proxy_username if Wordnik.configuration.proxy_username.present? request.proxy_password = Wordnik.configuration.proxy_password if Wordnik.configuration.proxy_password.present? end - - Wordnik.logger.debug "\n #{self.http_method.to_s.upcase} #{self.url}\n body: #{self.outgoing_body}\n headers: #{request.headers}\n\n" - + + Wordnik.logger.debug "\n #{self.http_method.to_s.upcase} #{u}\n body: #{self.outgoing_body}\n headers: #{request.headers}\n\n" + request.body = self.outgoing_body unless self.http_method.to_sym == :get - # Execute the request + # Execute the request — blocking call here. Typhoeus::Hydra.hydra.queue request - Typhoeus::Hydra.hydra.run - Response.new(request.response) + Typhoeus::Hydra.hydra.run + + # if we are using local load balancing, check for timeouts and connection errors + resp = request.response + + if Wordnik.configuration.load_balancer + if (resp.timed_out? || resp.code == 0) + # Wordnik.logger.debug "informing load balancer about failure" + Wordnik.configuration.load_balancer.inform_failure + if (attempt <= 3) + # Wordnik.logger.debug "Trying again after failing #{attempt} times..." + return make(attempt + 1) if attempt <= 3 # try three times to get a result... + end + else + # Wordnik.logger.debug "informing load balancer about success" + Wordnik.configuration.load_balancer.inform_success + end + end + + Response.new(resp) end - + def response self.make end - + def response_code_pretty return unless @response.present? - @response.code.to_s + @response.code.to_s end - + def response_headers_pretty return unless @response.present? # JSON.pretty_generate(@response.headers).gsub(/\n/, '<br/>') # <- This was for RestClient @response.headers.gsub(/\n/, '<br/>') # <- This is for Typhoeus end