Class: Blix::Rest::Controller

Inherits:
Object
  • Object
show all
Defined in:
lib/blix/rest/controller.rb

Constant Summary collapse

ESCAPE_HTML =

the following is copied from Rack::Utils

{
  '&' => '&',
  '<' => '&lt;',
  '>' => '&gt;',
  "'" => '&#x27;',
  '"' => '&quot;',
  '/' => '&#x2F;'
}.freeze
JS_ESCAPE_MAP =
{ '\\' => '\\\\', '</' => '<\/', "\r\n" => '\n', "\n" => '\n', "\r" => '\n', '"' => '\\"', "'" => "\\'" }.freeze
ESCAPE_HTML_PATTERN =
Regexp.union(*ESCAPE_HTML.keys)

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.__erb_pathObject

default method .. will be overridden with erb_path method



508
509
510
# File 'lib/blix/rest/controller.rb', line 508

def __erb_path
  nil
end

._after_hooksObject



600
601
602
# File 'lib/blix/rest/controller.rb', line 600

def _after_hooks
  @_after_hooks ||= {}
end

._before_hooksObject



596
597
598
# File 'lib/blix/rest/controller.rb', line 596

def _before_hooks
  @_before_hooks ||= {}
end

._do_after(ctx, *a) ⇒ Object



609
610
611
612
# File 'lib/blix/rest/controller.rb', line 609

def _do_after(ctx, *a)
  _after_hooks.each_value{ |h| ctx.instance_eval(&h) }
  superclass._do_after(ctx, *a) if superclass.respond_to? :_do_after
end

._do_before(ctx, *a) ⇒ Object



604
605
606
607
# File 'lib/blix/rest/controller.rb', line 604

def _do_before(ctx, *a)
  superclass._do_before(ctx, *a) if superclass.respond_to? :_do_before
  _before_hooks.each_value{ |h| ctx.instance_eval(&h) }
end

._do_route_hook(verb, path, opts) ⇒ Object



527
528
529
530
531
532
# File 'lib/blix/rest/controller.rb', line 527

def _do_route_hook(verb, path, opts)
  if @_route_hook
    superclass._do_route_hook(verb, path, opts) if superclass.respond_to? :_do_route_hook
    @_route_hook.call(verb, path, opts)
  end
end

.after(&block) ⇒ Object

define an after hook for a controller. only one hook can be defined per controller in a single source file.



626
627
628
629
630
631
632
# File 'lib/blix/rest/controller.rb', line 626

def after(&block)
  if block
    file = block.source_location[0]
    warn("warning: after hook already defined in #{file}") if _after_hooks[file]
    _after_hooks[file] = block
  end
end

.all(*a, &b) ⇒ Object



583
584
585
# File 'lib/blix/rest/controller.rb', line 583

def all(*a, &b)
  route 'ALL', *a, &b
end

.before(&block) ⇒ Object

define a before hook for a controller. only one hook can be defined per controller in a single source file.



616
617
618
619
620
621
622
# File 'lib/blix/rest/controller.rb', line 616

def before(&block)
  if block
    file = block.source_location[0]
    warn("warning: before hook already defined in #{file}") if _before_hooks[file]
    _before_hooks[file] = block
  end
end

.before_route(&b) ⇒ Object



591
592
593
# File 'lib/blix/rest/controller.rb', line 591

def before_route(&b)
  @_route_hook = b if b
end

.check_format(accept, format) ⇒ Object

Raises:



518
519
520
521
522
523
524
525
# File 'lib/blix/rest/controller.rb', line 518

def check_format(accept, format)
  return if (format == :json) && accept.nil?  # the majority of cases
  return if (format == :_) && accept.nil?     # assume json by default.

  accept ||= :json
  accept = [accept].flatten
  raise ServiceError, 'invalid format for this request' unless accept.index format
end

.delete(*a, &b) ⇒ Object



579
580
581
# File 'lib/blix/rest/controller.rb', line 579

def delete(*a, &b)
  route 'DELETE', *a, &b
end

.erb_dir(val) ⇒ Object

