Class: Jimson::Server

Inherits:
Object
  • Object
show all
Defined in:
lib/jimson/server.rb,
lib/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 Jimson::Router or extends 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?

Remaining options are forwarded to the underlying Rack server.



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

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 
  @opts = opts
end

Instance Attribute Details

#hostObject

Returns the value of attribute host.



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

def host
  @host
end

#optsObject

Returns the value of attribute opts.



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

def opts
  @opts
end

#portObject

Returns the value of attribute port.



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

def port
  @port
end

#routerObject

Returns the value of attribute router.



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

def router
  @router
end

#show_errorsObject

Returns the value of attribute show_errors.



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

def show_errors
  @show_errors
end

Class Method Details

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

Create a Server with routes defined



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

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

Instance Method Details

#call(env) ⇒ Object

Entry point for Rack



83
84
85
86
87
88
89
# File 'lib/jimson/server.rb', line 83

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



154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/jimson/server.rb', line 154

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



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/jimson/server.rb', line 176

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



196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/jimson/server.rb', line 196

def error_response(error, request = nil)
  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



115
116
117
118
119
120
121
122
123
124
125
126
127
128
# File 'lib/jimson/server.rb', line 115

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



218
219
220
221
222
# File 'lib/jimson/server.rb', line 218

def parse_request(post)
  data = MultiJson.decode(post)
  rescue 
    raise Server::Error::ParseError.new 
end

#process(content) ⇒ Object



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/jimson/server.rb', line 91

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.encode(response)
end

#startObject

Starts the server so it can process requests



72
73
74
75
76
77
78
# File 'lib/jimson/server.rb', line 72

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

#success_response(request, result) ⇒ Object



210
211
212
213
214
215
216
# File 'lib/jimson/server.rb', line 210

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

#validate_request(request) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/jimson/server.rb', line 130

def validate_request(request)
  required_keys = %w(jsonrpc method)
  required_types = {
                     'jsonrpc' => [String],
                     'method'  => [String], 
                     'params'  => [Hash, Array],
                     'id'      => [String, Integer, 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