blix-rest 0.1.30 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+ require 'thread'
2
3
 
3
4
  module Blix::Rest
4
5
  class RequestMapperError < StandardError; end
@@ -7,6 +8,8 @@ module Blix::Rest
7
8
  # these routes and return an associated block and parameters.
8
9
  class RequestMapper
9
10
 
11
+ @@mutex = Mutex.new
12
+
10
13
  WILD_PLACEHOLDER = '/'
11
14
  PATH_SEP = '/'
12
15
  STAR_PLACEHOLDER = '*'
@@ -52,6 +55,7 @@ module Blix::Rest
52
55
 
53
56
  class << self
54
57
 
58
+ # the root always starts with '/' and finishes with '/'
55
59
  def set_path_root(root)
56
60
  root = root.to_s
57
61
  root = '/' + root if root[0, 1] != '/'
@@ -64,19 +68,30 @@ module Blix::Rest
64
68
  @path_root || '/'
65
69
  end
66
70
 
67
- attr_reader :path_root_length
71
+ def path_root_length
72
+ @path_root_length.to_i
73
+ end
68
74
 
69
75
  def full_path(path)
70
76
  path = path[1..-1] if path[0, 1] == '/'
71
77
  path_root + path
72
78
  end
73
79
 
80
+ # ensure that absolute path is the full path
81
+ def ensure_full_path(path)
82
+ if path[0, 1] == '/' && (path_root_length>0) && path[0, path_root_length] != path_root[0, path_root_length]
83
+ path = path_root + path[1..-1]
84
+ end
85
+ path
86
+ end
87
+
74
88
  def locations
75
89
  @locations ||= Hash.new { |h, k| h[k] = [] }
76
90
  end
77
91
 
78
92
  def table
79
- @table ||= compile
93
+ # compile the table in one thread only.
94
+ @table ||= @@mutex.synchronize{@table ||= compile}
80
95
  end
81
96
 
82
97
  def dump
@@ -147,6 +162,7 @@ module Blix::Rest
147
162
  path = path[1..-1] if path[0, 1] == PATH_SEP
148
163
  RequestMapper.locations[verb] << [verb, path, opts, blk]
149
164
  @table = nil # force recompile
165
+ true
150
166
  end
151
167
 
152
168
  # match a given path to declared route.
@@ -212,12 +228,12 @@ module Blix::Rest
212
228
  # .. if there is a wildpath foloowing then fine ..
213
229
  # .. otherwise an error !
214
230
 
215
- if idx == limit # the last section of request
231
+ if idx == limit # the last section of path
216
232
  if current.blk
217
233
  return [current.blk, parameters, current.opts]
218
234
  elsif (havewild = current[STAR_PLACEHOLDER])
219
235
  parameters[havewild.parameter.to_s] = '/'
220
- return [havewild.blk, parameters, havewild.opts]
236
+ return [havewild.blk, parameters, havewild.opts, true]
221
237
  else
222
238
  return [nil, {}, nil]
223
239
  end
@@ -250,7 +266,7 @@ module Blix::Rest
250
266
  return [current.blk, parameters, current.opts]
251
267
  elsif (havewild = current[STAR_PLACEHOLDER])
252
268
  parameters[havewild.parameter.to_s] = '/'
253
- return [havewild.blk, parameters, havewild.opts]
269
+ return [havewild.blk, parameters, havewild.opts, true]
254
270
  else
255
271
  return [nil, {}, nil]
256
272
  end
@@ -268,7 +284,7 @@ module Blix::Rest
268
284
  parameters['format'] = wildformat[1..-1].to_sym
269
285
  end
270
286
  parameters[current.parameter.to_s] = wildpath
271
- return [current.blk, parameters, current.opts]
287
+ return [current.blk, parameters, current.opts, true]
272
288
  else
273
289
  return [nil, {}, nil]
274
290
  end
@@ -12,13 +12,13 @@ module Blix::Rest
12
12
 
13
13
  def initialize
14
14
  @status = 200
15
- @headers = {}
15
+ @headers = Rack::Headers.new
16
16
  @content = nil
17
17
  end
18
18
 
19
19
  def set(status,content=nil,headers=nil)
20
20
  @status = status if status
21
- @content = String.new(content) if content
21
+ @content = [String.new(content)] if content
22
22
  @headers.merge!(headers) if headers
23
23
  end
24
24
 