redefine the __erb_path method for this and derived classes



513
514
515
516
# File 'lib/blix/rest/controller.rb', line 513

def erb_dir(val)
  str = "def self.__erb_path;\"#{val}\";end"
  class_eval str
end

.erb_rootObject



439
440
441
442
443
444
445
446
# File 'lib/blix/rest/controller.rb', line 439

def self.erb_root
  @_erb_root ||= begin
    root = File.join(Dir.pwd, 'app', 'views')
    raise('use set_erb_root() to specify the location of your views') unless Dir.exist?(root)

    root
  end
end

.erb_templatesObject

cache templates here



431
432
433
# File 'lib/blix/rest/controller.rb', line 431

def self.erb_templates
  @_erb ||= {}
end

.get(*a, &b) ⇒ Object



559
560
561
# File 'lib/blix/rest/controller.rb', line 559

def get(*a, &b)
  route 'GET', *a, &b
end

.head(*a, &b) ⇒ Object



563
564
565
# File 'lib/blix/rest/controller.rb', line 563

def head(*a, &b)
  route 'HEAD', *a, &b
end

.no_template_cacheObject

do not cache templates in development mode



421
422
423
424
# File 'lib/blix/rest/controller.rb', line 421

def self.no_template_cache
  @_no_template_cache = (Blix::Rest.environment != 'production') if @_no_template_cache.nil?
  @_no_template_cache
end

.no_template_cache=(val) ⇒ Object



426
427
428
# File 'lib/blix/rest/controller.rb', line 426

def self.no_template_cache=(val)
  @_no_template_cache = val
end

.options(*a, &b) ⇒ Object



587
588
589
# File 'lib/blix/rest/controller.rb', line 587

def options(*a, &b)
  route 'OPTIONS', *a, &b
end

.patch(*a, &b) ⇒ Object



575
576
577
# File 'lib/blix/rest/controller.rb', line 575

def patch(*a, &b)
  route 'PATCH', *a, &b
end

.post(*a, &b) ⇒ Object



567
568
569
# File 'lib/blix/rest/controller.rb', line 567

def post(*a, &b)
  route 'POST', *a, &b
end

.put(*a, &b) ⇒ Object



571
572
573
# File 'lib/blix/rest/controller.rb', line 571

def put(*a, &b)
  route 'PUT', *a, &b
end

.render(text, context, opts = {}) ⇒ Object

render a string within a layout.



451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
# File 'lib/blix/rest/controller.rb', line 451

def render(text, context, opts = {})
  layout_name = opts[:layout]
  path        = opts[:path] || __erb_path || Controller.erb_root

  layout = layout_name && if no_template_cache
                            ERB.new(File.read(File.join(path, layout_name + '.html.erb')),:trim_mode=>'-')
                          else
                            erb_templates[layout_name] ||= ERB.new(File.read(File.join(path, layout_name + '.html.erb')),:trim_mode=>'-')
  end

  begin
    if layout
      layout.result(context._get_binding { |*_args| text })
    else
      text
    end
  rescue Exception
    ::Blix::Rest.logger <<  $!
    ::Blix::Rest.logger <<  $@
    '*** TEMPLATE ERROR ***'
  end
end

.render_erb(name, context, opts = {}) ⇒ Object



474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
# File 'lib/blix/rest/controller.rb', line 474

def render_erb(name, context, opts = {})
  name        = name.to_s
  layout_name = opts[:layout] && opts[:layout].to_s
  locals      = opts[:locals]
  path        = opts[:erb_dir] || __erb_path || Controller.erb_root

  layout = layout_name && if no_template_cache
                            ERB.new(File.read(File.join(path, layout_name + '.html.erb')),:trim_mode=>'-')
                          else
                            erb_templates[layout_name] ||= ERB.new(File.read(File.join(path, layout_name + '.html.erb')),:trim_mode=>'-')
  end

  erb = if no_template_cache
          ERB.new(File.read(File.join(path, name + '.html.erb')),:trim_mode=>'-')
        else
          erb_templates[name] ||= ERB.new(File.read(File.join(path, name + '.html.erb')),:trim_mode=>'-')
  end

  begin
    bind = context._get_binding
    locals&.each { |k, v| bind.local_variable_set(k, v) } # works from ruby 2.1
    if layout
      layout.result(context._get_binding { |*_args| erb.result(bind) })
    else
      erb.result(bind)
    end
  rescue Exception
    ::Blix::Rest.logger <<  $!
    ::Blix::Rest.logger <<  $@
    '*** TEMPLATE ERROR ***'
  end
