blix-rest 0.1.30 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c1b29a1f9ce5565c28991acebdb11f7a4e785db7760c59905936a9b2e8a6fa72
4
- data.tar.gz: 5cc8fc070fb5fc09c3256dbc65b8087e6a17df7ed0bfc80b51fb9bd43ab38609
3
+ metadata.gz: cf93b3d5836172b6cf1b4ac5df21dfe14e1e998e31e774f9b4fdb4bc5bdec323
4
+ data.tar.gz: 622db8bf8d0052fd25f3727656ac6b641bc63a2667e97eb3f93f7dc4884e03ee
5
5
  SHA512:
6
- metadata.gz: bf6e866a78e15c268c364b4a87765a05440e1b6f1f6173f442d0a82045fbae4c94df7dcb2afb46c8e27f8ef017f0dea255de9cb85321059f2015f87fc3ee0404
7
- data.tar.gz: 7e057e8b9b1dbc9bd1f46b952d071e51fc9d805b5e3236e8c5722902c839106bbece17de8175bcbf66a3ed2159c38121a88ad00d7ef68a94a70384e331e70176
6
+ metadata.gz: 32516afd36a4a6c36c81650e50f84970cf2436b7cf682a51cae83d1300f736d4dbfe49d04d590ac8ffde841d2dac7f236d0979b3b5c1d5bec230112584eb6e0d
7
+ data.tar.gz: 7f106344ec03beb691d5870c5809f379285d578ba17cb9b8edd1ea86c5c64dfa31b0a9747a75adab7730ba514cecca3a508cb36f5560343f147cd64d866ca3d4
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2017 Clive Andrews
3
+ Copyright (c) 2017-2020 Clive Andrews
4
4
 
5
5
 
6
6
  Permission is hereby granted, free of charge, to any person
@@ -22,4 +22,4 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22
22
  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23
23
  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24
24
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25
- OTHER DEALINGS IN THE SOFTWARE.
25
+ OTHER DEALINGS IN THE SOFTWARE.
data/README.md CHANGED
@@ -71,7 +71,7 @@ if there is a more specific path then it will be used first :
71
71
  ### Path options
72
72
 
73
73
  :accept : the format or formats to accept eg: :html or [:png, :jpeg]
74
- :default : default format if not derived through oher means.
74
+ :default : default format if not derived through other means.
75
75
  :force : force response into the given format
76
76
  :query : derive format from request query (default: false)
77
77
  :extension : derive format from path extension (default: true)
@@ -189,10 +189,19 @@ use the following to accept requests in a special format ..
189
189
 
190
190
  get '/custom', :accept=>:xyz, :force=>:raw do
191
191
  add_headers 'Content-Type'=>'text/xyz'
192
-
193
192
  "xyz"
194
193
  end
195
194
 
195
+
196
+ Alternatively it is possible to raise a RawResponse:
197
+
198
+ add_headers 'Content-Type'=>'text/xyz'
199
+ raise RawResponse, 'xyz'
200
+
201
+ or with status and headers:
202
+
203
+ raise RawResponse.new('xyz', 123, 'Content-Type'=>'text/xyz')
204
+
196
205
  ## FORMATS
197
206
 
198
207
  the format of a request is derived from
@@ -218,32 +227,39 @@ base class for controllers. within your block handling a particular route you
218
227
  have access to a number of methods
219
228
 
220
229
 
