blix-rest 0.1.30
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.
- checksums.yaml +7 -0
- data/LICENSE +25 -0
- data/README.md +457 -0
- data/lib/blix/rest.rb +145 -0
- data/lib/blix/rest/controller.rb +512 -0
- data/lib/blix/rest/cucumber.rb +8 -0
- data/lib/blix/rest/cucumber/hooks.rb +5 -0
- data/lib/blix/rest/cucumber/request_steps.rb +207 -0
- data/lib/blix/rest/cucumber/resource_steps.rb +28 -0
- data/lib/blix/rest/cucumber/world.rb +273 -0
- data/lib/blix/rest/format.rb +15 -0
- data/lib/blix/rest/format_parser.rb +167 -0
- data/lib/blix/rest/handlebars_assets_fix.rb +10 -0
- data/lib/blix/rest/request_mapper.rb +332 -0
- data/lib/blix/rest/resource_cache.rb +50 -0
- data/lib/blix/rest/response.rb +26 -0
- data/lib/blix/rest/server.rb +208 -0
- data/lib/blix/rest/string_hash.rb +100 -0
- data/lib/blix/rest/version.rb +5 -0
- data/lib/blix/utils.rb +2 -0
- data/lib/blix/utils/misc.rb +62 -0
- data/lib/blix/utils/redis_store.rb +173 -0
- data/lib/blix/utils/yaml_config.rb +74 -0
- metadata +126 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
module Blix::Rest
|
2
|
+
|
3
|
+
module ResourceCache
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def _cache_hash
|
8
|
+
@_cache ||= {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def _cache_get(*args,&block)
|
12
|
+
field = args[0].to_s
|
13
|
+
if block && args.length == 1
|
14
|
+
if _cache?(field)
|
15
|
+
_cache_hash[field]
|
16
|
+
else
|
17
|
+
_cache_hash[field]= block.call
|
18
|
+
end
|
19
|
+
elsif args.length == 1
|
20
|
+
_cache_hash[field]
|
21
|
+
elsif args.length == 2
|
22
|
+
if _cache?(field)
|
23
|
+
_cache_hash[field]
|
24
|
+
else
|
25
|
+
_cache_hash[field]= args[1]
|
26
|
+
end
|
27
|
+
else
|
28
|
+
raise "wrong number of arguments:#{args.length} for 1 or 2"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def _cache_set(field,val)
|
33
|
+
_cache_hash[field.to_s] = val
|
34
|
+
end
|
35
|
+
|
36
|
+
def _cache_reset(field)
|
37
|
+
_cache_hash.delete field.to_s
|
38
|
+
end
|
39
|
+
|
40
|
+
def _cache(field)
|
41
|
+
_cache_hash[field.to_s]
|
42
|
+
end
|
43
|
+
|
44
|
+
def _cache?(field)
|
45
|
+
_cache_hash.key?(field.to_s)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# pass a response object to the controller to set
|
2
|
+
# header status and content.
|
3
|
+
|
4
|
+
module Blix::Rest
|
5
|
+
|
6
|
+
|
7
|
+
class Response
|
8
|
+
|
9
|
+
attr_accessor :status
|
10
|
+
attr_reader :headers
|
11
|
+
attr_accessor :content
|
12
|
+
|
13
|
+
def initialize
|
14
|
+
@status = 200
|
15
|
+
@headers = {}
|
16
|
+
@content = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def set(status,content=nil,headers=nil)
|
20
|
+
@status = status if status
|
21
|
+
@content = String.new(content) if content
|
22
|
+
@headers.merge!(headers) if headers
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Blix::Rest
|
4
|
+
class Server
|
5
|
+
|
6
|
+
def initialize(opts = {})
|
7
|
+
@_parsers = {}
|
8
|
+
@_mime_types = {}
|
9
|
+
|
10
|
+
# register the default parsers and any passed in as options.
|
11
|
+
|
12
|
+
register_parser('html', HtmlFormatParser.new)
|
13
|
+
register_parser('json', JsonFormatParser.new)
|
14
|
+
register_parser('xml', XmlFormatParser.new)
|
15
|
+
register_parser('raw', RawFormatParser.new)
|
16
|
+
extract_parsers_from_options(opts)
|
17
|
+
@_options = opts
|
18
|
+
end
|
19
|
+
|
20
|
+
def _cache
|
21
|
+
@_cache ||= {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def extract_parsers_from_options(opts)
|
25
|
+
opts.each do |k, v|
|
26
|
+
next unless k =~ /^(\w*)_parser&/
|
27
|
+
|
28
|
+
format = Regexp.last_match(1)
|
29
|
+
parser = v
|
30
|
+
register_parser(format, parser)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def set_custom_headers(format, headers)
|
35
|
+
parser = get_parser(format)
|
36
|
+
raise "parser not found for custom headers format=>#{format}" unless parser
|
37
|
+
|
38
|
+
parser.__custom_headers = headers
|
39
|
+
end
|
40
|
+
|
41
|
+
def get_parser(format)
|
42
|
+
@_parsers[format.to_s]
|
43
|
+
end
|
44
|
+
|
45
|
+
def get_parser_from_type(type)
|
46
|
+
@_mime_types[type.downcase]
|
47
|
+
end
|
48
|
+
|
49
|
+
def register_parser(format, parser)
|
50
|
+
raise "#{k} must be an object with parent class Blix::Rest::FormatParser" unless parser.is_a?(FormatParser)
|
51
|
+
|
52
|
+
parser._format = format
|
53
|
+
@_parsers[format.to_s.downcase] = parser
|
54
|
+
parser._types.each { |t| @_mime_types[t.downcase] = parser } # register each of the mime types
|
55
|
+
end
|
56
|
+
|
57
|
+
def retrieve_params(env)
|
58
|
+
post_params = {}
|
59
|
+
body = ''
|
60
|
+
params = env['params'] || {}
|
61
|
+
params.merge!(::Rack::Utils.parse_nested_query(env['QUERY_STRING']))
|
62
|
+
|
63
|
+
if env['rack.input']
|
64
|
+
post_params = ::Rack::Utils::Multipart.parse_multipart(env)
|
65
|
+
unless post_params
|
66
|
+
body = env['rack.input'].read
|
67
|
+
env['rack.input'].rewind
|
68
|
+
|
69
|
+
if body.empty?
|
70
|
+
post_params = {}
|
71
|
+
else
|
72
|
+
begin
|
73
|
+
post_params = case (env['CONTENT_TYPE'])
|
74
|
+
when URL_ENCODED
|
75
|
+
::Rack::Utils.parse_nested_query(body)
|
76
|
+
when JSON_ENCODED then
|
77
|
+
json = MultiJson.load(body)
|
78
|
+
if json.is_a?(Hash)
|
79
|
+
json
|
80
|
+
else
|
81
|
+
{ '_json' => json }
|
82
|
+
end
|
83
|
+
else
|
84
|
+
{}
|
85
|
+
end
|
86
|
+
rescue StandardError => e
|
87
|
+
raise BadRequestError, "Invalid parameters: #{e.class}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
[params, post_params, body]
|
93
|
+
end
|
94
|
+
|
95
|
+
# accept header can have multiple entries. match on regexp
|
96
|
+
# can look like this
|
97
|
+
# text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 !!!!!
|
98
|
+
|
99
|
+
def get_format(env)
|
100
|
+
case env['HTTP_ACCEPT']
|
101
|
+
when JSON_ENCODED then :json
|
102
|
+
when HTML_ENCODED then :html
|
103
|
+
when XML_ENCODED then :xml
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def get_format_from_mime(mime)
|
108
|
+
case mime
|
109
|
+
when 'application/json' then :json
|
110
|
+
when 'text/html' then :html
|
111
|
+
when 'application/xml' then :xml
|
112
|
+
when 'application/xhtml+xml' then :xhtml
|
113
|
+
when '*/*' then :*
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
# attempt to handle mjltiple accept formats here..
|
118
|
+
# mime can include '.../*' and '*/*'
|
119
|
+
# FIXME
|
120
|
+
def get_format_new(env, options)
|
121
|
+
accept = options && options[:accept] || :json
|
122
|
+
accept = [accept].flatten
|
123
|
+
|
124
|
+
requested = env['HTTP_ACCEPT'].to_s.split(',')
|
125
|
+
requested.each do |request|
|
126
|
+
parts = request.split(';') # the quality part is after a ;
|
127
|
+
mime = parts[0].strip # the mime type
|
128
|
+
try = get_format_from_mime(mime)
|
129
|
+
next unless try
|
130
|
+
return accept[0] || :json if try == :*
|
131
|
+
return try if accept.include?(try)
|
132
|
+
end
|
133
|
+
nil # no match found
|
134
|
+
end
|
135
|
+
|
136
|
+
# convert the response to the appropriate format
|
137
|
+
def format_error(_message, _format)
|
138
|
+
parser
|
139
|
+
end
|
140
|
+
|
141
|
+
def call(env)
|
142
|
+
req = Rack::Request.new(env)
|
143
|
+
|
144
|
+
verb = env['REQUEST_METHOD']
|
145
|
+
path = req.path
|
146
|
+
|
147
|
+
blk, path_params, options = RequestMapper.match(verb, path)
|
148
|
+
|
149
|
+
blk, path_params, options = RequestMapper.match('ALL', path) unless blk
|
150
|
+
|
151
|
+
default_format = options && options[:default] && options[:default].to_sym
|
152
|
+
force_format = options && options[:force] && options[:force].to_sym
|
153
|
+
do_cache = options && options[:cache]
|
154
|
+
clear_cache = options && options[:cache_reset]
|
155
|
+
|
156
|
+
query_format = options && options[:query] && req.GET['format'] && req.GET['format'].to_sym
|
157
|
+
|
158
|
+
format = query_format || path_params[:format] || get_format_new(env, options) || default_format || :json
|
159
|
+
|
160
|
+
parser = get_parser(force_format || format)
|
161
|
+
|
162
|
+
return [406, {}, ["Invalid Format: #{format}"]] unless parser
|
163
|
+
|
164
|
+
parser._options = options
|
165
|
+
|
166
|
+
# check for cached response end return with cached response if found.
|
167
|
+
#
|
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]]
|
171
|
+
end
|
172
|
+
|
173
|
+
response = Response.new
|
174
|
+
|
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
|
+
if blk
|
182
|
+
|
183
|
+
begin
|
184
|
+
params = env['params']
|
185
|
+
value = blk.call(path_params, params, req, format, response, @_options)
|
186
|
+
rescue ServiceError => e
|
187
|
+
response.set(e.status, parser.format_error(e.message), e.headers)
|
188
|
+
rescue AuthorizationError => e
|
189
|
+
response.set(401, parser.format_error(e.message), AUTH_HEADER => "#{e.type} realm=\"#{e.realm}\", charset=\"UTF-8\"")
|
190
|
+
rescue Exception => e
|
191
|
+
response.set(500, parser.format_error('internal error'))
|
192
|
+
::Blix::Rest.logger << "----------------------------\n#{$!}\n----------------------------"
|
193
|
+
::Blix::Rest.logger << "----------------------------\n#{$@}\n----------------------------"
|
194
|
+
else # no error
|
195
|
+
parser.format_response(value, response)
|
196
|
+
# cache response if requested
|
197
|
+
_cache.clear if clear_cache
|
198
|
+
_cache["#{verb}|#{format}|#{path}"] = response if do_cache
|
199
|
+
end
|
200
|
+
|
201
|
+
else
|
202
|
+
response.set(404, parser.format_error('Invalid Url'))
|
203
|
+
end
|
204
|
+
[response.status, response.headers, [response.content]]
|
205
|
+
end
|
206
|
+
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
unless Hash.method_defined? :transform_keys
|
4
|
+
class Hash
|
5
|
+
# Returns a new hash with all keys converted using the block operation.
|
6
|
+
#
|
7
|
+
# hash = { name: 'Rob', age: '28' }
|
8
|
+
#
|
9
|
+
# hash.transform_keys{ |key| key.to_s.upcase }
|
10
|
+
# # => {"NAME"=>"Rob", "AGE"=>"28"}
|
11
|
+
def transform_keys
|
12
|
+
result = {}
|
13
|
+
each_key do |key|
|
14
|
+
result[yield(key)] = self[key]
|
15
|
+
end
|
16
|
+
result
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Blix::Rest
|
22
|
+
# indifferent hash for symbols or string keys.
|
23
|
+
# stores keys as a string
|
24
|
+
|
25
|
+
class StringHash < Hash
|
26
|
+
|
27
|
+
alias_method :parent_merge!, :merge!
|
28
|
+
|
29
|
+
# initialize without conversion. params must be in
|
30
|
+
# string key format.
|
31
|
+
def initialize(*params)
|
32
|
+
super()
|
33
|
+
parent_merge!(*params) unless params.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
# create with conversion
|
37
|
+
def self.create(params)
|
38
|
+
h = new
|
39
|
+
h.merge(params)
|
40
|
+
h
|
41
|
+
end
|
42
|
+
|
43
|
+
def [](k)
|
44
|
+
super(k.to_s)
|
45
|
+
end
|
46
|
+
|
47
|
+
def get(k, default = nil)
|
48
|
+
if key?(k.to_s)
|
49
|
+
self[k]
|
50
|
+
else
|
51
|
+
default
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def merge(*params)
|
56
|
+
super(* params.map { |h| h.transform_keys(&:to_s) })
|
57
|
+
end
|
58
|
+
|
59
|
+
def merge!(*params)
|
60
|
+
super(* params.map { |h| h.transform_keys(&:to_s) })
|
61
|
+
end
|
62
|
+
|
63
|
+
def replace(h)
|
64
|
+
super(h.transform_keys(&:to_s))
|
65
|
+
end
|
66
|
+
|
67
|
+
def has_key?(key)
|
68
|
+
super(key.to_s)
|
69
|
+
end
|
70
|
+
|
71
|
+
def member?(key)
|
72
|
+
super(key.to_s)
|
73
|
+
end
|
74
|
+
|
75
|
+
def store(key, value)
|
76
|
+
super(key.to_s, value)
|
77
|
+
end
|
78
|
+
|
79
|
+
def key(key)
|
80
|
+
super(key.to_s)
|
81
|
+
end
|
82
|
+
|
83
|
+
def key?(key)
|
84
|
+
super(key.to_s)
|
85
|
+
end
|
86
|
+
|
87
|
+
def []=(k, v)
|
88
|
+
super(k.to_s, v)
|
89
|
+
end
|
90
|
+
|
91
|
+
def include?(k)
|
92
|
+
super(k.to_s)
|
93
|
+
end
|
94
|
+
|
95
|
+
def delete(k)
|
96
|
+
super(k.to_s)
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
data/lib/blix/utils.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
|
2
|
+
|
3
|
+
module Blix
|
4
|
+
|
5
|
+
def self.require_dir(path)
|
6
|
+
raise "invalid dir path:#{path}" unless File.directory?(path)
|
7
|
+
Dir.glob("#{path}/*.rb").each {|file| require File.expand_path(file)[0..-4] }
|
8
|
+
end
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
# filter the hash using the supplied filter
|
13
|
+
#
|
14
|
+
# the filter is an array of keys that are permitted
|
15
|
+
# returns a hash containing only the permitted keys and values
|
16
|
+
def self.filter_hash(filter,hash)
|
17
|
+
hash = hash || {}
|
18
|
+
hash.select {|key, value| filter.include?(key.to_sym) || filter.include?(key.to_s)}
|
19
|
+
end
|
20
|
+
|
21
|
+
# used to raise errors on
|
22
|
+
module DatamapperExceptions
|
23
|
+
def save(*args)
|
24
|
+
raise ServiceError, errors.full_messages.join(',') unless super
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def update(*args)
|
29
|
+
raise ServiceError, errors.full_messages.join(',') unless super
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def destroy(*args)
|
34
|
+
raise ServiceError, errors.full_messages.join(',') unless super
|
35
|
+
true
|
36
|
+
end
|
37
|
+
|
38
|
+
module ClassMethods
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.included(mod)
|
43
|
+
mod.extend DatamapperExceptions::ClassMethods
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
class String
|
51
|
+
|
52
|
+
# try to convert utf special characters to normal characters.
|
53
|
+
def to_ascii
|
54
|
+
unicode_normalize(:nfd).gsub(/[\u0300-\u036f]/, "")
|
55
|
+
end
|
56
|
+
|
57
|
+
# standardize utf strings
|
58
|
+
def normalize
|
59
|
+
unicode_normalize(:nfc)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|