end

.route(verb, path, opts = {}, &blk) ⇒ Object



534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
# File 'lib/blix/rest/controller.rb', line 534

def route(verb, path, opts = {}, &blk)
  path = String.new(path)  # in case frozen.
  _do_route_hook(verb.dup, path, opts)
  proc = lambda do |context|
    unless opts[:force] && (opts[:accept] == :*)
      check_format(opts[:accept], context.format)
    end
    app = new
    app._setup(context, verb, path, opts)
    begin
      app.before(opts)
      app.__before
      context.response = app.instance_eval( &blk )
    rescue
      raise
    ensure
      app.__after
      app.after(opts, context.response)
      context.response
    end
  end

  RequestMapper.add_path(verb.to_s.upcase, path, opts, &proc)
end

.set_erb_root(dir) ⇒ Object



435
436
437
# File 'lib/blix/rest/controller.rb', line 435

def self.set_erb_root(dir)
  @_erb_root = dir
end

Instance Method Details

#__after(*a) ⇒ Object

perform the after hooks



379
380
381
# File 'lib/blix/rest/controller.rb', line 379

def __after(*a)
  self.class._do_after(self, *a)
end

#__before(*a) ⇒ Object

perform the before hooks.



374
375
376
# File 'lib/blix/rest/controller.rb', line 374

def __before(*a)
  self.class._do_before(self, *a)
end

#_get_bindingObject



214
215
216
# File 'lib/blix/rest/controller.rb', line 214

def _get_binding
  binding
end

#_opt(opts, key) ⇒ Object



360
361
362
363
364
365
366
# File 'lib/blix/rest/controller.rb', line 360

def  _opt(opts,key)
  if opts.key?(key.to_sym)
    opts[key.to_sym]
  else
    opts[key.to_s]
  end
end

#_opt?(opts, key) ⇒ Boolean

Returns:

  • (Boolean)


356
357
358
# File 'lib/blix/rest/controller.rb', line 356

def _opt?(opts,key)
  opts.key?(key.to_sym) || opts.key?(key.to_s)
end

#_setup(context, _verb, _path, _parameters) ⇒ Object




397
398
399
400
401
402
403
404
405
406
407
408
409
410
# File 'lib/blix/rest/controller.rb', line 397

def _setup(context, _verb, _path, _parameters)
  @_context        = context
  @_req            = context.req
  @_env            = req.env
  @_query_params = StringHash.new(req.GET)
  @_path_params  = StringHash.new(context.path_params)
  @_format         = context.format
  @_verb           = _verb
  @_response       = context.response
  @_server_options = context.server._options
  @_parameters     = _parameters
  @_server_cache   = context.server._cache
  @_method         = context.method
end

#add_headers(headers) ⇒ Object



239
240
241
# File 'lib/blix/rest/controller.rb', line 239

def add_headers(headers)
  @_response.headers.merge!(headers.map{|k,v| [k.to_s.downcase,v]}.to_h)
end

#after(_opts, response) ⇒ Object

a hook used to insert processing for after the method call. return a hash containing the response.



391
392
393
# File 'lib/blix/rest/controller.rb', line 391

def after(_opts, response)
  response
end

#auth_error(*params) ⇒ Object

Raises:



289
290
291
292
293
294
295
296
297
298
# File 'lib/blix/rest/controller.rb', line 289

def auth_error(*params)
  if params[0].kind_of?(String)
      message = params[0]
      opts = params[1] || {}
  else
      message = nil
      opts = params[-1] || {}
  end
  raise AuthorizationError.new(message,opts[:realm], opts[:type])
end