221
- env : the request environment hash
222
- method : the request method lowercase( 'get'/'post' ..)
223
- req : the rack request
224
- body : the request body as a string
225
- query_params : a hash of parameters as passed in the url as parameters
226
- path_params : a hash of parameters constructed from variable parts of the path
227
- post_params : a hash of parameters passed in the body of the request
228
- params : all the params combined
229
- user : the user making this request ( or nil if
230
- format : the format the response should be in :json or :html
231
- before : before hook ( opts ) - remember to add 'super' as first line !!!
232
- after : after hook (opts,response)- remember to add 'super' as first line !!!
233
- proxy : forward the call to another service (service,path, opts={}) , :include_query=>true/false
234
- session : req.session
235
- redirect : (path, status=302) redirect to another url.
236
- request_ip : the ip of the request
237
- render_erb : (template_name [,:layout=>name])
238
- path_for : (path) give the correct path for an internal path
239
- url_for : (path) give the full url for an internal path
240
- h : escape html string to avoid XSS
241
- escape_javascript : escape a javascript string
242
- server_options : options as passed to server at create time
243
- mode_test? : test mode ?
244
- mode_production? : production mode ?
245
- mode_development? : development mode?
246
-
230
+ env : the request environment hash
231
+ method : the request method lowercase( 'get'/'post' ..)
232
+ req : the rack request
233
+ body : the request body as a string
234
+ query_params : a hash of parameters as passed in the url as parameters
235
+ path_params : a hash of parameters constructed from variable parts of the path
236
+ post_params : a hash of parameters passed in the body of the request
237
+ params : all the params combined
238
+ user : the user making this request ( or nil if
239
+ format : the format the response should be in :json or :html
240
+ before : before hook ( opts ) - remember to add 'super' as first line !!!
241
+ after : after hook (opts,response)- remember to add 'super' as first line !!!
242
+ proxy : forward the call to another service (service,path, opts={}) , :include_query=>true/false
243
+ session : req.session
244
+ redirect : (path, status=302) redirect to another url.
245
+ request_ip : the ip of the request
246
+ render_erb : (template_name [,:layout=>name])
247
+ server_cache : get the server cache object
248
+ server_cache_get : retrieve/store value in cache
249
+ path_for : (path) give the correct path for an internal path
250
+ url_for : (path) give the full url for an internal path
251
+ h : escape html string to avoid XSS
252
+ escape_javascript : escape a javascript string
253
+ server_options : options as passed to server at create time
254
+ logger : system logger
255
+ mode_test? : test mode ?
256
+ mode_production? : production mode ?
257
+ mode_development? : development mode?
258
+ send_data : send raw data (data, options )
259
+ [:type=>mimetype]
260
+ [:filename=>name]
261
+ [:disposition=>inline|attachment]
262
+ [:status=>234]
247
263
 
248
264
  get_session_id(session_name, opts={}) :
249
265
  refresh_session_id(session_name, opts={}) :
@@ -252,6 +268,45 @@ to accept requests other than json then set `:accept=>[:json,:html]` as options
252
268
 
253
269
  eg `post '/myform' :accept=>[:html] # this will only accept html requests.`
254
270
 
271
+ ### Hooks
272
+
273
+ a before or after hook can be defined on a controller. Only define the hook once
274
+ for a given controller per source file. A hook included from another source file
275
+ is ok though.
276
+
277
+ class MyController < Blix::Rest::Controller
278
+
279
+ before do
280
+ ...
281
+ end
282
+
283
+ after do
284
+ ...
285
+ end
286
+
287
+ end
288
+
289
+
290
+ #### manipulate the route path or options
291
+
292
+ the `before_route` hook can be used to modify the path or options of a route.
293
+
294
+ *NOTE* ! when manipulating the path you have to modify the string object in place.
295
+
296
+ the verb can not be modified
297
+
298
+ example:
299
+
300
+ class MyController < Blix::Rest::Controller
301
+
302
+ before_route do |verb, path, opts|
303
+ opts[:level] = :visitor unless opts[:level]
304
+ path.prepend('/') unless path[0] == '/'
305
+ path.prepend('/app') unless path[0, 4] == '/app'
306
+ end
307
+ ...
308
+ end
309
+
255
310
  ### Sessions
256
311
 
257
312
  the following methods are available in the controller for managing sessions.
@@ -272,7 +327,78 @@ options can include:
272
327
  :samesite =>:lax # use lax x-site policy
273
328
 
274
329
 
330
+ ## Cache
331
+
332
+
333
+ the server has a cache which can also be used for storing your own data.
334
+
335
+ within a controller access the controller with `server_cache` which returns the
336
+ cache object.
337
+
338
+ cache object methods:
339
+
340
+ get(key) # return value from the cache or nil
341
+ set(key,value) # set a value in the cache
342
+ key?(key) # is a key present in the cache
343
+ delete(key) # delete a key from the cache
344
+ clear # delete all keys from the cache.
345
+
346
+ there is also a `server_cache_get` method.
347
+
348
+ server_cache_get(key){ action }
349
+
350
+ get the value from the cache. If the key is missing in the cache then perform
351
+ the action in the provided block and store the result in the cache.
352
+
353
+ the default cache is just a ruby hash in memory. Pass a custom cache to
354
+ when creating a server with the `:cache` parameter.
355
+
356
+ class MyCache < Blix::Rest::Cache
357
+ def get(key)
358
+ ..
359
+ end
360
+
361
+ def set(key,value)
362
+ ..
363
+ end
364
+
365
+ def key?(key)
366
+ ..
367
+ end
368
+
369
+ def delete(key)
370
+ ..
371
+ end
372
+
373
+ def clear
374
+ ..
375
+ end
376
+ end
377
+
378
+ cache = MyCache.new
379
+
380
+ app = Blix::Rest::Server.new(:cache=>cache)
381
+
382
+ there is a redis cache already defined:
383
+
384
+ require 'blix/rest/redis_cache'
385
+
386
+ cache = Blix::Rest::RedisCache.new(:expire_secs=>60*60*24) # expire after 1 day
387
+ run Blix::Rest::Server.new(:cache=>cache)
388
+
389
+
390
+ ### automatically cache server responses
391
+
392
+ add `:cache=>true` to your route options in order to cache this route.
393
+
394
+ add `:cache_reset=>true` to your route options if the cache should be cleared when
395
+ calling this route.
396
+
397
+ the cache is not used in development/testmode , only in production mode.
398
+
399
+ ### IMPORTANT - DO NOT CACHE SESSION KEYS AND OTHER SPECIFIC DATA IN CACHE
275
400
 
401
+ only cache pages with **HEADERS** and **CONTENT** that is not user specific.
276
402
 
277
403
  ## Views
278
404
 
@@ -0,0 +1,79 @@
1
+ module Blix::Rest
2
+
3
+ # cache server responses
4
+ class Cache
5
+
6
+ attr_reader :params
7
+
8
+ def initialize(params={})
9
+ @params = params
10
+ end
11
+
12
+ def [](key)
13
+ get(key)
14
+ end
15
+
16
+ def []=(key,data)
17
+ set(key, data)
18
+ end
19
+
20
+ #--------------- redefine these methods ..
21
+
22
+ # clear all data from the cache
23
+ def clear
24
+
25
+ end
26
+
27
+ # retrieve data from the cache
28
+ def get(key)
29
+
30
+ end
31
+
32
+ # set data in the cache
33
+ def set(key, data)
34
+
35
+ end
36
+
37
+ # is key present in the cache
38
+ def key?(key)
39
+
40
+ end
41
+
42
+ def delete(key)
43
+
44
+ end
45
+
46
+ #---------------------------------------------------------------------------
47
+
48
+
49
+ end
50
+
51
+ # implement cache as a simple ruby hash
52
+ class MemoryCache < Cache
53
+
54
+ def cache
55
+ @cache ||= {}
56
+ end
57
+
58
+ def get(key)
59
+ cache[key]
60
+ end
61
+
62
+ def set(key, data)
63
+ cache[key] = data
64
+ end
65
+
66
+ def clear
67
+ cache.clear
68
+ end
69
+
70
+ def key?(key)
71
+ cache.key?(key)
72
+ end
73
+
74
+ def delete(key)
75
+ cache.delete(key)
76
+ end
77
+
78
+ end
79
+ end
@@ -3,6 +3,7 @@
3
3
  require 'base64'
4
4
  require 'erb'
5
5
  require 'securerandom'
6
+ require 'digest'
6
7
 
7
8
  module Blix::Rest
8
9
  # base class for controllers. within your block handling a particular route you
@@ -21,8 +22,19 @@ module Blix::Rest
21
22
  # to accept requests other thatn json then set :accept=>[:json,:html] as options in the route
22
23
  # eg post '/myform' :accept=>[:html] # this will only accept html requests.
23
24
 
25
+ Context = Struct.new(
26
+ :path_params,
27
+ :params,
28
+ :req,
29
+ :format,
30
+ :response,
31
+ :method,
32
+ :server
33
+ )
34
+
24
35
  class Controller
25
36
 
37
+
26
38
  #--------------------------------------------------------------------------------------------------------
27
39
  # convenience methods
28
40
  #--------------------------------------------------------------------------------------------------------
@@ -35,6 +47,14 @@ module Blix::Rest
35
47
  @_server_options
36
48
  end
37
49
 
50
+ def server_cache
51
+ @_server_cache
52
+ end
53
+
54
+ def server_cache_get(key)
55
+ server_cache[key] ||= yield if block_given?
56
+ end
57
+
38
58
  def logger
39
59
  Blix::Rest.logger
40
60
  end
@@ -60,8 +80,18 @@ module Blix::Rest
60
80
  # env['rack.input'].rewindreq.POST #env["body"]
61
81
  end
62
82
 
83
+ # ovverride the path method to return the internal path.
63
84
  def path
64
- req.path
85
+ p = req.path
86
+ p = '/' + p if p[0, 1] != '/' # ensure a leading slash on path
87
+ idx = RequestMapper.path_root_length
88
+ if idx > 0
89
+ p = p[idx..-1] || '/'
90
+ p = '/' + p if p[0, 1] != '/' # ensure a leading slash on path
91
+ p
92
+ else
93
+ p
94
+ end
65
95
  end
66
96
 
67
97
  def form_hash
@@ -113,7 +143,7 @@ module Blix::Rest
113
143
  end
114
144
 
115
145
  def path_for(path)
116
- File.join(RequestMapper.path_root, path)
146
+ File.join(RequestMapper.path_root, path || '')
117
147
  end
118
148
 
119
149
  def url_for(path)
@@ -128,6 +158,18 @@ module Blix::Rest
128
158
  @_verb
129
159
  end
130
160
 
161
+ def method
162
+ @_method
163
+ end
164
+
165
+ def route_parameters
166
+ @_parameters
167
+ end
168
+
169
+ def response
170
+ @_response
171
+ end
172
+
131
173
  def method
132
174
  env['REQUEST_METHOD'].downcase
133
175
  end
@@ -147,7 +189,7 @@ module Blix::Rest
147
189
  end
148
190
 
149
191
  def redirect(path, status = 302)
150
- raise ServiceError.new(nil, status, 'Location' => path)
192
+ raise ServiceError.new(nil, status, 'location' => RequestMapper.ensure_full_path(path))
151
193
  end
152
194
 
153
195
  alias redirect_to redirect
@@ -191,11 +233,11 @@ module Blix::Rest
191
233
  end
192
234
 
193
235
  def set_status(value)
194
- @_response.status = value
236
+ @_response.status = value.to_i
195
237
  end
196
238
 
197
239
  def add_headers(headers)
198
- @_response.headers.merge!(headers)
240
+ @_response.headers.merge!(headers.map{|k,v| [k.to_s.downcase,v]}.to_h)
199
241
  end
200
242
 
201
243
  # the following is copied from Rack::Utils
@@ -231,6 +273,19 @@ module Blix::Rest
231
273
  raise ServiceError.new(message, status, headers)
232
274
  end
233
275
 
276
+ # send data to browser as attachment
277
+ def send_data(data, opts = {})
278
+ add_headers 'content-type'=> opts[:type] || 'application/octet-stream'
279
+ if opts[:filename]
280
+ add_headers 'content-disposition'=>'attachment;filename='+ opts[:filename]
281
+ elsif opts[:disposition] == 'attachment'
282
+ add_headers 'content-disposition'=>'attachment'
283
+ elsif opts[:disposition] == 'inline'
284
+ add_headers 'content-disposition'=>'inline'
285
+ end
286
+ raise RawResponse.new(data, opts[:status] || 200)
287
+ end
288
+
234
289
  def auth_error(*params)
235
290
  if params[0].kind_of?(String)
236
291
  message = params[0]
@@ -279,7 +334,7 @@ module Blix::Rest
279
334
  # else
280
335
  # cookie_header = cookie_text
281
336
  # end
282
- @_response.headers['Set-Cookie'] = @_cookies.values.join("\n")
337
+ @_response.headers['set-cookie'] = @_cookies.values.join("\n")
283
338
  value
284
339
  end
285
340
 
@@ -315,6 +370,16 @@ module Blix::Rest
315
370
  store_cookie(session_name, session_id, opts)
316
371
  end
317
372
 
373
+ # perform the before hooks.
374
+ def __before(*a)
375
+ self.class._do_before(self, *a)
376
+ end
377
+
378
+ # perform the after hooks
379
+ def __after(*a)
380
+ self.class._do_after(self, *a)
381
+ end
382
+
318
383
  #----------------------------------------------------------------------------------------------------------
319
384
  # template methods that can be overwritten
320
385
 
@@ -329,15 +394,27 @@ module Blix::Rest
329
394
 
330
395
  #----------------------------------------------------------------------------------------------------------
331
396
 
332
- def initialize(path_params, _params, req, format, verb, response, server_options)
333
- @_req = req
334
- @_env = req.env
397
+ def _setup(context, _verb, _path, _parameters)
398
+ @_context = context
399
+ @_req = context.req
400
+ @_env = req.env
335
401
  @_query_params = StringHash.new(req.GET)
336
- @_path_params = StringHash.new(path_params)
337
- @_format = format
338
- @_verb = verb
339
- @_response = response
340
- @_server_options = server_options
402
+ @_path_params = StringHash.new(context.path_params)
403
+ @_format = context.format
404
+ @_verb = _verb
405
+ @_response = context.response
406
+ @_server_options = context.server._options
407
+ @_parameters = _parameters
408
+ @_server_cache = context.server._cache
409
+ @_method = context.method
410
+ end
411
+
412
+ def to_s
413
+ "<#{self.class.to_s}:#{object_id}>"
414
+ end
415
+
416
+ def inspect
417
+ to_s
341
418
  end
342
419
 
343
420
  # do not cache templates in development mode
@@ -376,9 +453,9 @@ module Blix::Rest
376
453
  path = opts[:path] || __erb_path || Controller.erb_root
377
454
 
378
455
  layout = layout_name && if no_template_cache
379
- ERB.new(File.read(File.join(path, layout_name + '.html.erb')),nil,'-')
456
+ ERB.new(File.read(File.join(path, layout_name + '.html.erb')),:trim_mode=>'-')
380
457
  else
381
- erb_templates[layout_name] ||= ERB.new(File.read(File.join(path, layout_name + '.html.erb')),nil,'-')
458
+ erb_templates[layout_name] ||= ERB.new(File.read(File.join(path, layout_name + '.html.erb')),:trim_mode=>'-')
382
459
  end
383
460
 
384
461
  begin
@@ -388,8 +465,8 @@ module Blix::Rest
388
465
  text
389
466
  end
390
467
  rescue Exception
391
- puts $!
392
- puts $@
468
+ ::Blix::Rest.logger << $!
469
+ ::Blix::Rest.logger << $@
393
470
  '*** TEMPLATE ERROR ***'
394
471
  end
395
472
  end
@@ -401,15 +478,15 @@ module Blix::Rest
401
478
  path = opts[:erb_dir] || __erb_path || Controller.erb_root
402
479
 
403
480
  layout = layout_name && if no_template_cache
404
- ERB.new(File.read(File.join(path, layout_name + '.html.erb')),nil,'-')
481
+ ERB.new(File.read(File.join(path, layout_name + '.html.erb')),:trim_mode=>'-')
405
482
  else
406
- erb_templates[layout_name] ||= ERB.new(File.read(File.join(path, layout_name + '.html.erb')),nil,'-')
483
+ erb_templates[layout_name] ||= ERB.new(File.read(File.join(path, layout_name + '.html.erb')),:trim_mode=>'-')
407
484
  end
408
485
 
409
486
  erb = if no_template_cache
410
- ERB.new(File.read(File.join(path, name + '.html.erb')),nil,'-')
487
+ ERB.new(File.read(File.join(path, name + '.html.erb')),:trim_mode=>'-')
411
488
  else
412
- erb_templates[name] ||= ERB.new(File.read(File.join(path, name + '.html.erb')),nil,'-')
489
+ erb_templates[name] ||= ERB.new(File.read(File.join(path, name + '.html.erb')),:trim_mode=>'-')
413
490
  end
414
491
 
415
492
  begin
@@ -421,8 +498,8 @@ module Blix::Rest
421
498
  erb.result(bind)
422
499
  end
423
500
  rescue Exception
424
- puts $!
425
- puts $@
501
+ ::Blix::Rest.logger << $!
502
+ ::Blix::Rest.logger << $@
426
503
  '*** TEMPLATE ERROR ***'
427
504
  end
428
505
  end
@@ -447,19 +524,32 @@ module Blix::Rest
447
524
  raise ServiceError, 'invalid format for this request' unless accept.index format
448
525
  end
449
526
 
527
+ def _do_route_hook(verb, path, opts)
528
+ if @_route_hook
529
+ superclass._do_route_hook(verb, path, opts) if superclass.respond_to? :_do_route_hook
530
+ @_route_hook.call(verb, path, opts)
531
+ end
532
+ end
533
+
450
534
  def route(verb, path, opts = {}, &blk)
451
- proc = lambda do |_path_params, _params, _req, _format, _response, server_options|
535
+ path = String.new(path) # in case frozen.
536
+ _do_route_hook(verb.dup, path, opts)
537
+ proc = lambda do |context|
452
538
  unless opts[:force] && (opts[:accept] == :*)
453
- check_format(opts[:accept], _format)
539
+ check_format(opts[:accept], context.format)
454
540
  end
455
- app = new(_path_params, _params, _req, _format, verb, _response, server_options)
541
+ app = new
542
+ app._setup(context, verb, path, opts)
456
543
  begin
457
544
  app.before(opts)
458
- response = app.instance_eval( &blk )
545
+ app.__before
546
+ context.response = app.instance_eval( &blk )
459
547
  rescue
460
548
  raise
461
549
  ensure
462
- app.after(opts, response)
550
+ app.__after
551
+ app.after(opts, context.response)
552
+ context.response
463
553
  end
464
554
  end
465
555
 
@@ -498,6 +588,49 @@ module Blix::Rest
498
588
  route 'OPTIONS', *a, &b
499
589
  end
500
590
 
591
+ def before_route(&b)
592
+ @_route_hook = b if b
593
+ end
594
+
595
+
596
+ def _before_hooks
597
+ @_before_hooks ||= {}
598
+ end
599
+
600
+ def _after_hooks
601
+ @_after_hooks ||= {}
602
+ end
603
+
604
+ def _do_before(ctx, *a)
605
+ superclass._do_before(ctx, *a) if superclass.respond_to? :_do_before
606
+ _before_hooks.each_value{ |h| ctx.instance_eval(&h) }
607
+ end
608
+
609
+ def _do_after(ctx, *a)
610
+ _after_hooks.each_value{ |h| ctx.instance_eval(&h) }
611
+ superclass._do_after(ctx, *a) if superclass.respond_to? :_do_after
612
+ end
613
+
614
+ # define a before hook for a controller. only one hook can be defined per
615
+ # controller in a single source file.
616
+ def before(&block)
617
+ if block
618
+ file = block.source_location[0]
619
+ warn("warning: before hook already defined in #{file}") if _before_hooks[file]
620
+ _before_hooks[file] = block
621
+ end
622
+ end
623
+
624
+ # define an after hook for a controller. only one hook can be defined per
625
+ # controller in a single source file.
626
+ def after(&block)
627
+ if block
628
+ file = block.source_location[0]
629
+ warn("warning: after hook already defined in #{file}") if _after_hooks[file]
630
+ _after_hooks[file] = block
631
+ end
632
+ end
633
+
501
634
  end
502
635
 
503
636
  end