Class: JsonRpcClient

Inherits:
Object
  • Object
show all
Defined in:
lib/json-rpc-client.rb

Overview

fiber.resume

end

Defined Under Namespace

Classes: Error, Request

Constant Summary collapse

INVALID_JSON =

Invalid JSON was received by the server. An error occurred on the server while parsing the JSON text.

-32700
# The JSON sent is not a valid Request object.
INVALID_REQUEST =

The JSON sent is not a valid Request object.

-32600
# The method does not exist / is not available.
METHOD_NOT_FOUND =

The method does not exist / is not available.

-32601
# Invalid method parameter(s).
INVALID_PARAMS =

Invalid method parameter(s).

-32602
# Internal JSON-RPC error.
INTERNAL_ERROR =

Internal JSON-RPC error.

-32603
@@default_logger =

The logger to be used for an instances if they don’t have a logger set on that instance.

nil

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(service_uri, options = {}) ⇒ JsonRpcClient

Create an instance to call the RPC methods on.

Parameters:

  • service_uri (String, Addressable::URI, #to_str)

    The URI to connect to.

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

    Options hash to pass to the instance. See Instance Attribute Details in the documentation for more details on supported options.



67
68
69
70
71
72
73
74
# File 'lib/json-rpc-client.rb', line 67

def initialize(service_uri, options = {})
  @uri = Addressable::URI.parse(service_uri)
  @asynchronous_calls = options.has_key?(:asynchronous_calls) ?
    !!options[:asynchronous_calls] :
    true
  @symbolize_names = options.has_key?(:symbolize_names) ? !!options[:symbolize_names] : true
  @logger = options[:logger]
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(method, *params) ⇒ Object

Called whenever the current object receives a method call that it does not respond to. Will make a call asynchronously or synchronously depending on asynchronous_calls.

Parameters:

  • method (String)

    the API method, ie get, set.

  • params (Array)

    the parameters sent with the method call.



81
82
83
# File 'lib/json-rpc-client.rb', line 81

def method_missing(method, *params)
  @asynchronous_calls ? self._call_async(method, params) : self._call_sync(method, params)
end

Instance Attribute Details

#asynchronous_callsBoolean

Returns If method_missing calls are made asynchronously. Default: true.

Returns:

  • (Boolean)

    If method_missing calls are made asynchronously. Default: true



47
48
49
50
51
52
53
54
55
56
57
58
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/json-rpc-client.rb', line 47

class JsonRpcClient
  attr_accessor :asynchronous_calls, :symbolize_names, :logger

  # Invalid JSON was received by the server.
  # An error occurred on the server while parsing the JSON text.
  INVALID_JSON     = -32700
  # The JSON sent is not a valid Request object.
  INVALID_REQUEST  = -32600
  # The method does not exist / is not available.
  METHOD_NOT_FOUND = -32601
  # Invalid method parameter(s).
  INVALID_PARAMS   = -32602
  # Internal JSON-RPC error.
  INTERNAL_ERROR   = -32603

  # Create an instance to call the RPC methods on.
  #
  # @param [String, Addressable::URI, #to_str] service_uri The URI to connect to.
  # @param [Hash] options Options hash to pass to the instance.
  #   See Instance Attribute Details in the documentation for more details on supported options.
  def initialize(service_uri, options = {})
    @uri = Addressable::URI.parse(service_uri)
    @asynchronous_calls = options.has_key?(:asynchronous_calls) ?
      !!options[:asynchronous_calls] :
      true
    @symbolize_names = options.has_key?(:symbolize_names) ? !!options[:symbolize_names] : true
    @logger = options[:logger]
  end

  # Called whenever the current object receives a method call that it does not respond to.
  # Will make a call asynchronously or synchronously depending on asynchronous_calls.
  #
  # @param [String] method the API method, ie get, set.
  # @param [Array]  params the parameters sent with the method call.
  def method_missing(method, *params)
    @asynchronous_calls ? self._call_async(method, params) : self._call_sync(method, params)
  end

  # Makes the call asynchronously and returns a EM::Deferrable.
  # The underscore is there to avoid conflicts with server methods, not to denote a private method.
  #
  # @param [String] method The API method, ie get, set etc.
  # @param [Array, Hash] params The parameters that should be sent along in the post body.
  # @return [EM::Deferrable] The JsonRpcClient::Request as data.
  def _call_async(method, params)
    return Request.new({
      service_uri:     @uri.to_s,
      method:          method,
      params:          params,
      logger:          @logger,
      symbolize_names: @symbolize_names
    });
  end

  # Make the call synchronously, returns the result directly.
  # The underscore is there to avoid conflicts with server methods, not to denote a private method.
  #
  # @param [String]       method The API method, ie get, set etc.
  # @param [Array, Hash]  params The parameters that should be sent along in the post body.
  # @return [Hash]        The result.
  # @raise JsonRpcClient::Error When the request responds with failed status.
  def _call_sync(method, params)
    f = Fiber.current

    request = _call_async(method, params)

    request.callback do |*args|
      # If we happen to be in the calling fiber, return the data directly.
      return args.size == 1 ? args.first : args if f == Fiber.current
      # else, return it to the yield call below (in the correct fiber).
      f.resume(*args)
    end

    request.errback do |error|
      json_rpc_error = Error.new(error[:message], error[:code], error[:data])
      # If we happen to be in the calling fiber, raise the error directly.
      raise json_rpc_error if f == Fiber.current
      # else, return it to the yield call below (in the correct fiber).
      f.resume(json_rpc_error)
    end

    begin
      response = Fiber.yield # will yield and return the data or raise the error.
    rescue FiberError
      raise "To to use the syncing behaviour in JsonRpcClient, the call must be in a fiber."
    end
    raise response if response.kind_of?(Error)
    return response
  end

  # Makes a notify call by just sending a HTTP request and not caring about the response.
  # The underscore is there to avoid conflicts with server methods, not to denote a private method.
  #
  # @param [String]        method The API method, ie get, set etc.
  # @param [Array, Hash]  params The parameters that should be sent along in the post body.
  def _notify(method, params)
    post_body = {
      method:  method,
      params:  params,
      jsonrpc: '2.0'
    }.to_json


    EM::HttpRequest.new(@uri.to_s).post :body => post_body
    self.class.log(:debug, "NOTIFY: #{@uri.to_s} --> #{post_body}", @logger)
  end

  # The logger to be used for an instances if they don't have a logger set on that instance.
  @@default_logger = nil

  # @return The default logger object.
  def self.default_logger()
    @@default_logger
  end

  # Add a default logging instance, that should accept method calls to debug, info, warning & error.
  # Don't use directly, use self.log.
  def self.default_logger=(logger)
    @@default_logger = logger
  end

  # Logging class that takes severity and message. Only logs if a logger is attached.
  #
  # @param [Symbol, String] level The severity, ie a method of a logger, (info, debug, warn, error).
  # @param [String]         message The log message.
  # @param [Logger]         logger An instance of a logger class.
  def self.log(level, message, logger = nil)
    logger = logger || @@default_logger
    logger.send(level.to_sym, message) if logger.respond_to?(level.to_sym)
  end

  # This class corresponds to the JSON-RPC error object gotten from the server.
  # A "faked" instance of this will be thrown for communication errors as well.
  class Error < RuntimeError
    attr_reader :code, :data
    def initialize(msg, code, data)
      super(msg)
      @code = code
      @data = data
    end


    # Returns the contents of the current error object as a string.
    #
    # @return [String]
    def inspect
      %|#{self.class}: #{self.message}, code: #{@code.inspect}, data: #{@data.inspect}|
    end
  end

  # This class makes a single request to the JSON-RPC service as a EventMachine::Deferrable.
  # The deferrable object will give a successful callback in the result-part of the response.
  # A unsuccessful request will set the deferred status as failed, and will not deliver a result
  # only the JSON-RPC error object as a Hash.
  class Request
    include EM::Deferrable

    def initialize(params)
      service_uri = params[:service_uri]
      post_body = {
        method:  params[:method],
        params:  params[:params],
        id:      'jsonrpc',
        jsonrpc: '2.0',
      }.to_json

      http = EM::HttpRequest.new(service_uri).post :body => post_body
      JsonRpcClient.log(:debug, "NEW REQUEST: #{service_uri} --> #{post_body}", params[:logger])

      http.callback do |response|
        begin
          resp = JSON.parse(response.response, {symbolize_names: params[:symbolize_names]})
          JsonRpcClient.log(
            :debug,
            "REQUEST FINISH: #{service_uri} METHOD: #{params[:method]} RESULT: #{resp}",
            params[:logger]
          )

          if resp.has_key?(:error) || resp.has_key?("error")
            JsonRpcClient.log(
              :error,
              "Error in response from #{service_uri}: #{resp[:error]}",
               params[:logger]
            )
            self.set_deferred_status :failed, resp[:error] || resp["error"]
          end
          self.set_deferred_status :succeeded, resp[:result] || resp["result"]
        rescue JSON::ParserError => e
          JsonRpcClient.log(
            :error,
            "Got exception during parsing of #{response}: #{e}",
            params[:logger]
          )

          # Making an error object in the same style as a JSON RPC error.
          set_deferred_status :failed, {
            code:    JsonRpcClient::INVALID_JSON,
            message: e.message,
            data:    e
          }
        end
      end

      http.errback do |response|
        JsonRpcClient.log(:error, "Error in http request: #{response.error}", params[:logger])
        set_deferred_status :failed, {
          code: JsonRpcClient::INVALID_JSON,
          message: response.error
        }
      end

      self
    end
  end
end

#loggerLogger

Returns The logger instance attached to the instance of JsonRpcClient. Should accept method calls to debug, info, warning & error. Use JsonRpcClient.log for logging.

Returns:

  • (Logger)

    The logger instance attached to the instance of JsonRpcClient. Should accept method calls to debug, info, warning & error. Use JsonRpcClient.log for logging



47
48
49
50
51
52
53
54
55
56
57
58
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/json-rpc-client.rb', line 47

class JsonRpcClient
  attr_accessor :asynchronous_calls, :symbolize_names, :logger

  # Invalid JSON was received by the server.
  # An error occurred on the server while parsing the JSON text.
  INVALID_JSON     = -32700
  # The JSON sent is not a valid Request object.
  INVALID_REQUEST  = -32600
  # The method does not exist / is not available.
  METHOD_NOT_FOUND = -32601
  # Invalid method parameter(s).
  INVALID_PARAMS   = -32602
  # Internal JSON-RPC error.
  INTERNAL_ERROR   = -32603

  # Create an instance to call the RPC methods on.
  #
  # @param [String, Addressable::URI, #to_str] service_uri The URI to connect to.
  # @param [Hash] options Options hash to pass to the instance.
  #   See Instance Attribute Details in the documentation for more details on supported options.
  def initialize(service_uri, options = {})
    @uri = Addressable::URI.parse(service_uri)
    @asynchronous_calls = options.has_key?(:asynchronous_calls) ?
      !!options[:asynchronous_calls] :
      true
    @symbolize_names = options.has_key?(:symbolize_names) ? !!options[:symbolize_names] : true
    @logger = options[:logger]
  end

  # Called whenever the current object receives a method call that it does not respond to.
  # Will make a call asynchronously or synchronously depending on asynchronous_calls.
  #
  # @param [String] method the API method, ie get, set.
  # @param [Array]  params the parameters sent with the method call.
  def method_missing(method, *params)
    @asynchronous_calls ? self._call_async(method, params) : self._call_sync(method, params)
  end

  # Makes the call asynchronously and returns a EM::Deferrable.
  # The underscore is there to avoid conflicts with server methods, not to denote a private method.
  #
  # @param [String] method The API method, ie get, set etc.
  # @param [Array, Hash] params The parameters that should be sent along in the post body.
  # @return [EM::Deferrable] The JsonRpcClient::Request as data.
  def _call_async(method, params)
    return Request.new({
      service_uri:     @uri.to_s,
      method:          method,
      params:          params,
      logger:          @logger,
      symbolize_names: @symbolize_names
    });
  end

  # Make the call synchronously, returns the result directly.
  # The underscore is there to avoid conflicts with server methods, not to denote a private method.
  #
  # @param [String]       method The API method, ie get, set etc.
  # @param [Array, Hash]  params The parameters that should be sent along in the post body.
  # @return [Hash]        The result.
  # @raise JsonRpcClient::Error When the request responds with failed status.
  def _call_sync(method, params)
    f = Fiber.current

    request = _call_async(method, params)

    request.callback do |*args|
      # If we happen to be in the calling fiber, return the data directly.
      return args.size == 1 ? args.first : args if f == Fiber.current
      # else, return it to the yield call below (in the correct fiber).
      f.resume(*args)
    end

    request.errback do |error|
      json_rpc_error = Error.new(error[:message], error[:code], error[:data])
      # If we happen to be in the calling fiber, raise the error directly.
      raise json_rpc_error if f == Fiber.current
      # else, return it to the yield call below (in the correct fiber).
      f.resume(json_rpc_error)
    end

    begin
      response = Fiber.yield # will yield and return the data or raise the error.
    rescue FiberError
      raise "To to use the syncing behaviour in JsonRpcClient, the call must be in a fiber."
    end
    raise response if response.kind_of?(Error)
    return response
  end

  # Makes a notify call by just sending a HTTP request and not caring about the response.
  # The underscore is there to avoid conflicts with server methods, not to denote a private method.
  #
  # @param [String]        method The API method, ie get, set etc.
  # @param [Array, Hash]  params The parameters that should be sent along in the post body.
  def _notify(method, params)
    post_body = {
      method:  method,
      params:  params,
      jsonrpc: '2.0'
    }.to_json


    EM::HttpRequest.new(@uri.to_s).post :body => post_body
    self.class.log(:debug, "NOTIFY: #{@uri.to_s} --> #{post_body}", @logger)
  end

  # The logger to be used for an instances if they don't have a logger set on that instance.
  @@default_logger = nil

  # @return The default logger object.
  def self.default_logger()
    @@default_logger
  end

  # Add a default logging instance, that should accept method calls to debug, info, warning & error.
  # Don't use directly, use self.log.
  def self.default_logger=(logger)
    @@default_logger = logger
  end

  # Logging class that takes severity and message. Only logs if a logger is attached.
  #
  # @param [Symbol, String] level The severity, ie a method of a logger, (info, debug, warn, error).
  # @param [String]         message The log message.
  # @param [Logger]         logger An instance of a logger class.
  def self.log(level, message, logger = nil)
    logger = logger || @@default_logger
    logger.send(level.to_sym, message) if logger.respond_to?(level.to_sym)
  end

  # This class corresponds to the JSON-RPC error object gotten from the server.
  # A "faked" instance of this will be thrown for communication errors as well.
  class Error < RuntimeError
    attr_reader :code, :data
    def initialize(msg, code, data)
      super(msg)
      @code = code
      @data = data
    end


    # Returns the contents of the current error object as a string.
    #
    # @return [String]
    def inspect
      %|#{self.class}: #{self.message}, code: #{@code.inspect}, data: #{@data.inspect}|
    end
  end

  # This class makes a single request to the JSON-RPC service as a EventMachine::Deferrable.
  # The deferrable object will give a successful callback in the result-part of the response.
  # A unsuccessful request will set the deferred status as failed, and will not deliver a result
  # only the JSON-RPC error object as a Hash.
  class Request
    include EM::Deferrable

    def initialize(params)
      service_uri = params[:service_uri]
      post_body = {
        method:  params[:method],
        params:  params[:params],
        id:      'jsonrpc',
        jsonrpc: '2.0',
      }.to_json

      http = EM::HttpRequest.new(service_uri).post :body => post_body
      JsonRpcClient.log(:debug, "NEW REQUEST: #{service_uri} --> #{post_body}", params[:logger])

      http.callback do |response|
        begin
          resp = JSON.parse(response.response, {symbolize_names: params[:symbolize_names]})
          JsonRpcClient.log(
            :debug,
            "REQUEST FINISH: #{service_uri} METHOD: #{params[:method]} RESULT: #{resp}",
            params[:logger]
          )

          if resp.has_key?(:error) || resp.has_key?("error")
            JsonRpcClient.log(
              :error,
              "Error in response from #{service_uri}: #{resp[:error]}",
               params[:logger]
            )
            self.set_deferred_status :failed, resp[:error] || resp["error"]
          end
          self.set_deferred_status :succeeded, resp[:result] || resp["result"]
        rescue JSON::ParserError => e
          JsonRpcClient.log(
            :error,
            "Got exception during parsing of #{response}: #{e}",
            params[:logger]
          )

          # Making an error object in the same style as a JSON RPC error.
          set_deferred_status :failed, {
            code:    JsonRpcClient::INVALID_JSON,
            message: e.message,
            data:    e
          }
        end
      end

      http.errback do |response|
        JsonRpcClient.log(:error, "Error in http request: #{response.error}", params[:logger])
        set_deferred_status :failed, {
          code: JsonRpcClient::INVALID_JSON,
          message: response.error
        }
      end

      self
    end
  end
end

#symbolize_namesBoolean

Returns If the result of sync calls should have the names be symbols. Default: true.

Returns:

  • (Boolean)

    If the result of sync calls should have the names be symbols. Default: true



47
48
49
50
51
52
53
54
55
56
57
58
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
# File 'lib/json-rpc-client.rb', line 47

class JsonRpcClient
  attr_accessor :asynchronous_calls, :symbolize_names, :logger

  # Invalid JSON was received by the server.
  # An error occurred on the server while parsing the JSON text.
  INVALID_JSON     = -32700
  # The JSON sent is not a valid Request object.
  INVALID_REQUEST  = -32600
  # The method does not exist / is not available.
  METHOD_NOT_FOUND = -32601
  # Invalid method parameter(s).
  INVALID_PARAMS   = -32602
  # Internal JSON-RPC error.
  INTERNAL_ERROR   = -32603

  # Create an instance to call the RPC methods on.
  #
  # @param [String, Addressable::URI, #to_str] service_uri The URI to connect to.
  # @param [Hash] options Options hash to pass to the instance.
  #   See Instance Attribute Details in the documentation for more details on supported options.
  def initialize(service_uri, options = {})
    @uri = Addressable::URI.parse(service_uri)
    @asynchronous_calls = options.has_key?(:asynchronous_calls) ?
      !!options[:asynchronous_calls] :
      true
    @symbolize_names = options.has_key?(:symbolize_names) ? !!options[:symbolize_names] : true
    @logger = options[:logger]
  end

  # Called whenever the current object receives a method call that it does not respond to.
  # Will make a call asynchronously or synchronously depending on asynchronous_calls.
  #
  # @param [String] method the API method, ie get, set.
  # @param [Array]  params the parameters sent with the method call.
  def method_missing(method, *params)
    @asynchronous_calls ? self._call_async(method, params) : self._call_sync(method, params)
  end

  # Makes the call asynchronously and returns a EM::Deferrable.
  # The underscore is there to avoid conflicts with server methods, not to denote a private method.
  #
  # @param [String] method The API method, ie get, set etc.
  # @param [Array, Hash] params The parameters that should be sent along in the post body.
  # @return [EM::Deferrable] The JsonRpcClient::Request as data.
  def _call_async(method, params)
    return Request.new({
      service_uri:     @uri.to_s,
      method:          method,
      params:          params,
      logger:          @logger,
      symbolize_names: @symbolize_names
    });
  end

  # Make the call synchronously, returns the result directly.
  # The underscore is there to avoid conflicts with server methods, not to denote a private method.
  #
  # @param [String]       method The API method, ie get, set etc.
  # @param [Array, Hash]  params The parameters that should be sent along in the post body.
  # @return [Hash]        The result.
  # @raise JsonRpcClient::Error When the request responds with failed status.
  def _call_sync(method, params)
    f = Fiber.current

    request = _call_async(method, params)

    request.callback do |*args|
      # If we happen to be in the calling fiber, return the data directly.
      return args.size == 1 ? args.first : args if f == Fiber.current
      # else, return it to the yield call below (in the correct fiber).
      f.resume(*args)
    end

    request.errback do |error|
      json_rpc_error = Error.new(error[:message], error[:code], error[:data])
      # If we happen to be in the calling fiber, raise the error directly.
      raise json_rpc_error if f == Fiber.current
      # else, return it to the yield call below (in the correct fiber).
      f.resume(json_rpc_error)
    end

    begin
      response = Fiber.yield # will yield and return the data or raise the error.
    rescue FiberError
      raise "To to use the syncing behaviour in JsonRpcClient, the call must be in a fiber."
    end
    raise response if response.kind_of?(Error)
    return response
  end

  # Makes a notify call by just sending a HTTP request and not caring about the response.
  # The underscore is there to avoid conflicts with server methods, not to denote a private method.
  #
  # @param [String]        method The API method, ie get, set etc.
  # @param [Array, Hash]  params The parameters that should be sent along in the post body.
  def _notify(method, params)
    post_body = {
      method:  method,
      params:  params,
      jsonrpc: '2.0'
    }.to_json


    EM::HttpRequest.new(@uri.to_s).post :body => post_body
    self.class.log(:debug, "NOTIFY: #{@uri.to_s} --> #{post_body}", @logger)
  end

  # The logger to be used for an instances if they don't have a logger set on that instance.
  @@default_logger = nil

  # @return The default logger object.
  def self.default_logger()
    @@default_logger
  end

  # Add a default logging instance, that should accept method calls to debug, info, warning & error.
  # Don't use directly, use self.log.
  def self.default_logger=(logger)
    @@default_logger = logger
  end

  # Logging class that takes severity and message. Only logs if a logger is attached.
  #
  # @param [Symbol, String] level The severity, ie a method of a logger, (info, debug, warn, error).
  # @param [String]         message The log message.
  # @param [Logger]         logger An instance of a logger class.
  def self.log(level, message, logger = nil)
    logger = logger || @@default_logger
    logger.send(level.to_sym, message) if logger.respond_to?(level.to_sym)
  end

  # This class corresponds to the JSON-RPC error object gotten from the server.
  # A "faked" instance of this will be thrown for communication errors as well.
  class Error < RuntimeError
    attr_reader :code, :data
    def initialize(msg, code, data)
      super(msg)
      @code = code
      @data = data
    end


    # Returns the contents of the current error object as a string.
    #
    # @return [String]
    def inspect
      %|#{self.class}: #{self.message}, code: #{@code.inspect}, data: #{@data.inspect}|
    end
  end

  # This class makes a single request to the JSON-RPC service as a EventMachine::Deferrable.
  # The deferrable object will give a successful callback in the result-part of the response.
  # A unsuccessful request will set the deferred status as failed, and will not deliver a result
  # only the JSON-RPC error object as a Hash.
  class Request
    include EM::Deferrable

    def initialize(params)
      service_uri = params[:service_uri]
      post_body = {
        method:  params[:method],
        params:  params[:params],
        id:      'jsonrpc',
        jsonrpc: '2.0',
      }.to_json

      http = EM::HttpRequest.new(service_uri).post :body => post_body
      JsonRpcClient.log(:debug, "NEW REQUEST: #{service_uri} --> #{post_body}", params[:logger])

      http.callback do |response|
        begin
          resp = JSON.parse(response.response, {symbolize_names: params[:symbolize_names]})
          JsonRpcClient.log(
            :debug,
            "REQUEST FINISH: #{service_uri} METHOD: #{params[:method]} RESULT: #{resp}",
            params[:logger]
          )

          if resp.has_key?(:error) || resp.has_key?("error")
            JsonRpcClient.log(
              :error,
              "Error in response from #{service_uri}: #{resp[:error]}",
               params[:logger]
            )
            self.set_deferred_status :failed, resp[:error] || resp["error"]
          end
          self.set_deferred_status :succeeded, resp[:result] || resp["result"]
        rescue JSON::ParserError => e
          JsonRpcClient.log(
            :error,
            "Got exception during parsing of #{response}: #{e}",
            params[:logger]
          )

          # Making an error object in the same style as a JSON RPC error.
          set_deferred_status :failed, {
            code:    JsonRpcClient::INVALID_JSON,
            message: e.message,
            data:    e
          }
        end
      end

      http.errback do |response|
        JsonRpcClient.log(:error, "Error in http request: #{response.error}", params[:logger])
        set_deferred_status :failed, {
          code: JsonRpcClient::INVALID_JSON,
          message: response.error
        }
      end

      self
    end
  end
end

Class Method Details

.default_loggerObject

Returns The default logger object.

Returns:

  • The default logger object.



158
159
160
# File 'lib/json-rpc-client.rb', line 158

def self.default_logger()
  @@default_logger
end

.default_logger=(logger) ⇒ Object

Add a default logging instance, that should accept method calls to debug, info, warning & error. Don’t use directly, use self.log.



164
165
166
# File 'lib/json-rpc-client.rb', line 164

def self.default_logger=(logger)
  @@default_logger = logger
end

.log(level, message, logger = nil) ⇒ Object

Logging class that takes severity and message. Only logs if a logger is attached.

Parameters:

  • level (Symbol, String)

    The severity, ie a method of a logger, (info, debug, warn, error).

  • message (String)

    The log message.

  • logger (Logger) (defaults to: nil)

    An instance of a logger class.



173
174
175
176
# File 'lib/json-rpc-client.rb', line 173

def self.log(level, message, logger = nil)
  logger = logger || @@default_logger
  logger.send(level.to_sym, message) if logger.respond_to?(level.to_sym)
end

Instance Method Details

#_call_async(method, params) ⇒ EM::Deferrable

Makes the call asynchronously and returns a EM::Deferrable. The underscore is there to avoid conflicts with server methods, not to denote a private method.

Parameters:

  • method (String)

    The API method, ie get, set etc.

  • params (Array, Hash)

    The parameters that should be sent along in the post body.

Returns:

  • (EM::Deferrable)

    The JsonRpcClient::Request as data.



91
92
93
94
95
96
97
98
99
# File 'lib/json-rpc-client.rb', line 91

def _call_async(method, params)
  return Request.new({
    service_uri:     @uri.to_s,
    method:          method,
    params:          params,
    logger:          @logger,
    symbolize_names: @symbolize_names
  });
end

#_call_sync(method, params) ⇒ Hash

Make the call synchronously, returns the result directly. The underscore is there to avoid conflicts with server methods, not to denote a private method.

Parameters:

  • method (String)

    The API method, ie get, set etc.

  • params (Array, Hash)

    The parameters that should be sent along in the post body.

Returns:

  • (Hash)

    The result.

Raises:

  • JsonRpcClient::Error When the request responds with failed status.



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
# File 'lib/json-rpc-client.rb', line 108

def _call_sync(method, params)
  f = Fiber.current

  request = _call_async(method, params)

  request.callback do |*args|
    # If we happen to be in the calling fiber, return the data directly.
    return args.size == 1 ? args.first : args if f == Fiber.current
    # else, return it to the yield call below (in the correct fiber).
    f.resume(*args)
  end

  request.errback do |error|
    json_rpc_error = Error.new(error[:message], error[:code], error[:data])
    # If we happen to be in the calling fiber, raise the error directly.
    raise json_rpc_error if f == Fiber.current
    # else, return it to the yield call below (in the correct fiber).
    f.resume(json_rpc_error)
  end

  begin
    response = Fiber.yield # will yield and return the data or raise the error.
  rescue FiberError
    raise "To to use the syncing behaviour in JsonRpcClient, the call must be in a fiber."
  end
  raise response if response.kind_of?(Error)
  return response
end

#_notify(method, params) ⇒ Object

Makes a notify call by just sending a HTTP request and not caring about the response. The underscore is there to avoid conflicts with server methods, not to denote a private method.

Parameters:

  • method (String)

    The API method, ie get, set etc.

  • params (Array, Hash)

    The parameters that should be sent along in the post body.



142
143
144
145
146
147
148
149
150
151
152
# File 'lib/json-rpc-client.rb', line 142

def _notify(method, params)
  post_body = {
    method:  method,
    params:  params,
    jsonrpc: '2.0'
  }.to_json


  EM::HttpRequest.new(@uri.to_s).post :body => post_body
  self.class.log(:debug, "NOTIFY: #{@uri.to_s} --> #{post_body}", @logger)
end