@@ -0,0 +1,33 @@
1
+ module Blix::Rest
2
+
3
+ # class used to represent a route and to allow manipulation of the
4
+ # options and path of the route before registrATION
5
+
6
+ class Route
7
+
8
+ attr_reader :verb, :path, :opts
9
+ alias_method :method, :verb
10
+ alias_method :options, :opts
11
+
12
+ def initialize(verb, path, opts)
13
+ @verb = verb.dup
14
+ @path = path
15
+ @opts = opts
16
+ end
17
+
18
+ def default_option(key, val)
19
+ opts[key.to_sym] = val unless opts.key?(key) || opts.key?(key.to_sym)
20
+ end
21
+
22
+ def path_prefix(prefix)
23
+ prefix = prefix.to_s
24
+ prefix = '/' + prefix unless prefix[0] == '/'
25
+ path.replace( prefix + path) unless path.start_with?(prefix)
26
+ end
27
+
28
+ def path=(val)
29
+ path.replace(val.to_s)
30
+ end
31
+
32
+ end
33
+ end
@@ -17,8 +17,23 @@ module Blix::Rest
17
17
  @_options = opts
18
18
  end
19
19
 
20
+ # options passed to the server
21
+ def _options
22
+ @_options
23
+ end
24
+
25
+
26
+ # the object serving as a cache
20
27
  def _cache
21
- @_cache ||= {}
28
+ @_cache ||= begin
29
+ obj = _options[:cache] || _options['cache']
30
+ if obj
31
+ raise "cache must be a subclass of Blix::Rest::Cache" unless obj.is_a?(Cache)
32
+ obj
33
+ else
34
+ MemoryCache.new
35
+ end
36
+ end
22
37
  end
23
38
 
24
39
  def extract_parsers_from_options(opts)
@@ -34,7 +49,6 @@ module Blix::Rest
34
49
  def set_custom_headers(format, headers)
35
50
  parser = get_parser(format)
36
51
  raise "parser not found for custom headers format=>#{format}" unless parser
37
-
38
52
  parser.__custom_headers = headers
39
53
  end
40
54
 
@@ -54,6 +68,7 @@ module Blix::Rest
54
68
  parser._types.each { |t| @_mime_types[t.downcase] = parser } # register each of the mime types
55
69
  end
56
70
 
71
+ # retrieve parameters from the http request
57
72
  def retrieve_params(env)
58
73
  post_params = {}
59
74
  body = ''
@@ -104,6 +119,7 @@ module Blix::Rest
104
119
  end
105
120
  end
106
121
 
122
+ # determine standard format from http mime type
107
123
  def get_format_from_mime(mime)
108
124
  case mime
109
125
  when 'application/json' then :json
@@ -138,20 +154,24 @@ module Blix::Rest
138
154
  parser
139
155
  end
140
156
 
157
+ # the call function is the interface with the rack framework
141
158
  def call(env)
142
159
  req = Rack::Request.new(env)
143
160
 
144
- verb = env['REQUEST_METHOD']
145
- path = req.path
161
+ verb = env['REQUEST_METHOD']
162
+ path = req.path
163
+ path = CGI.unescape(path).gsub('+',' ') unless _options[:unescape] == false
146
164
 
147
- blk, path_params, options = RequestMapper.match(verb, path)
165
+ blk, path_params, options, is_wild = RequestMapper.match(verb, path)
166
+
167
+ match_all = RequestMapper.match('ALL', path) unless blk && !is_wild
168
+ blk, path_params, options = match_all if match_all && match_all[0] # override
148
169
 
149
- blk, path_params, options = RequestMapper.match('ALL', path) unless blk
150
170
 
151
171
  default_format = options && options[:default] && options[:default].to_sym
152
172
  force_format = options && options[:force] && options[:force].to_sym
153
- do_cache = options && options[:cache]
154
- clear_cache = options && options[:cache_reset]
173
+ do_cache = options && options[:cache] && !Blix::Rest.cache_disabled
174
+ clear_cache = options && options[:cache_reset]
155
175
 
156
176
  query_format = options && options[:query] && req.GET['format'] && req.GET['format'].to_sym
157
177
 
@@ -159,39 +179,49 @@ module Blix::Rest
159
179
 
160
180
  parser = get_parser(force_format || format)
161
181
 
162
- return [406, {}, ["Invalid Format: #{format}"]] unless parser
182
+ unless parser
183
+ if blk
184
+ return [406, {}, ["Invalid Format: #{format}"]]
185
+ else
186
+ return [404, {}, ["Invalid Url"]]
187
+ end
188
+ end
163
189
 
164
190
  parser._options = options
165
191
 
166
192
  # check for cached response end return with cached response if found.
167
193
  #
