require 'net/http' require 'net/https' require 'open-uri' require 'rexml/document' module SoraGeocoding class Request < Base def parse_xml(data) REXML::Document.new(data) rescue StandardError unless raise_error(ResponseParseError.new(data)) SoraGeocoding.log(:warn, "API's response was not valid XML") SoraGeocoding.log(:debug, "Raw response: #{data}") end end def parse_raw_data(raw_data) parse_xml(raw_data) end # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity def check_response_for_errors!(response) if response.code.to_i == 400 raise_error(SoraGeocoding::InvalidRequest) || SoraGeocoding.log(:warn, 'API error: 400 Bad Request') elsif response.code.to_i == 401 raise_error(SoraGeocoding::RequestDenied) || SoraGeocoding.log(:warn, 'API error: 401 Unauthorized') elsif response.code.to_i == 402 raise_error(SoraGeocoding::PaymentRequiredError) || SoraGeocoding.log(:warn, 'API error: 402 Payment Required') elsif response.code.to_i == 403 raise_error(SoraGeocoding::ForbiddenError) || SoraGeocoding.log(:warn, 'API error: 403 Forbidden') elsif response.code.to_i == 404 raise_error(SoraGeocoding::NotFoundError) || SoraGeocoding.log(:warn, 'API error: 404 Not Found') elsif response.code.to_i == 407 raise_error(SoraGeocoding::ProxyAuthenticationRequiredError) || SoraGeocoding.log(:warn, 'API error: 407 Proxy Authentication Required') elsif response.code.to_i == 429 raise_error(SoraGeocoding::OverQueryLimitError) || SoraGeocoding.log(:warn, 'API error: 429 Too Many Requests') elsif response.code.to_i == 503 raise_error(SoraGeocoding::ServiceUnavailable) || SoraGeocoding.log(:warn, 'API error: 503 Service Unavailable') end end # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity def fetch_data(query) parse_raw_data(fetch_raw_data(query)) rescue SocketError => e raise_error(e) || SoraGeocoding.log(:warn, 'API connection cannot be established.') rescue Errno::ECONNREFUSED => e raise_error(e) || SoraGeocoding.log(:warn, 'API connection refused.') rescue SoraGeocoding::NetworkError => e raise_error(e) || SoraGeocoding.log(:warn, 'API connection is either unreacheable or reset by the peer') rescue SoraGeocoding::Timeout => e raise_error(e) || SoraGeocoding.log(:warn, 'API not responding fast enough ' \ '(use SoraGeocoding.configure(:timeout => ...) to set limit).') end # # Make an HTTP(S) request to return the response object. # # rubocop:disable Metrics/AbcSize def make_api_request(query_url) uri = URI.parse(query_url) SoraGeocoding.log(:debug, "HTTP request being made for #{uri}") http_client.start( uri.host, uri.port, use_ssl: use_ssl?(uri.port), open_timeout: configuration.timeout, read_timeout: configuration.timeout ) do |client| configure_ssl!(client) if use_ssl? req = Net::HTTP::Get.new(uri.request_uri, configuration.http_headers) if configuration.basic_auth[:user] && configuration.basic_auth[:password] req.basic_auth(configuration.basic_auth[:user], configuration.basic_auth[:password]) end client.request(req) end rescue Timeout::Error raise SoraGeocoding::Timeout rescue Errno::EHOSTUNREACH, Errno::ETIMEDOUT, Errno::ENETUNREACH, Errno::ECONNRESET raise SoraGeocoding::NetworkError end # rubocop:enable Metrics/AbcSize # # Detect if you are using ssl # def use_ssl?(http_port = nil) if (http_port == 443) || (supported_protocols == [:https]) true elsif supported_protocols == [:http] false else configuration.use_https end end def configure_ssl!(client); end # # Generate a string of http, https or protocol. # def protocol 'http' + (use_ssl? ? 's' : '') end # # Make HTTP requests. # def http_client proxy = configuration.send("#{protocol}_proxy") if proxy proxy_url = proxy =~ /^#{protocol}/ ? proxy : protocol + '://' + proxy begin uri = URI.parse(proxy_url) rescue URI::InvalidURIError raise ConfigurationError, "Error parsing #{protocol.upcase} proxy URL: '#{proxy_url}'" end Net::HTTP::Proxy(uri.host, uri.port, uri.user, uri.password) else Net::HTTP end end # # Supported Protocols # def supported_protocols %i[http https] end private def fetch_raw_data(query) response = make_api_request(query) check_response_for_errors!(response) response.body end end end