Class: Hive::RPC::HttpClient

Inherits:
BaseClient show all
Defined in:
lib/hive/rpc/http_client.rb

Overview

HttpClient is intended for single-threaded applications. For multi-threaded apps, use ThreadSafeHttpClient.

Direct Known Subclasses

ThreadSafeHttpClient

Constant Summary collapse

TIMEOUT_ERRORS =

Timeouts are lower level errors, related in that retrying them is trivial, unlike, for example TransactionExpiredError, that requires the client to do something before retrying.

These situations are hopefully momentary interruptions or rate limiting but they might indicate a bigger problem with the node, so they are not retried forever, only up to MAX_TIMEOUT_RETRY_COUNT and then we give up.

Note: JSON::ParserError is included in this list because under certain timeout conditions, a web server may respond with a generic http status code of 200 and HTML page.

[Net::OpenTimeout, JSON::ParserError, Net::ReadTimeout,
Errno::EBADF, IOError, Errno::ENETDOWN, Hive::RemoteDatabaseLockError,
Hive::RequestTimeoutUpstreamResponseError, Hive::RemoteServerError,
Hive::RemoteServerError]
POST_HEADERS =
{
  'Content-Type' => 'application/json; charset=utf-8',
  'User-Agent' => Hive::AGENT_ID
}
JSON_RPC_BATCH_SIZE_MAXIMUM =
50

Constants inherited from BaseClient

BaseClient::MAX_TIMEOUT_BACKOFF, BaseClient::MAX_TIMEOUT_RETRY_COUNT

Constants included from ChainConfig

ChainConfig::EXPIRE_IN_SECS, ChainConfig::EXPIRE_IN_SECS_PROPOSAL, ChainConfig::NETWORKS_HIVE_ADDRESS_PREFIX, ChainConfig::NETWORKS_HIVE_CHAIN_ID, ChainConfig::NETWORKS_HIVE_CORE_ASSET, ChainConfig::NETWORKS_HIVE_DEBT_ASSET, ChainConfig::NETWORKS_HIVE_DEFAULT_NODE, ChainConfig::NETWORKS_HIVE_LEGACY_CHAIN_ID, ChainConfig::NETWORKS_HIVE_VEST_ASSET, ChainConfig::NETWORKS_TEST_ADDRESS_PREFIX, ChainConfig::NETWORKS_TEST_CHAIN_ID, ChainConfig::NETWORKS_TEST_CORE_ASSET, ChainConfig::NETWORKS_TEST_DEBT_ASSET, ChainConfig::NETWORKS_TEST_DEFAULT_NODE, ChainConfig::NETWORKS_TEST_VEST_ASSET, ChainConfig::NETWORK_CHAIN_IDS

Instance Attribute Summary

Attributes inherited from BaseClient

#chain, #error_pipe, #url

Instance Method Summary collapse

Methods inherited from BaseClient

#evaluate_id, #initialize, #put, #rpc_id, #uri, #yield_response

Constructor Details

This class inherits a constructor from Hive::RPC::BaseClient

Instance Method Details

#httpObject



32
33
34
35
36
37
38
39
40
41
# File 'lib/hive/rpc/http_client.rb', line 32

def http
  @http ||= Net::HTTP.new(uri.host, uri.port).tap do |http|
    http.use_ssl = true if uri.to_s =~ /^https/i
    http.keep_alive_timeout = 150 # seconds
    
    # WARNING This method opens a serious security hole. Never use this
    # method in production code.
    # http.set_debug_output(STDOUT) if !!ENV['DEBUG']
  end
end

#http_postObject



43
44
45
# File 'lib/hive/rpc/http_client.rb', line 43

def http_post
  @http_post ||= Net::HTTP::Post.new(uri.request_uri, POST_HEADERS)
end

#http_request(request) ⇒ Object



47
48
49
# File 'lib/hive/rpc/http_client.rb', line 47

def http_request(request)
  http.request(request)
end

#rpc_batch_execute(options = {}, &block) ⇒ Object



157
158
159
160
161
# File 'lib/hive/rpc/http_client.rb', line 157

def rpc_batch_execute(options = {}, &block)
  api_name = options[:api_name]
  
  yield_response rpc_execute(api_name, nil, options), &block
end

#rpc_execute(api_name = @api_name, api_method = nil, options = {}, &block) ⇒ Object

This is the main method used by API instances to actually fetch data from the remote node. It abstracts the api namespace, method name, and parameters so that the API instance can be decoupled from the protocol.

Parameters:

  • api_name (String) (defaults to: @api_name)

    API namespace of the method being called.

  • api_method (String) (defaults to: nil)

    API method name being called.

  • options (Hash) (defaults to: {})

    options

Options Hash (options):

  • :request_object (Object)

    Hash or Array to become json in request body.



59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/hive/rpc/http_client.rb', line 59

def rpc_execute(api_name = @api_name, api_method = nil, options = {}, &block)
  reset_timeout
  
  response = nil
  
  loop do
    sub_options = options.dup
    request = http_post(api_name)
    
    request_object = if !!api_name && !!api_method
      put(api_name, api_method, sub_options)
    elsif !!options && defined?(sub_options.delete)
      sub_options.delete(:request_object)
    end
    
    if request_object.size > JSON_RPC_BATCH_SIZE_MAXIMUM
      raise JsonRpcBatchMaximumSizeExceededError, "Maximum json-rpc-batch is #{JSON_RPC_BATCH_SIZE_MAXIMUM} elements."
    end
    
    request.body = if request_object.class == Hash
      request_object
    elsif request_object.size == 1
      request_object.first
    else
      request_object
    end.to_json
    
    response = catch :http_request do; begin; http_request(request)
    rescue *TIMEOUT_ERRORS => e
      retry_timeout(:http_request, e) and redo
    end; end
    
    if response.nil?
      retry_timeout(:tota_cera_pila, 'response was nil') and redo
    end
    
    case response.code
    when '200'
      response = catch :parse_json do; begin; JSON[response.body]
      rescue *TIMEOUT_ERRORS => e
        throw retry_timeout(:parse_json, e)
      end; end
      
      response = case response
      when Hash
        Hashie::Mash.new(response).tap do |r|
          evaluate_id(request: request_object.first, response: r, api_method: api_method)
        end
      when Array
        Hashie::Array.new(response).tap do |r|
          request_object.each_with_index do |req, index|
            evaluate_id(request: req, response: r[index], api_method: api_method)
          end
        end
      else; response
      end
      
      timeout_detected = false
      timeout_cause = nil
      
      [response].flatten.each_with_index do |r, i|
        if defined?(r.error) && !!r.error
          if !!r.error.message
            begin
              rpc_method_name = "#{api_name}.#{api_method}"
              rpc_args = [request_object].flatten[i]
              raise_error_response rpc_method_name, rpc_args, r
            rescue *TIMEOUT_ERRORS => e
              timeout_detected = true
              timeout_cause = JSON[e.message]['error'] + " while posting: #{rpc_args}" rescue e.to_s
              
              break # fail fast
            end
          else
            raise Hive::ArgumentError, r.error.inspect
          end
        end
      end
      
      if timeout_detected
        retry_timeout(:tota_cera_pila, timeout_cause) and redo
      end
      
      yield_response response, &block
    when '504' # Gateway Timeout
      retry_timeout(:tota_cera_pila, response.body) and redo
    when '502' # Bad Gateway
      retry_timeout(:tota_cera_pila, response.body) and redo
    else
      raise UnknownError, "#{api_name}.#{api_method}: #{response.body}"
    end
    
    break # success!
  end
  
  response
end