Class: QB::IPC::RPC::Server

Inherits:
Object
  • Object
show all
Includes:
NRSER::Log::Mixin
Defined in:
lib/qb/ipc/rpc/server.rb

Overview

TODO:

document Server class.

Constant Summary collapse

CONTENT_TYPE_JSON =

Constants

{ 'Content-Type' => 'application/json' }.freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(unicorn_log_level: :warn) ⇒ Server

Instantiate a new Server, which wraps a Unicorn::HttpServer instance.

Parameters:

  • unicorn_log_level (::Symbol) (defaults to: :warn)

    Log level for the Unicorn HTTP server. Defaults to :warn so that we don't see it's info log in qb STDOUT.



135
136
137
138
139
140
141
142
143
144
145
# File 'lib/qb/ipc/rpc/server.rb', line 135

def initialize unicorn_log_level: :warn
  @socket_dir = Dir.mktmpdir( 'qb-ipc-rpc' ).to_pn
  @socket_path = socket_dir + 'socket'
  
  unicorn_logger = NRSER::Log[ "#{ self.class.name }#http_server" ]
  unicorn_logger.level = unicorn_log_level
  
  @http_server = ::Unicorn::HttpServer.new self,
    listeners: socket_path.to_s,
    logger: unicorn_logger
end

Instance Attribute Details

#http_serverUnicorn::HTTPServer (readonly)

TODO document http_server attribute.

Returns:

  • (Unicorn::HTTPServer)


123
124
125
# File 'lib/qb/ipc/rpc/server.rb', line 123

def http_server
  @http_server
end

#socket_dirPathname (readonly)

Temp dir where the socket goes.

Returns:

  • (Pathname)


109
110
111
# File 'lib/qb/ipc/rpc/server.rb', line 109

def socket_dir
  @socket_dir
end

#socket_pathPathname (readonly)

Absolute path to the socket file.

Returns:

  • (Pathname)


116
117
118
# File 'lib/qb/ipc/rpc/server.rb', line 116

def socket_path
  @socket_path
end

Class Method Details

.instancereturn_type

TODO:

Document instance method.

Returns @todo Document return value.

Parameters:

  • arg_name (type)

    @todo Add name param description.

Returns:

  • (return_type)

    @todo Document return value.



66
67
68
# File 'lib/qb/ipc/rpc/server.rb', line 66

def self.instance
  @instance
end

.run_around(&block) ⇒ return_type

TODO:

Document run! method.

Returns @todo Document return value.

Parameters:

  • arg_name (type)

    @todo Add name param description.

Returns:

  • (return_type)

    @todo Document return value.



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/qb/ipc/rpc/server.rb', line 78

def self.run_around &block
  unless ENV[QB::IPC::RPC::ENV_VAR_NAME].to_s == ''
    raise NRSER::ConflictError.new \
      "RPC ENV var already set",
      var_name:   QB::IPC::RPC::ENV_VAR_NAME,
      var_value:  ENV[QB::IPC::RPC::ENV_VAR_NAME]
  end

  @instance = new.start!

  ENV[QB::IPC::RPC::ENV_VAR_NAME] = instance.socket_path.to_s

  begin
    block_result = block.call
  ensure
    instance.stop!
    @instance = nil
    ENV.delete QB::IPC::RPC::ENV_VAR_NAME
  end

  block_result
end

Instance Method Details

#call(env) ⇒ Object



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
# File 'lib/qb/ipc/rpc/server.rb', line 262

def call env
  begin
    request = Rack::Request.new env
    body = request.body.read
    payload = JSON.load body

    logger.trace "Received request",
      path: request.path,
      body: body,
      payload: payload
    
    route request.path, payload
    
  rescue StandardError => error
    logger.error "Error processing request",
      { request: request },
      error
    
    respond_error message: error.message
  end
end

#handle_plugins_filtersObject



202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/qb/ipc/rpc/server.rb', line 202

def handle_plugins_filters
  map = {}
  
  QB::Ansible::Plugins::Filters.methods( false ).each { |method|
    map[method] = {
      receiver: 'QB::Ansible::Plugins::Filters',
      method:   method.to_s,
    }
  }
  
  respond_ok data: map
end

#handle_send(payload) ⇒ Object



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
# File 'lib/qb/ipc/rpc/server.rb', line 216

def handle_send payload
  receiver = payload.fetch 'receiver'
  method = payload.fetch 'method'
  args = payload.fetch 'args', []

  if payload['kwds'] && !payload['kwds'].empty?
    args = [*args, payload['kwds'].to_options]
  end

  logger.trace "Unpacked /send payload",
    receiver: receiver,
    method: method,
    args: args

  if  Hash === receiver &&
      receiver.key?( NRSER::Props::DEFAULT_CLASS_KEY )
    
    logger.trace "Loading payload into a " + 
      "#{ receiver[NRSER::Props::DEFAULT_CLASS_KEY] }..."

    receiver = NRSER::Props.UNSAFE_load_instance_from_data receiver
  elsif   String === receiver &&
          receiver =~ /\A(?:(?:\:\:)?[A-Z]\w*)+\z/
    if (const = receiver.to_const)
      receiver = const
    end
  end

  logger.trace "Sending...",
    receiver: receiver,
    method: method,
    args: args

  # For some reason this doesn't work:
  # result = receiver.send method, *args, **kwds
  # 
  # So we do this (after conditionally appending kwds up top)
  result = receiver.send method, *args

  logger.trace "Got result, responding",
    result: result

  respond_ok data: result
end

#respond(code, values = {}) ⇒ Object



163
164
165
166
167
168
169
170
171
172
# File 'lib/qb/ipc/rpc/server.rb', line 163

def respond code, values = {}
  [
    code.to_s,
    CONTENT_TYPE_JSON,
    [ values.to_json ]
].tap { |response|
  logger.trace "Responding",
    response: response
}
end

#respond_error(code: 500, message: 'Server error') ⇒ Object



180
181
182
# File 'lib/qb/ipc/rpc/server.rb', line 180

def respond_error code: 500, message: 'Server error'
  respond code, message: message
end

#respond_not_found(message: 'Not found') ⇒ Object



185
186
187
# File 'lib/qb/ipc/rpc/server.rb', line 185

def respond_not_found message: 'Not found'
  respond 404, message: message
end

#respond_ok(values = {}) ⇒ Object



175
176
177
# File 'lib/qb/ipc/rpc/server.rb', line 175

def respond_ok values = {}
  respond 200, values
end

#route(path, payload) ⇒ Object



190
191
192
193
194
195
196
197
198
199
# File 'lib/qb/ipc/rpc/server.rb', line 190

def route path, payload
  case path
  when '/send'
    handle_send payload
  when '/plugins/filters'
    handle_plugins_filters
  else
    respond_not_found
  end
end

#start!Object

Instance Methods



151
152
153
154
# File 'lib/qb/ipc/rpc/server.rb', line 151

def start!
  http_server.start
  self
end

#stop!(graceful: true) ⇒ Object



157
158
159
160
# File 'lib/qb/ipc/rpc/server.rb', line 157

def stop! graceful: true
  http_server.stop graceful
  self
end