168
- if do_cache && _cache["#{verb}|#{format}|#{path}"]
169
- response = _cache["#{verb}|#{format}|#{path}"]
170
- return [response.status, response.headers.merge('X-Blix-Cache' => 'cached'), [response.content]]
194
+ if do_cache && ( response = _cache["#{verb}|#{format}|#{path}"] )
195
+ return [response.status, response.headers.merge('x-blix-cache' => 'cached'), response.content]
171
196
  end
172
197
 
173
198
  response = Response.new
174
199
 
175
- if parser.__custom_headers
176
- response.headers.merge! parser.__custom_headers
177
- else
178
- parser.set_default_headers(response.headers)
179
- end
180
-
181
200
  if blk
182
201
 
183
202
  begin
184
203
  params = env['params']
185
- value = blk.call(path_params, params, req, format, response, @_options)
204
+ context = Context.new(path_params, params, req, format, response, verb, self)
205
+ value = blk.call(context)
186
206
  rescue ServiceError => e
207
+ set_default_headers(parser,response)
187
208
  response.set(e.status, parser.format_error(e.message), e.headers)
209
+ rescue RawResponse => e
210
+ value = e.content
211
+ value = [value.to_s] unless value.respond_to?(:each) || value.respond_to?(:call)
212
+ response.status = e.status if e.status
213
+ response.content = value
214
+ response.headers.merge!(e.headers) if e.headers
188
215
  rescue AuthorizationError => e
216
+ set_default_headers(parser,response)
189
217
  response.set(401, parser.format_error(e.message), AUTH_HEADER => "#{e.type} realm=\"#{e.realm}\", charset=\"UTF-8\"")
190
218
  rescue Exception => e
219
+ set_default_headers(parser,response)
191
220
  response.set(500, parser.format_error('internal error'))
192
221
  ::Blix::Rest.logger << "----------------------------\n#{$!}\n----------------------------"
193
222
  ::Blix::Rest.logger << "----------------------------\n#{$@}\n----------------------------"
194
223
  else # no error
224
+ set_default_headers(parser,response)
195
225
  parser.format_response(value, response)
196
226
  # cache response if requested
197
227
  _cache.clear if clear_cache
@@ -199,9 +229,19 @@ module Blix::Rest
199
229
  end
200
230
 
201
231
  else
232
+ set_default_headers(parser,response)
202
233
  response.set(404, parser.format_error('Invalid Url'))
203
234
  end
204
- [response.status, response.headers, [response.content]]
235
+
236
+ [response.status.to_i, response.headers, response.content]
237
+ end
238
+
239
+ def set_default_headers(parser,response)
240
+ if parser.__custom_headers
241
+ response.headers.merge! parser.__custom_headers
242
+ else
243
+ parser.set_default_headers(response.headers)
244
+ end
205
245
  end
206
246
 
207
247
  end
