Class: Sr::Jimson::Server

Inherits:
Object show all
Defined in:
lib/sr/jimson/server.rb,
lib/sr/jimson/server/error.rb

Defined Under Namespace

Classes: Error, System

Constant Summary collapse

JSON_RPC_VERSION =
'2.0'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(router_or_handler, opts = {}) ⇒ Server

router_or_handler is an instance of Sr::Jimson::Router or extends Sr::Jimson::Handler

opts may include:

  • :host - the hostname or ip to bind to

  • :port - the port to listen on

  • :server - the rack handler to use, e.g. ‘webrick’ or ‘thin’

  • :show_errors - true or false, send backtraces in error responses?

  • :logger - optional logger to log errors to

Remaining options are forwarded to the underlying Rack server.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
# File 'lib/sr/jimson/server.rb', line 54

def initialize(router_or_handler, opts = {})
  if !router_or_handler.is_a?(Router)
    # arg is a handler, wrap it in a Router
    @router = Router.new
    @router.root router_or_handler
  else
    # arg is a router
    @router = router_or_handler
  end
  @router.namespace 'system', System.new(@router)

  @host = opts.delete(:host) || '0.0.0.0'
  @port = opts.delete(:port) || 8999
  @show_errors = opts.delete(:show_errors) || false
  @logger = opts.delete(:logger) || nil
  @opts = opts
end

Instance Attribute Details

#hostObject

Returns the value of attribute host.



31
32
33
# File 'lib/sr/jimson/server.rb', line 31

def host
  @host
end

#optsObject

Returns the value of attribute opts.



31
32
33
# File 'lib/sr/jimson/server.rb', line 31

def opts
  @opts
end

#portObject

Returns the value of attribute port.



31
32
33
# File 'lib/sr/jimson/server.rb', line 31

def port
  @port
end

#routerObject

Returns the value of attribute router.



31
32
33
# File 'lib/sr/jimson/server.rb', line 31

def router
  @router
end

#show_errorsObject

Returns the value of attribute show_errors.



31
32
33
# File 'lib/sr/jimson/server.rb', line 31

def show_errors
  @show_errors
end

Class Method Details

.with_routes(opts = {}, &block) ⇒ Object

Create a Server with routes defined



36
37
38
39
40
# File 'lib/sr/jimson/server.rb', line 36

def self.with_routes(opts = {}, &block)
  router = Router.new
  router.send(:draw, &block)
  self.new(router, opts)
end

Instance Method Details

#_log(severity, message) ⇒ Object



72
73
74
75
# File 'lib/sr/jimson/server.rb', line 72

def _log(severity, message)
  return if not @logger
  @logger.log(severity, message)
end

#call(env) ⇒ Object

Entry point for Rack



91
92
93
94
95
96
97
# File 'lib/sr/jimson/server.rb', line 91

def call(env)
  req = Rack::Request.new(env)
  resp = Rack::Response.new
  return resp.finish if !req.post?
  resp.write process(req.body.read)
  resp.finish
end

#create_response(request) ⇒ Object



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/sr/jimson/server.rb', line 162

def create_response(request)
  method = request[:method]
  params = request[:params]
  result = dispatch_request(method, params)

  response = success_response(request, result)

  # A Notification is a Request object without an "id" member.
  # The Server MUST NOT reply to a Notification, including those
  # that are within a batch request.
  response = nil if !request.has_key?(:id)

  return response

  rescue Server::Error => e
    raise e
  rescue ArgumentError
    raise Server::Error::InvalidParams.new
  rescue Exception, StandardError => e
    raise Server::Error::ApplicationError.new(e, @show_errors)
end

#dispatch_request(method, params) ⇒ Object



184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/sr/jimson/server.rb', line 184

def dispatch_request(method, params)
  method_name = method.to_s
  handler = @router.handler_for_method(method_name)
  method_name = @router.strip_method_namespace(method_name)

  if handler.nil? \
  || !handler.class.jimson_exposed_methods.include?(method_name) \
  || !handler.respond_to?(method_name)
    raise Server::Error::MethodNotFound.new(method)
  end

  if params.nil?
    return handler.send(method_name)
  elsif params.is_a?(Hash)
    return handler.send(method_name, params)
  else
    return handler.send(method_name, *params)
  end
end

#error_response(error, request = nil) ⇒ Object



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/sr/jimson/server.rb', line 204

def error_response(error, request = nil)
  if error.is_a? Server::Error::InternalError
    _log Logger::ERROR, "internal error: #{error} - #{error.backtrace}"
  else
    _log Logger::WARN, "client error: #{error}"
  end
  resp = {
           'jsonrpc' => JSON_RPC_VERSION,
           'error'   => error.to_h,
         }
  if !!request && request.has_key?(:id)
    resp[:id] = request[:id]
  else
    resp[:id] = nil
  end

  resp
end

#handle_request(request) ⇒ Object



123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/sr/jimson/server.rb', line 123

def handle_request(request)
  response = nil
  begin
    if !validate_request(request)
      response = error_response(Server::Error::InvalidRequest.new)
    else
      response = create_response(request)
    end
  rescue Server::Error => e
    response = error_response(e, request)
  end

  response
end

#parse_request(post) ⇒ Object



231
232
233
234
235
# File 'lib/sr/jimson/server.rb', line 231

def parse_request(post)
  MultiJson.load(post, :symbolize_keys => true)
rescue
  raise Server::Error::ParseError.new
end

#process(content) ⇒ Object



99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/sr/jimson/server.rb', line 99

def process(content)
  begin
    request = parse_request(content)
    if request.is_a?(Array)
      raise Server::Error::InvalidRequest.new if request.empty?
      response = request.map { |req| handle_request(req) }
    else
      response = handle_request(request)
    end
  rescue Server::Error::ParseError, Server::Error::InvalidRequest => e
    response = error_response(e)
  rescue Server::Error => e
    response = error_response(e, request)
  rescue StandardError, Exception => e
    response = error_response(Server::Error::InternalError.new(e))
  end

  response.compact! if response.is_a?(Array)

  return nil if response.nil? || (response.respond_to?(:empty?) && response.empty?)

  MultiJson.dump(response)
end

#startObject

Starts the server so it can process requests



80
81
82
83
84
85
86
# File 'lib/sr/jimson/server.rb', line 80

def start
  Rack::Server.start(opts.merge(
    :app    => self,
    :Host   => @host,
    :Port   => @port
  ))
end

#success_response(request, result) ⇒ Object



223
224
225
226
227
228
229
# File 'lib/sr/jimson/server.rb', line 223

def success_response(request, result)
  {
    'jsonrpc' => JSON_RPC_VERSION,
    'result'  => result,
    'id'      => request[:id]
  }
end

#validate_request(request) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
# File 'lib/sr/jimson/server.rb', line 138

def validate_request(request)
  required_keys = [:jsonrpc, :method]
  required_types = {
                     :jsonrpc => [String],
                     :method  => [String],
                     :params  => [Hash, Array],
                     :id      => [String, Fixnum, Bignum, NilClass]
                   }

  return false if !request.is_a?(Hash)

  required_keys.each do |key|
    return false if !request.has_key?(key)
  end

  required_types.each do |key, types|
    return false if request.has_key?(key) && !types.any? { |type| request[key].is_a?(type) }
  end

  return false if request[:jsonrpc] != JSON_RPC_VERSION

  true
end