#before(opts) ⇒ Object

a hook used to insert processing for before the method call



387
# File 'lib/blix/rest/controller.rb', line 387

def before(opts); end

#bodyObject



78
79
80
81
# File 'lib/blix/rest/controller.rb', line 78

def body
  @_body ||= env['rack.input'].read
  #      env['rack.input'].rewindreq.POST #env["body"]
end

#body_hashObject



101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/blix/rest/controller.rb', line 101

def body_hash
  @_body_hash ||= if body.empty?
                    {}
                  else
                    # should we check the content type here?
                    begin
                      StringHash.new(MultiJson.load(body))
                    rescue StandardError
                      raise ServiceError, "error in data json format/#{body}/"
                    end
  end
end

#envObject


convenience methods




41
42
43
# File 'lib/blix/rest/controller.rb', line 41

def env
  @_env
end

#escape_javascript(javascript) ⇒ Object

escape javascript



263
264
265
266
267
268
269
# File 'lib/blix/rest/controller.rb', line 263

def escape_javascript(javascript)
  if javascript
    javascript.gsub(%r{(\|</|\r\n|\342\200\250|\342\200\251|[\n\r"'])}u) { |match| JS_ESCAPE_MAP[match] }
  else
    ''
  end
end

#form_hashObject



97
98
99
# File 'lib/blix/rest/controller.rb', line 97

def form_hash
  StringHash.new(req.POST)
end

#formatObject



118
119
120
# File 'lib/blix/rest/controller.rb', line 118

def format
  @_format
end

#full_path(path) ⇒ Object

add on the root path



182
183
184
# File 'lib/blix/rest/controller.rb', line 182

def full_path(path)
  RequestMapper.full_path(path)
end

#full_url(_path) ⇒ Object

the full url of this path.



187
188
189
# File 'lib/blix/rest/controller.rb', line 187

def full_url(_path)
  raise 'not yet implemented'
end

#get_basic_auth(realm = nil) ⇒ Object

extract the user and login from the basic authentication

Raises:



219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/blix/rest/controller.rb', line 219

def get_basic_auth(realm=nil)
  data = env['HTTP_AUTHORIZATION']
  raise AuthorizationError.new('authentication missing',realm) unless data

  type = data[0, 5]
  rest = data[6..-1]

  raise  AuthorizationError.new('wrong authentication method',realm) unless type == 'Basic'
  raise  AuthorizationError.new('username:password missing',realm)   unless rest

  auth_parts = Base64.decode64(rest).split(':')
   = auth_parts[0]
  password = auth_parts[1]
  [, password]
end


300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/blix/rest/controller.rb', line 300

def get_cookie(name)
  cookie_header = env['HTTP_COOKIE']
  cookie_length = name.length
  parts = cookie_header&.split(';')
  value = nil
  parts&.reverse&.each do |cookie|
    cookie.strip!
    if cookie[0..cookie_length] == name + '='
      value = cookie[cookie_length + 1..-1]
      break
    end
  end
  value
end

#get_data(field) ⇒ Object



114
115
116
# File 'lib/blix/rest/controller.rb', line 114

def get_data(field)
  body_hash['data'] && body_hash['data'][field]
end

#get_session_id(session_name, opts = {}) ⇒ Object

manage session handling ————————————————– setup the session and retrieve the session_id this id can be used to retrieve and data associated with the session_id in eg: a database or a memory hash



345
346
347
348
# File 'lib/blix/rest/controller.rb', line 345

def get_session_id(session_name, opts = {})
  session_id = get_cookie(session_name)
  session_id || refresh_session_id(session_name, opts)
end

#h(string) ⇒ Object

Escape ampersands, brackets and quotes to their HTML/XML entities.



258
259
260
# File 'lib/blix/rest/controller.rb', line 258

def h(string)
  string.to_s.gsub(ESCAPE_HTML_PATTERN) { |c| ESCAPE_HTML[c] }
end

#inspectObject



416
417
418
# File 'lib/blix/rest/controller.rb', line 416

def inspect
  to_s
end

#loggerObject



58
59
60
# File 'lib/blix/rest/controller.rb', line 58

def logger
  Blix::Rest.logger
end

#methodObject



161
162
163
# File 'lib/blix/rest/controller.rb', line 161

def method
  @_method
end

#mode_development?Boolean

Returns:

  • (Boolean)


70
71
72
# File 'lib/blix/rest/controller.rb', line 70

def mode_development?
  rack_env == 'development'
end

#mode_production?Boolean

Returns:

  • (Boolean)


74
75
76
# File 'lib/blix/rest/controller.rb', line 74

def mode_production?
  rack_env == 'production'
end

#mode_test?Boolean

Returns:

  • (Boolean)


66
67
68
# File 'lib/blix/rest/controller.rb', line 66

def mode_test?
  rack_env == 'test'
end

#paramsObject



130
131
132
# File 'lib/blix/rest/controller.rb', line 130

def params
  @_params ||= StringHash.new(@_query_params,@_path_params)
end

#pathObject

ovverride the path method to return the internal path.



84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/blix/rest/controller.rb', line 84

def path
  p = req.path
  p = '/' + p if p[0, 1] != '/' # ensure a leading slash on path
  idx = RequestMapper.path_root_length
  if idx > 0
    p = p[idx..-1] || '/'
    p = '/' + p if p[0, 1] != '/' # ensure a leading slash on path
    p
  else
    p
  end
end

#path_for(path) ⇒ Object



145
146
147
# File 'lib/blix/rest/controller.rb', line 145

def path_for(path)
  File.join(RequestMapper.path_root, path || '')
end

#path_paramsObject



126
127
128
# File 'lib/blix/rest/controller.rb', line 126

def path_params
  @_path_params
end

#post_paramsObject



134
135
136
137
138
139
140
141
142
143
# File 'lib/blix/rest/controller.rb', line 134

def post_params
  @_post_params ||= begin
    type = req.media_type
    if type && Rack::Request::FORM_DATA_MEDIA_TYPES.include?(type)
      form_hash
    else
      body_hash
    end
  end
end

#query_paramsObject



122
123
124
# File 'lib/blix/rest/controller.rb', line 122

def query_params
  @_query_params
end

#rack_envObject



62
63
64
# File 'lib/blix/rest/controller.rb', line 62

def rack_env
  ENV['RACK_ENV']
end

#rawjson(str) ⇒ Object



210
211
212
# File 'lib/blix/rest/controller.rb', line 210

def rawjson(str)
  RawJsonString.new(str)
end

#redirect(path, status = 302) ⇒ Object Also known as: redirect_to

Raises:



191
192
193
# File 'lib/blix/rest/controller.rb', line 191

def redirect(path, status = 302)
  raise ServiceError.new(nil, status, 'location' => RequestMapper.ensure_full_path(path))
end

#refresh_session_id(session_name, opts = {}) ⇒ Object

generate an new session_id for the current session



351
352
353
354
# File 'lib/blix/rest/controller.rb', line 351

def refresh_session_id(session_name, opts = {})
  session_id = SecureRandom.hex(32)
  store_session_id(session_name, session_id, opts)
end

#render(text, opts = {}) ⇒ Object



206
207
208
# File 'lib/blix/rest/controller.rb', line 206

def render(text, opts = {})
  self.class.render_erb(text, self, opts)
end

#render_erb(template_name, opts = {}) ⇒ Object

render an erb template with the variables in the controller



202
203
204
# File 'lib/blix/rest/controller.rb', line 202

def render_erb(template_name, opts = {})
  self.class.render_erb(template_name, self, opts)
end

#reqObject



153
154
155
# File 'lib/blix/rest/controller.rb', line 153

def req
  @_req
end

#request_ipObject



197
198
199
# File 'lib/blix/rest/controller.rb', line 197

def request_ip
  req.ip
end

#responseObject



169
170
171
# File 'lib/blix/rest/controller.rb', line 169

def response
  @_response
end

#route_parametersObject



165
166
167
# File 'lib/blix/rest/controller.rb', line 165

def route_parameters
  @_parameters
end

#send_data(data, opts = {}) ⇒ Object

send data to browser as attachment

Raises:



277
278
279
280
281
282
283
284
285
286
287
# File 'lib/blix/rest/controller.rb', line 277

def send_data(data, opts = {})
  add_headers 'content-type'=> opts[:type] || 'application/octet-stream'
  if opts[:filename]
    add_headers 'content-disposition'=>'attachment;filename='+ opts[:filename]
  elsif opts[:disposition] == 'attachment'
    add_headers 'content-disposition'=>'attachment'
  elsif opts[:disposition] == 'inline'
    add_headers 'content-disposition'=>'inline'
  end
  raise RawResponse.new(data, opts[:status] || 200)
end

#send_error(message, status = nil, headers = nil) ⇒ Object

send a (default) error

Raises:



272
273
274
# File 'lib/blix/rest/controller.rb', line 272

def send_error(message, status = nil, headers = nil)
  raise ServiceError.new(message, status, headers)
end

#server_cacheObject



50
51
52
# File 'lib/blix/rest/controller.rb', line 50

def server_cache
  @_server_cache
end

#server_cache_get(key) ⇒ Object



54
55
56
# File 'lib/blix/rest/controller.rb', line 54

def server_cache_get(key)
  server_cache[key] ||= yield if block_given?
end

#server_optionsObject

options that were passed to the server at create time.



46
47
48
# File 'lib/blix/rest/controller.rb', line 46

def server_options
  @_server_options
end

#sessionObject



177
178
179
# File 'lib/blix/rest/controller.rb', line 177

def session
  req.session
end

#set_status(value) ⇒ Object



235
236
237
# File 'lib/blix/rest/controller.rb', line 235

def set_status(value)
  @_response.status = value.to_i
end


315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/blix/rest/controller.rb', line 315

def store_cookie(name, value, opts={})
  cookie_text = String.new("#{name}=#{value}")
  cookie_text << '; Secure'                                  if _opt?(opts,:secure)
  cookie_text << '; HttpOnly'                                if _opt?(opts,:http)
  cookie_text << "; HostOnly=#{_opt(opts,:hostOnly)}"        if _opt?(opts,:hostOnly)
  cookie_text << "; Expires=#{_opt(opts,:expires).httpdate}" if _opt?(opts,:expires)
  cookie_text << "; Max-Age=#{_opt(opts,:max_age)}"          if _opt?(opts,:max_age)
  cookie_text << "; Domain=#{_opt(opts,:domain)}"            if _opt?(opts,:domain)
  cookie_text << "; Path=#{_opt(opts,:path)}"                if _opt?(opts,:path)
  if policy = _opt(opts,:samesite)
    cookie_text << '; SameSite=Strict' if policy.to_s.downcase == 'strict'
    cookie_text << '; SameSite=Lax'    if policy.to_s.downcase == 'lax'
    cookie_text << '; SameSite=None'   if policy.to_s.downcase == 'none'
  end
  @_cookies ||= {}
  @_cookies[name] = cookie_text
  # cookie_header = @_response.headers['Set-Cookie']
  # if cookie_header
  #   cookie_header = cookie_header << "\n" << cookie_text
  # else
  #   cookie_header = cookie_text
  # end
  @_response.headers['set-cookie'] = @_cookies.values.join("\n")
  value
end

#store_session_id(session_name, session_id, opts = {}) ⇒ Object

set the cookie header that stores the session_id on the browser.



369
370
371
# File 'lib/blix/rest/controller.rb', line 369

def store_session_id(session_name, session_id, opts = {})
  store_cookie(session_name, session_id, opts)
end

#to_sObject



412
413
414
# File 'lib/blix/rest/controller.rb', line 412

def to_s
  "<#{self.class.to_s}:#{object_id}>"
end

#url_for(path) ⇒ Object



149
150
151
# File 'lib/blix/rest/controller.rb', line 149

def url_for(path)
  req.base_url + path_for(path)
end

#verbObject



157
158
159
# File 'lib/blix/rest/controller.rb', line 157

def verb
  @_verb
end