@@ -0,0 +1,138 @@
1
+ module Blix::Rest
2
+
3
+ module Session
4
+ #----------------------------------------------------------------------------
5
+ #
6
+ # manage the session and authorization
7
+ #
8
+ #----------------------------------------------------------------------------
9
+
10
+ DAY = 24 * 60 * 60
11
+ MIN = 60
12
+ SESSION_NAME = 'blix'
13
+
14
+ SESSION_OPTS = {
15
+ #:secure=>true,
16
+ :http => false,
17
+ :samesite => :lax,
18
+ :path => Blix::Rest.full_path('/'),
19
+ :expire_secs => 30 * MIN, # 30 mins
20
+ :cleanup_every_secs => 5 * 60 # 5 minutes
21
+ #:max_age => nil # session cookie
22
+ }.freeze
23
+
24
+
25
+ def session_manager
26
+ self.class.get_session_manager
27
+ end
28
+
29
+ def session_name
30
+ self.class.get_session_name
31
+ end
32
+
33
+ def session_opts
34
+ self.class.get_session_opts
35
+ end
36
+
37
+ def session
38
+ @__session
39
+ end
40
+
41
+ def csrf_token
42
+ @__session['csrf'] ||= SecureRandom.hex(32)
43
+ end
44
+
45
+ def reset_session
46
+ raise 'login_session missing' unless @__session && @__session_id
47
+ session_manager.delete_session(@__session_id)
48
+ @__session_id = refresh_session_id(session_name, session_opts)
49
+ @__session['csrf'] = SecureRandom.hex(32)
50
+ session_manager.store_session(@__session_id, @__session)
51
+ end
52
+
53
+ # get a session id and use this to retrieve the session information - if any.
54
+ #
55
+ def session_before(opts)
56
+ @__session = {}
57
+
58
+ # do not set session on pages. that will be cached.
59
+ unless opts[:nosession] || opts[:cache]
60
+ @__session_id = get_session_id(session_name, session_opts)
61
+ @__session =
62
+ begin
63
+ session_manager.get_session(@__session_id)
64
+ rescue SessionExpiredError
65
+ @__session_id = refresh_session_id(session_name, session_opts)
66
+ session_manager.get_session(@__session_id)
67
+ end
68
+ end
69
+
70
+ if opts[:csrf]
71
+ if env["HTTP_X_CSRF_TOKEN"] != csrf_token
72
+ send_error("invalid csrf token")
73
+ end
74
+ end
75
+
76
+ end
77
+
78
+ # save the session hash before we go.
79
+ def session_after
80
+ session_manager.store_session(@__session_id, @__session) if @__session_id
81
+ end
82
+
83
+ private
84
+
85
+ def self.included(mod)
86
+ mod.extend Session::ClassMethods
87
+ end
88
+
89
+ module ClassMethods
90
+
91
+ def session_name(name)
92
+ @_sess_name = name.to_s
93
+ end
94
+
95
+ def session_opts(opts)
96
+ @_sess_custom_opts = opts
97
+ end
98
+
99
+ def session_manager(man)
100
+ raise ArgumentError, "incompatible session store" unless man.respond_to?(:get_session)
101
+ raise ArgumentError, "incompatible session store" unless man.respond_to?(:store_session)
102
+ raise ArgumentError, "incompatible session store" unless man.respond_to?(:delete_session)
103
+ @_sess_manager= man
104
+ end
105
+
106
+ def get_session_name
107
+ @_sess_name ||= begin
108
+ if superclass.respond_to?(:get_session_name)
109
+ superclass.get_session_name
110
+ else
111
+ SESSION_NAME
112
+ end
113
+ end
114
+ end
115
+
116
+ def get_session_opts
117
+ @_session_opts ||= begin
118
+ if superclass.respond_to?(:get_session_opts)
119
+ superclass.get_session_opts.merge(@_sess_custom_opts || {})
120
+ else
121
+ SESSION_OPTS.merge(@_sess_custom_opts || {})
122
+ end
123
+ end
124
+ end
125
+
126
+ def get_session_manager
127
+ @_sess_manager ||= begin
128
+ if superclass.respond_to?(:get_session_manager)
129
+ superclass.get_session_manager
130
+ else
131
+ Blix::RedisStore.new(get_session_opts)
132
+ end
133
+ end
134
+ end
135
+
136
+ end # ClassMethods
137
+ end # Session
138
+ end # Blix::Rest
@@ -1,5 +1,5 @@
1
1
  module Blix
2
2
  module Rest
3
- VERSION = "0.1.30"
3
+ VERSION = "0.8.2"
4
4
  end
5
5
  end
data/lib/blix/rest.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'base64'
2
2
  require 'logger'
3
3
  require 'time'
4
+ require 'cgi'
5
+ require 'rack'
4
6
 
5
7
  module Blix
6
8
  module Rest
@@ -8,14 +10,14 @@ module Blix
8
10
  # EXPIRED_TOKEN_MESSAGE = 'token expired'
9
11
  # INVALID_TOKEN_MESSAGE = 'invalid token'
10
12
 
11
- CONTENT_TYPE = 'Content-Type'.freeze
13
+ CONTENT_TYPE = 'content-type'.freeze
12
14
  CONTENT_TYPE_JSON = 'application/json'.freeze
13
15
  CONTENT_TYPE_HTML = 'text/html; charset=utf-8'.freeze
14
16
  CONTENT_TYPE_XML = 'application/xml'.freeze
15
- AUTH_HEADER = 'WWW-Authenticate'.freeze
16
- CACHE_CONTROL = 'Cache-Control'.freeze
17
+ AUTH_HEADER = 'www-authenticate'.freeze
18
+ CACHE_CONTROL = 'cache-control'.freeze
17
19
  CACHE_NO_STORE = 'no-store'.freeze
18
- PRAGMA = 'Pragma'.freeze
20
+ PRAGMA = 'pragma'.freeze
19
21
  NO_CACHE = 'no-cache'.freeze
20
22
  URL_ENCODED = %r{^application/x-www-form-urlencoded}.freeze
21
23
  JSON_ENCODED = %r{^application/json}.freeze # NOTE: "text/json" and "text/javascript" are deprecated forms
@@ -43,6 +45,14 @@ module Blix
43
45
  @_logger = val
44
46
  end
45
47
 
48
+ def self.cache_disabled
49
+ @_cache_disabled
50
+ end
51
+
52
+ def self.disable_cache
53
+ @_cache_disabled = true
54
+ end
55
+
46
56
  def self.logger
47
57
  @_logger ||= begin
48
58
  l = Logger.new(STDOUT)
@@ -67,13 +77,10 @@ module Blix
67
77
  end
68
78
 
69
79
  # interpret payload string as json
70
- class RawJsonString
71
- def initialize(str)
72
- @str = str
73
- end
80
+ class RawJsonString < String
74
81
 
75
82
  def as_json(*_a)
76
- @str
83
+ self
77
84
  end
78
85
 
79
86
  def to_json(*a)
@@ -94,6 +101,19 @@ module Blix
94
101
  end
95
102
  end
96
103
 
104
+ class RawResponse < StandardError
105
+ attr_reader :status
106
+ attr_reader :headers
107
+ attr_reader :content
108
+
109
+ def initialize(message, status = nil, headers = nil)
110
+ super(message || "")
111
+ @status = status
112
+ @headers = headers
113
+ @content = message
114
+ end
115
+ end
116
+
97
117
  class AuthorizationError < StandardError
98
118
  attr_reader :realm, :type
99
119
  def initialize(message=nil, realm=nil, type=nil)
@@ -128,8 +148,9 @@ require 'rack'
128
148
  require 'blix/rest/response'
129
149
  require 'blix/rest/format_parser'
130
150
  require 'blix/rest/request_mapper'
151
+ require 'blix/rest/cache'
131
152
  require 'blix/rest/server'
132
- # require 'blix/rest/provider'
153
+ require 'blix/rest/route'
133
154
  require 'blix/rest/controller'
134
155
  # require 'blix/rest/provider_controller'
135
156
 
@@ -36,7 +36,7 @@ module Blix
36
36
  def store_data(id, data)
37
37
  params = {}
38
38
  params[:ex] = _opts[:expire_secs] if _opts.key?(:expire_secs)
39
- redis.set(_key(id), data, params)
39
+ redis.set(_key(id), data, **params)
40
40
  data
41
41
  end
42
42
 
@@ -89,7 +89,7 @@ module Blix
89
89
  params[:ex] = _opts[:expire_secs] if _opts.key?(:expire_secs)
90
90
  hash ||= {}
91
91
  hash['_last_access'] = Time.now
92
- redis.set(_key(id), _encode(hash), params)
92
+ redis.set(_key(id), _encode(hash), **params)
93
93
  hash
94
94
  end
95
95
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blix-rest
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.30
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Clive Andrews
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-08-30 00:00:00.000000000 Z
11
+ date: 2023-09-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: httpclient
@@ -44,14 +44,14 @@ dependencies:
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: 3.0.0
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: 3.0.0
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: rspec
57
57
  requirement: !ruby/object:Gem::Requirement
@@ -66,6 +66,20 @@ dependencies:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
69
83
  description: |-
70
84
  Rack based framework focused on building JSON REST web services.
71
85
  Concentrates on making the basics very easy to write and fast to execute.
@@ -81,6 +95,7 @@ files:
81
95
  - LICENSE
82
96
  - README.md
83
97
  - lib/blix/rest.rb
98
+ - lib/blix/rest/cache.rb
84
99
  - lib/blix/rest/controller.rb
85
100
  - lib/blix/rest/cucumber.rb
86
101
  - lib/blix/rest/cucumber/hooks.rb
@@ -90,10 +105,13 @@ files:
90
105
  - lib/blix/rest/format.rb
91
106
  - lib/blix/rest/format_parser.rb
92
107
  - lib/blix/rest/handlebars_assets_fix.rb
108
+ - lib/blix/rest/redis_cache.rb
93
109
  - lib/blix/rest/request_mapper.rb
94
110
  - lib/blix/rest/resource_cache.rb
95
111
  - lib/blix/rest/response.rb
112
+ - lib/blix/rest/route.rb
96
113
  - lib/blix/rest/server.rb
114
+ - lib/blix/rest/session.rb
97
115
  - lib/blix/rest/string_hash.rb
98
116
  - lib/blix/rest/version.rb
99
117
  - lib/blix/utils.rb
@@ -119,7 +137,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
119
137
  - !ruby/object:Gem::Version
120
138
  version: '0'
121
139
  requirements: []
122
- rubygems_version: 3.1.4
140
+ rubygems_version: 3.0.9
123
141
  signing_key:
124
142
  specification_version: 4
125
143
  summary: Framework for HTTP REST services