webservice 0.6.1 → 0.7.0
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 +4 -4
- data/Manifest.txt +7 -5
- data/README.md +22 -2
- data/lib/webservice.rb +3 -2
- data/lib/webservice/base/base.rb +117 -0
- data/lib/webservice/{response_handler.rb → base/response_handler.rb} +0 -0
- data/lib/webservice/{base.rb → metal.rb} +297 -381
- data/lib/webservice/version.rb +2 -2
- data/test/helper.rb +0 -6
- data/test/service/debug.rb +14 -0
- data/test/test_metal.rb +43 -0
- data/test/test_mustermann.rb +5 -1
- data/test/{test_app.rb → test_service_app.rb} +4 -4
- data/test/test_service_debug.rb +32 -0
- metadata +9 -7
- data/test/test_samples.rb +0 -34
- data/test/test_samples_debug.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8ad7a2790f5e6914233060b188e91202c2d02bca
|
4
|
+
data.tar.gz: e790a006ecbaa61295205a44936b7bf7802849b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 03ea246f0daf6d8602774d681886afb70f3a4ec7e2626747c4d6f5311a0629146a1407335896b10d18bc83c39a05f9a60514bf4cdf8aa47300bc5feffbaf4997
|
7
|
+
data.tar.gz: 5c5ef7e78019811e996f65b9daf1cbb5462f9016fd341a58976fa794777c3b520a00d898766c3b80961f1f66ec3f39819d018b8cb99f772ce76ae2859d772d04
|
data/Manifest.txt
CHANGED
@@ -4,12 +4,14 @@ README.md
|
|
4
4
|
Rakefile
|
5
5
|
assets/webservice-32x32.png
|
6
6
|
lib/webservice.rb
|
7
|
-
lib/webservice/base.rb
|
8
|
-
lib/webservice/response_handler.rb
|
7
|
+
lib/webservice/base/base.rb
|
8
|
+
lib/webservice/base/response_handler.rb
|
9
|
+
lib/webservice/metal.rb
|
9
10
|
lib/webservice/version.rb
|
10
11
|
test/helper.rb
|
11
12
|
test/service/app.rb
|
12
|
-
test/
|
13
|
+
test/service/debug.rb
|
14
|
+
test/test_metal.rb
|
13
15
|
test/test_mustermann.rb
|
14
|
-
test/
|
15
|
-
test/
|
16
|
+
test/test_service_app.rb
|
17
|
+
test/test_service_debug.rb
|
data/README.md
CHANGED
@@ -134,7 +134,7 @@ get '/beer/(r|rnd|rand|random)' do # special keys for random beer
|
|
134
134
|
end
|
135
135
|
|
136
136
|
get '/beer/:key'
|
137
|
-
Beer.find_by! key: params[
|
137
|
+
Beer.find_by! key: params['key']
|
138
138
|
end
|
139
139
|
|
140
140
|
get '/brewery/(r|rnd|rand|random)' do # special keys for random brewery
|
@@ -142,13 +142,33 @@ get '/brewery/(r|rnd|rand|random)' do # special keys for random brewery
|
|
142
142
|
end
|
143
143
|
|
144
144
|
get '/brewery/:key'
|
145
|
-
Brewery.find_by! key: params[
|
145
|
+
Brewery.find_by! key: params['key']
|
146
146
|
end
|
147
|
+
|
148
|
+
...
|
147
149
|
```
|
148
150
|
|
151
|
+
|
149
152
|
[**`worlddb / world.db.service`**](https://github.com/worlddb/world.db.service) -
|
150
153
|
world.db HTTP JSON API (web service) scripts
|
151
154
|
|
155
|
+
```ruby
|
156
|
+
get '/countries(.:format)?' do
|
157
|
+
Country.by_key.all # sort/order by key
|
158
|
+
end
|
159
|
+
|
160
|
+
get '/cities(.:format)?' do
|
161
|
+
City.by_key.all # sort/order by key
|
162
|
+
end
|
163
|
+
|
164
|
+
get '/tag/:slug(.:format)?' do # e.g. /tag/north_america.csv
|
165
|
+
Tag.find_by!( slug: params['slug'] ).countries
|
166
|
+
end
|
167
|
+
|
168
|
+
...
|
169
|
+
```
|
170
|
+
|
171
|
+
|
152
172
|
[**`sportdb / sport.db.service`**](https://github.com/sportdb/sport.db.service) -
|
153
173
|
sport.db (football.db) HTTP JSON API (web service) scripts
|
154
174
|
|
data/lib/webservice.rb
CHANGED
@@ -22,9 +22,10 @@ require 'rack'
|
|
22
22
|
|
23
23
|
# our own code
|
24
24
|
require 'webservice/version' # note: let version always go first
|
25
|
-
require 'webservice/
|
25
|
+
require 'webservice/metal'
|
26
26
|
|
27
|
-
require 'webservice/
|
27
|
+
require 'webservice/base/base'
|
28
|
+
require 'webservice/base/response_handler' ## default (built-in) response handler (magic)
|
28
29
|
|
29
30
|
|
30
31
|
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
module Webservice
|
5
|
+
|
6
|
+
class Base < Metal
|
7
|
+
|
8
|
+
|
9
|
+
## note: before (filter) for now is just a method (NOT a chain for blocks, etc.);
|
10
|
+
## override method to change before (filter)
|
11
|
+
def before
|
12
|
+
### move cors headers to responseHandler to initialize!!!! - why? why not??
|
13
|
+
## (auto-)add (merge in) cors headers
|
14
|
+
## todo: move into a before filter ?? lets you overwrite headers - needed - why? why not??
|
15
|
+
headers 'Access-Control-Allow-Origin' => '*',
|
16
|
+
'Access-Control-Allow-Headers' => 'Authorization,Accepts,Content-Type,X-CSRF-Token,X-Requested-With',
|
17
|
+
'Access-Control-Allow-Methods' => 'GET,POST,PUT,DELETE,OPTIONS'
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
# note: for now use "plugable" response handler
|
22
|
+
## rename to respond_with or something? why? why not??
|
23
|
+
## make it a "stateless" function e.g. just retrun tripled [status, headers, body] - why? why not??
|
24
|
+
def handle_response( obj, opts={} )
|
25
|
+
handler = ResponseHandler.new( self ) ## for now "hard-coded"; make it a setting later - why? why not?
|
26
|
+
handler.handle_response( obj ) ## prepare response
|
27
|
+
end
|
28
|
+
|
29
|
+
|
30
|
+
##################################
|
31
|
+
## add some fallback (builtin) routes
|
32
|
+
|
33
|
+
get '/favicon.ico' do
|
34
|
+
## use 302 to redirect
|
35
|
+
## note: use strg+F5 to refresh page (clear cache for favicon.ico)
|
36
|
+
redirect '/webservice-32x32.png'
|
37
|
+
end
|
38
|
+
|
39
|
+
get '/webservice-32x32.png' do
|
40
|
+
send_file "#{Webservice.root}/assets/webservice-32x32.png"
|
41
|
+
end
|
42
|
+
|
43
|
+
get '/routes' do
|
44
|
+
msg =<<TXT
|
45
|
+
#{dump_routes}
|
46
|
+
|
47
|
+
#{dump_version}
|
48
|
+
TXT
|
49
|
+
end
|
50
|
+
|
51
|
+
## catch all (404 not found)
|
52
|
+
get '/*' do
|
53
|
+
pp env
|
54
|
+
pp self.class.routes ## note: dump routes of derived class
|
55
|
+
|
56
|
+
msg =<<TXT
|
57
|
+
404 Not Found
|
58
|
+
|
59
|
+
No route matched >#{request.request_method} #{request.path_info}<:
|
60
|
+
|
61
|
+
REQUEST_METHOD: >#{request.request_method}<
|
62
|
+
PATH_INFO: >#{request.path_info}<
|
63
|
+
QUERY_STRING: >#{request.query_string}<
|
64
|
+
|
65
|
+
SCRIPT_NAME: >#{request.script_name}<
|
66
|
+
REQUEST_URI: >#{request.url}<
|
67
|
+
|
68
|
+
|
69
|
+
#{dump_routes}
|
70
|
+
|
71
|
+
#{dump_version}
|
72
|
+
TXT
|
73
|
+
|
74
|
+
halt 404, msg
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
############################
|
79
|
+
## fallback helpers
|
80
|
+
|
81
|
+
def dump_routes ## todo/check - rename to build_routes/show_routes/etc. - why? why not?
|
82
|
+
buf = ""
|
83
|
+
walk_routes_for( buf, self.class )
|
84
|
+
buf
|
85
|
+
end
|
86
|
+
|
87
|
+
def walk_routes_for( buf, base=self.class )
|
88
|
+
|
89
|
+
buf << " Routes >#{base.name}<:\n\n"
|
90
|
+
|
91
|
+
base.routes.each do |method,routes|
|
92
|
+
buf << " #{method}:\n"
|
93
|
+
routes.each do |pattern,block|
|
94
|
+
buf << " #{pattern.to_s}\n"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
if base.superclass.respond_to? :routes
|
99
|
+
buf << "\n\n"
|
100
|
+
walk_routes_for( buf, base.superclass )
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
def dump_version
|
106
|
+
## single line version string
|
107
|
+
buf = " " # note: start with two leading spaces (indent)
|
108
|
+
buf << "webservice/#{VERSION} "
|
109
|
+
buf << "(#{self.class.environment}), "
|
110
|
+
buf << "rack/#{Rack::RELEASE} (#{Rack::VERSION.join('.')}) - "
|
111
|
+
buf << "ruby/#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}/#{RUBY_PLATFORM})"
|
112
|
+
buf
|
113
|
+
end
|
114
|
+
|
115
|
+
end # class Base
|
116
|
+
|
117
|
+
end # module Webservice
|
File without changes
|
@@ -1,381 +1,297 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
|
4
|
-
module Webservice
|
5
|
-
|
6
|
-
## use (an reuse from Rack) some freezed string constants
|
7
|
-
## HTTP verbs
|
8
|
-
GET = Rack::GET
|
9
|
-
POST = Rack::POST
|
10
|
-
PATCH = Rack::PATCH
|
11
|
-
PUT = Rack::PUT
|
12
|
-
DELETE = Rack::DELETE
|
13
|
-
HEAD = Rack::HEAD
|
14
|
-
OPTIONS = Rack::OPTIONS
|
15
|
-
|
16
|
-
## HTTP headers
|
17
|
-
CONTENT_LENGTH = Rack::CONTENT_LENGTH
|
18
|
-
CONTENT_TYPE = Rack::CONTENT_TYPE
|
19
|
-
# -- more HTTP headers - not available from Rack
|
20
|
-
LOCATION = 'Location'.freeze
|
21
|
-
LAST_MODIFIED = 'Last-Modified'.freeze
|
22
|
-
|
23
|
-
|
24
|
-
module Helpers
|
25
|
-
## add some more helpers
|
26
|
-
## "inspired" by sinatra (mostly) - for staying compatible
|
27
|
-
## see https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb
|
28
|
-
|
29
|
-
## todo -- add status -- why? why not??
|
30
|
-
|
31
|
-
# Halt processing and return the error status provided.
|
32
|
-
def error( code, body=nil )
|
33
|
-
response.body = body unless body.nil?
|
34
|
-
halt code
|
35
|
-
end
|
36
|
-
|
37
|
-
# Halt processing and return a 404 Not Found.
|
38
|
-
def not_found( body=nil )
|
39
|
-
error 404, body
|
40
|
-
end
|
41
|
-
|
42
|
-
|
43
|
-
def
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
##
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
## puts
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
## puts
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
def
|
140
|
-
## puts "calling #{self.name}.
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
def
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
def
|
163
|
-
|
164
|
-
def route(
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
end
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
def
|
192
|
-
|
193
|
-
##
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
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
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
# no match found for route/request
|
300
|
-
halt 404
|
301
|
-
end
|
302
|
-
end
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
##################################
|
307
|
-
### add some fallback (builtin) routes
|
308
|
-
|
309
|
-
fallback_route GET, '/favicon.ico' do
|
310
|
-
## use 302 to redirect
|
311
|
-
## note: use strg+F5 to refresh page (clear cache for favicon.ico)
|
312
|
-
redirect_to '/webservice-32x32.png'
|
313
|
-
end
|
314
|
-
|
315
|
-
fallback_route GET, '/webservice-32x32.png' do
|
316
|
-
send_file "#{Webservice.root}/assets/webservice-32x32.png"
|
317
|
-
end
|
318
|
-
|
319
|
-
fallback_route GET, '/routes' do
|
320
|
-
msg =<<TXT
|
321
|
-
#{dump_routes}
|
322
|
-
|
323
|
-
#{dump_version}
|
324
|
-
TXT
|
325
|
-
end
|
326
|
-
|
327
|
-
## catch all (404 not found)
|
328
|
-
fallback_route GET, '/*' do
|
329
|
-
pp env
|
330
|
-
pp self.class.routes
|
331
|
-
|
332
|
-
msg =<<TXT
|
333
|
-
404 Not Found
|
334
|
-
|
335
|
-
No route matched >#{request.request_method} #{request.path_info}<:
|
336
|
-
|
337
|
-
REQUEST_METHOD: >#{request.request_method}<
|
338
|
-
PATH_INFO: >#{request.path_info}<
|
339
|
-
QUERY_STRING: >#{request.query_string}<
|
340
|
-
|
341
|
-
SCRIPT_NAME: >#{request.script_name}<
|
342
|
-
REQUEST_URI: >#{env['REQUEST_URI']}<
|
343
|
-
|
344
|
-
|
345
|
-
#{dump_routes}
|
346
|
-
|
347
|
-
#{dump_version}
|
348
|
-
TXT
|
349
|
-
|
350
|
-
halt 404, msg
|
351
|
-
end
|
352
|
-
|
353
|
-
############################
|
354
|
-
## fallback helpers
|
355
|
-
|
356
|
-
def dump_routes ## todo/check - rename to build_routes/show_routes/etc. - why? why not?
|
357
|
-
buf = ""
|
358
|
-
buf << " Routes >#{self.class.name}<:\n\n"
|
359
|
-
|
360
|
-
self.class.routes.each do |method,routes|
|
361
|
-
buf << " #{method}:\n"
|
362
|
-
routes.each do |pattern,block|
|
363
|
-
buf << " #{pattern.to_s}\n"
|
364
|
-
end
|
365
|
-
end
|
366
|
-
buf
|
367
|
-
end
|
368
|
-
|
369
|
-
def dump_version
|
370
|
-
## single line version string
|
371
|
-
buf = " " # note: start with two leading spaces (indent)
|
372
|
-
buf << "webservice/#{VERSION} "
|
373
|
-
buf << "(#{self.class.environment}), "
|
374
|
-
buf << "rack/#{Rack::RELEASE} (#{Rack::VERSION.join('.')}) - "
|
375
|
-
buf << "ruby/#{RUBY_VERSION} (#{RUBY_RELEASE_DATE}/#{RUBY_PLATFORM})"
|
376
|
-
buf
|
377
|
-
end
|
378
|
-
|
379
|
-
end # class Base
|
380
|
-
|
381
|
-
end # module Webservice
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
module Webservice
|
5
|
+
|
6
|
+
## use (an reuse from Rack) some freezed string constants
|
7
|
+
## HTTP verbs
|
8
|
+
GET = Rack::GET
|
9
|
+
POST = Rack::POST
|
10
|
+
PATCH = Rack::PATCH
|
11
|
+
PUT = Rack::PUT
|
12
|
+
DELETE = Rack::DELETE
|
13
|
+
HEAD = Rack::HEAD
|
14
|
+
OPTIONS = Rack::OPTIONS
|
15
|
+
|
16
|
+
## HTTP headers
|
17
|
+
CONTENT_LENGTH = Rack::CONTENT_LENGTH
|
18
|
+
CONTENT_TYPE = Rack::CONTENT_TYPE
|
19
|
+
# -- more HTTP headers - not available from Rack
|
20
|
+
LOCATION = 'Location'.freeze
|
21
|
+
LAST_MODIFIED = 'Last-Modified'.freeze
|
22
|
+
|
23
|
+
|
24
|
+
module Helpers
|
25
|
+
## add some more helpers
|
26
|
+
## "inspired" by sinatra (mostly) - for staying compatible
|
27
|
+
## see https://github.com/sinatra/sinatra/blob/master/lib/sinatra/base.rb
|
28
|
+
|
29
|
+
## todo -- add status -- why? why not??
|
30
|
+
|
31
|
+
# Halt processing and return the error status provided.
|
32
|
+
def error( code, body=nil )
|
33
|
+
response.body = body unless body.nil?
|
34
|
+
halt code
|
35
|
+
end
|
36
|
+
|
37
|
+
# Halt processing and return a 404 Not Found.
|
38
|
+
def not_found( body=nil )
|
39
|
+
error 404, body
|
40
|
+
end
|
41
|
+
|
42
|
+
|
43
|
+
def redirect( uri, status=302 ) ## Note: 302 == Found, 301 == Moved Permanently
|
44
|
+
|
45
|
+
##
|
46
|
+
## todo/fix: add/prepepand SCRIPT_NAME if NOT empty - why? why not??
|
47
|
+
## without SCRIPT_NAME redirect will not work with (non-root) mounted apps
|
48
|
+
|
49
|
+
halt status, { LOCATION => uri }
|
50
|
+
end
|
51
|
+
alias_method :redirect_to, :redirect
|
52
|
+
|
53
|
+
|
54
|
+
# Set multiple response headers with Hash.
|
55
|
+
def headers( hash=nil )
|
56
|
+
response.headers.merge! hash if hash
|
57
|
+
response.headers
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
|
62
|
+
## (simple) content_type helper - all "hard-coded" for now; always uses utf-8 too
|
63
|
+
def content_type( type=nil )
|
64
|
+
return response[ CONTENT_TYPE ] unless type
|
65
|
+
|
66
|
+
if type.to_sym == :json
|
67
|
+
response[ CONTENT_TYPE ] = 'application/json; charset=utf-8'
|
68
|
+
elsif type.to_sym == :js || type.to_sym == :javascript
|
69
|
+
response[ CONTENT_TYPE ] = 'application/javascript; charset=utf-8'
|
70
|
+
## use 'text/javascript; charset=utf-8' -- why? why not??
|
71
|
+
## note: ietf recommends application/javascript
|
72
|
+
elsif type.to_sym == :csv || type.to_sym == :text || type.to_sym == :txt
|
73
|
+
response[ CONTENT_TYPE ] = 'text/plain; charset=utf-8'
|
74
|
+
elsif type.to_sym == :html || type.to_sym == :htm
|
75
|
+
response[ CONTENT_TYPE ] = 'text/html; charset=utf-8'
|
76
|
+
else
|
77
|
+
### unknown type; do nothing - sorry; issue warning - why? why not??
|
78
|
+
end
|
79
|
+
end ## method content_type
|
80
|
+
|
81
|
+
|
82
|
+
## simple send file (e.g. for images/binary blobs, etc.) helper
|
83
|
+
def send_file( path )
|
84
|
+
## puts "send_file path=>#{path}<"
|
85
|
+
|
86
|
+
## puts "HTTP_IF_MODIFIED_SINCE:"
|
87
|
+
## puts request.get_header('HTTP_IF_MODIFIED_SINCE')
|
88
|
+
|
89
|
+
last_modified = File.mtime(path).httpdate
|
90
|
+
## puts "last_modified:"
|
91
|
+
## puts last_modified
|
92
|
+
|
93
|
+
## HTTP 304 => Not Modified
|
94
|
+
halt 304 if request.get_header('HTTP_IF_MODIFIED_SINCE') == last_modified
|
95
|
+
|
96
|
+
headers[ LAST_MODIFIED ] = last_modified
|
97
|
+
|
98
|
+
bytes = File.open( path, 'rb' ) { |f| f.read }
|
99
|
+
|
100
|
+
## puts "encoding:"
|
101
|
+
## puts bytes.encoding
|
102
|
+
|
103
|
+
## puts "size:"
|
104
|
+
## puts bytes.size
|
105
|
+
|
106
|
+
extname = File.extname( path )
|
107
|
+
## puts "extname:"
|
108
|
+
## puts extname
|
109
|
+
|
110
|
+
## puts "headers (before):"
|
111
|
+
## pp headers
|
112
|
+
|
113
|
+
if extname == '.png'
|
114
|
+
headers[ CONTENT_TYPE ] = 'image/png'
|
115
|
+
else
|
116
|
+
## fallback to application/octet-stream
|
117
|
+
headers[ CONTENT_TYPE ] = 'application/octet-stream'
|
118
|
+
end
|
119
|
+
|
120
|
+
headers[ CONTENT_LENGTH ] = bytes.size.to_s ## note: do NOT forget to use to_s (requires string!)
|
121
|
+
|
122
|
+
## puts "headers (after):"
|
123
|
+
## pp headers
|
124
|
+
|
125
|
+
halt 200, bytes
|
126
|
+
end # method send_file
|
127
|
+
|
128
|
+
|
129
|
+
|
130
|
+
end ## module Helpers
|
131
|
+
|
132
|
+
|
133
|
+
class Metal ## bare bones core (use base for more built-in functionality)
|
134
|
+
include Helpers
|
135
|
+
|
136
|
+
|
137
|
+
class << self
|
138
|
+
|
139
|
+
def call( env ) ## note self.call(env) lets you use => run Base instead of run Base.new
|
140
|
+
## puts "calling #{self.name}.call"
|
141
|
+
prototype.call( env )
|
142
|
+
end
|
143
|
+
|
144
|
+
def prototype
|
145
|
+
## puts "calling #{self.name}.prototype"
|
146
|
+
@prototype ||= self.new
|
147
|
+
## pp @prototype
|
148
|
+
## @prototype
|
149
|
+
end
|
150
|
+
|
151
|
+
|
152
|
+
## todo/check: all verbs needed! (supported) - why, why not??
|
153
|
+
## e.g. add LINK, UNLINK ??
|
154
|
+
|
155
|
+
# Note: for now defining a `GET` handler also automatically defines
|
156
|
+
# a `HEAD` handler (follows sinatra convention)
|
157
|
+
def get( pattern, &block )
|
158
|
+
route( GET, pattern, &block )
|
159
|
+
route( HEAD, pattern, &block )
|
160
|
+
end
|
161
|
+
|
162
|
+
def post( pattern, &block) route( POST, pattern, &block ); end
|
163
|
+
def patch( pattern, &block) route( PATCH, pattern, &block ); end
|
164
|
+
def put( pattern, &block) route( PUT, pattern, &block ); end
|
165
|
+
def delete( pattern, &block) route( DELETE, pattern, &block ); end
|
166
|
+
def head( pattern, &block) route( HEAD, pattern, &block ); end
|
167
|
+
def options( pattern, &block) route( OPTIONS, pattern, &block ); end
|
168
|
+
|
169
|
+
def route( method, pattern, &block )
|
170
|
+
puts "[debug] Webservice::Metal.#{method.downcase} - add route #{method} '#{pattern}' to #<#{self.name}:#{self.object_id}> : #{self.class.name}"
|
171
|
+
|
172
|
+
## note: for now use (default to) the sintatra-style patterns (with mustermann)
|
173
|
+
routes[method] << [Mustermann::Sinatra.new(pattern), block]
|
174
|
+
end
|
175
|
+
|
176
|
+
def routes
|
177
|
+
@routes ||= Hash.new { |hash, key| hash[key]=[] }
|
178
|
+
end
|
179
|
+
|
180
|
+
def environment
|
181
|
+
## include APP_ENV why? why not?
|
182
|
+
## todo -- cache value? why why not? (see/follow sinatara set machinery ??)
|
183
|
+
(ENV['APP_ENV'] || ENV['RACK_ENV'] || :development).to_sym
|
184
|
+
end
|
185
|
+
|
186
|
+
def development?() environment == :development; end
|
187
|
+
def production?() environment == :production; end
|
188
|
+
def test?() environment == :test; end
|
189
|
+
|
190
|
+
## convenience method
|
191
|
+
def run!
|
192
|
+
puts "[debug] Webservice::Metal.run! - self = #<#{self.name}:#{self.object_id}> : #{self.class.name}" # note: assumes self is class
|
193
|
+
app = self ## note: use self; will be derived class (e.g. App and not Base)
|
194
|
+
port = 4567
|
195
|
+
Rack::Handler::WEBrick.run( app, Port:port ) do |server|
|
196
|
+
## todo: add traps here - why, why not??
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end ## class << self
|
200
|
+
|
201
|
+
|
202
|
+
attr_reader :request
|
203
|
+
attr_reader :response
|
204
|
+
attr_reader :params
|
205
|
+
attr_reader :env
|
206
|
+
|
207
|
+
|
208
|
+
def call( env )
|
209
|
+
dup.call!( env )
|
210
|
+
end
|
211
|
+
|
212
|
+
def call!( env )
|
213
|
+
env['PATH_INFO'] = '/' if env['PATH_INFO'].empty?
|
214
|
+
|
215
|
+
@request = Rack::Request.new( env )
|
216
|
+
@response = Rack::Response.new
|
217
|
+
@params = request.params
|
218
|
+
@env = env
|
219
|
+
|
220
|
+
catch(:halt) do
|
221
|
+
## call before if defined in derived (sub)classes
|
222
|
+
before if respond_to? :before
|
223
|
+
|
224
|
+
route!
|
225
|
+
end
|
226
|
+
|
227
|
+
@response.finish
|
228
|
+
end
|
229
|
+
|
230
|
+
|
231
|
+
def halt( *args )
|
232
|
+
response.status = args.detect{ |arg| arg.is_a?(Fixnum) } || 200
|
233
|
+
response.header.merge!( args.detect{ |arg| arg.is_a?(Hash) } || {} )
|
234
|
+
response.body = [args.detect{ |arg| arg.is_a?(String) } || '']
|
235
|
+
throw :halt, response ## todo/check response arg used - what for??
|
236
|
+
end
|
237
|
+
|
238
|
+
|
239
|
+
private
|
240
|
+
|
241
|
+
## run a route block and throw :halt
|
242
|
+
def route_eval( &block )
|
243
|
+
obj = instance_eval( &block ) ## return result - for now assumes a single object
|
244
|
+
|
245
|
+
if respond_to? :handle_response
|
246
|
+
handle_response( obj ) ## prepare response
|
247
|
+
else
|
248
|
+
## default response; string expected
|
249
|
+
## if string pass it along
|
250
|
+
## if NOT string for debugging / dump to string with inspect
|
251
|
+
response.status = 200
|
252
|
+
response.body = [obj.is_a?(String) ? obj.to_s : obj.inspect]
|
253
|
+
end
|
254
|
+
|
255
|
+
throw :halt
|
256
|
+
end
|
257
|
+
|
258
|
+
|
259
|
+
def route!( base=self.class )
|
260
|
+
|
261
|
+
puts " [#{base.name}] try matching route >#{request.request_method} #{request.path_info}<..."
|
262
|
+
|
263
|
+
routes = base.routes[ request.request_method ]
|
264
|
+
routes.each do |pattern, block|
|
265
|
+
## puts "trying matching route >#{request.path_info}<..."
|
266
|
+
url_params = pattern.params( request.path_info )
|
267
|
+
if url_params ## note: params returns nil if no match
|
268
|
+
## puts " BINGO! url_params: #{url_params.inspect}"
|
269
|
+
if !url_params.empty? ## url_params hash NOT empty (e.g. {}) merge with req params
|
270
|
+
## todo/fix: check merge order - params overwrites url_params - why? why not??
|
271
|
+
|
272
|
+
## todo/fix: check params - params works with string keys only - check for indiffent keys - why? why not?
|
273
|
+
## check rack params - works with indifferent keys by default??
|
274
|
+
@params = url_params.merge( @params )
|
275
|
+
end
|
276
|
+
route_eval( &block )
|
277
|
+
## todo/check: keep return - why? why not? - note: route_eval will always throw :halt
|
278
|
+
## handler.handle_response( instance_eval( &block ))
|
279
|
+
## return
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
## check recursive - all super(parent)classes too (e.g. App > Base > Metal etc.)
|
284
|
+
## note: superclass is the parent class (returns nil if no more parent class)
|
285
|
+
if base.superclass.respond_to? :routes
|
286
|
+
route!( base.superclass )
|
287
|
+
return
|
288
|
+
end
|
289
|
+
|
290
|
+
# no match found for route/request
|
291
|
+
halt 404
|
292
|
+
end
|
293
|
+
|
294
|
+
|
295
|
+
end # class Metal
|
296
|
+
|
297
|
+
end # module Webservice
|
data/lib/webservice/version.rb
CHANGED
@@ -4,8 +4,8 @@ module Webservice
|
|
4
4
|
|
5
5
|
module Version
|
6
6
|
MAJOR = 0 ## todo: namespace inside version or something - why? why not??
|
7
|
-
MINOR =
|
8
|
-
PATCH =
|
7
|
+
MINOR = 7
|
8
|
+
PATCH = 0 ## note: if not put in module will overwrite PATCH (HTTP Verb Constant)!!!
|
9
9
|
end
|
10
10
|
|
11
11
|
VERSION = [Version::MAJOR,
|
data/test/helper.rb
CHANGED
@@ -2,11 +2,6 @@
|
|
2
2
|
## minitest setup
|
3
3
|
require 'minitest/autorun'
|
4
4
|
|
5
|
-
## deps
|
6
|
-
|
7
|
-
### require 'worlddb'
|
8
|
-
## require 'sportdb/models' # note: will include worlddb
|
9
|
-
|
10
5
|
|
11
6
|
## our own code
|
12
7
|
require 'webservice' ## includes rack
|
@@ -14,4 +9,3 @@ require 'webservice' ## includes rack
|
|
14
9
|
|
15
10
|
## more libs
|
16
11
|
require 'rack/test'
|
17
|
-
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
|
4
|
+
## gets evaluated in class context (self is class) -- uses class_eval
|
5
|
+
puts "[debug] eval (top) self = #<#{self.name}:#{self.object_id}> : #{self.class.name}"
|
6
|
+
|
7
|
+
get '/hello' do
|
8
|
+
|
9
|
+
## gets evaluated in object context (self is object) -- uses instance_eval
|
10
|
+
puts "[debug] eval (get /hello) self = #<#{self.class.name}:#{self.class.object_id}> : #{self.class.class.name}"
|
11
|
+
|
12
|
+
data = { text: 'hello' }
|
13
|
+
data
|
14
|
+
end
|
data/test/test_metal.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
###
|
4
|
+
# to run use
|
5
|
+
# ruby -I ./lib -I ./test test/test_metal.rb
|
6
|
+
|
7
|
+
|
8
|
+
require 'helper'
|
9
|
+
|
10
|
+
|
11
|
+
class MetalApp < Webservice::Metal
|
12
|
+
|
13
|
+
get '/hello' do
|
14
|
+
'Hello, World!'
|
15
|
+
end
|
16
|
+
|
17
|
+
end # class MetalApp
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
class TestMetal < MiniTest::Test
|
22
|
+
|
23
|
+
include Rack::Test::Methods
|
24
|
+
|
25
|
+
def app
|
26
|
+
## return (rack-ready) app object
|
27
|
+
@@app ||= begin
|
28
|
+
app_class = MetalApp
|
29
|
+
pp app_class.routes
|
30
|
+
app_class
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
def test_get
|
36
|
+
get '/hello'
|
37
|
+
assert last_response.ok?
|
38
|
+
|
39
|
+
get '/'
|
40
|
+
assert_equal 404, last_response.status
|
41
|
+
end
|
42
|
+
|
43
|
+
end # class TestMetal
|
data/test/test_mustermann.rb
CHANGED
@@ -12,12 +12,16 @@ class TestMustermann < MiniTest::Test
|
|
12
12
|
|
13
13
|
def test_hello
|
14
14
|
|
15
|
-
pp Rack::VERSION
|
15
|
+
pp Rack::VERSION # e.g. [1,2]
|
16
|
+
pp Rack::VERSION.join('.') # e.g. 1.2
|
17
|
+
pp Rack::RELEASE
|
16
18
|
pp Mustermann::VERSION
|
17
19
|
|
20
|
+
|
18
21
|
pattern = Mustermann::Sinatra.new( '/:name' )
|
19
22
|
pp pattern
|
20
23
|
pp pattern.names
|
24
|
+
pp pattern.to_s # prints passed in pattern (as "plain" string as is)
|
21
25
|
|
22
26
|
m = pattern.match( "/test" )
|
23
27
|
pp m
|
@@ -2,13 +2,13 @@
|
|
2
2
|
|
3
3
|
###
|
4
4
|
# to run use
|
5
|
-
# ruby -I ./lib -I ./test test/
|
5
|
+
# ruby -I ./lib -I ./test test/test_service_app.rb
|
6
6
|
|
7
7
|
|
8
8
|
require 'helper'
|
9
9
|
|
10
10
|
|
11
|
-
class
|
11
|
+
class TestServiceApp < MiniTest::Test
|
12
12
|
|
13
13
|
include Rack::Test::Methods
|
14
14
|
|
@@ -17,7 +17,7 @@ class TestApp < MiniTest::Test
|
|
17
17
|
@@app ||= begin
|
18
18
|
app_class = Webservice.load_file( "#{Webservice.root}/test/service/app.rb" )
|
19
19
|
pp app_class.routes
|
20
|
-
app_class
|
20
|
+
app_class
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -113,4 +113,4 @@ HTML
|
|
113
113
|
assert_equal "Error fatal", last_response.body
|
114
114
|
end
|
115
115
|
|
116
|
-
end # class
|
116
|
+
end # class TestServiceApp
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
###
|
4
|
+
# to run use
|
5
|
+
# ruby -I ./lib -I ./test test/test_service_debug.rb
|
6
|
+
|
7
|
+
|
8
|
+
require 'helper'
|
9
|
+
|
10
|
+
|
11
|
+
class TestServiceDebug < MiniTest::Test
|
12
|
+
|
13
|
+
include Rack::Test::Methods
|
14
|
+
|
15
|
+
def app
|
16
|
+
## return (rack-ready) app object
|
17
|
+
@@app ||= begin
|
18
|
+
app_class = Webservice.load_file( "#{Webservice.root}/test/service/debug.rb" )
|
19
|
+
pp app_class.routes
|
20
|
+
app_class
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def test_get
|
25
|
+
get '/hello'
|
26
|
+
assert last_response.ok?
|
27
|
+
|
28
|
+
get '/'
|
29
|
+
assert_equal 404, last_response.status
|
30
|
+
end
|
31
|
+
|
32
|
+
end # class TestServiceDebug
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: webservice
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gerald Bauer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-09-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: logutils
|
@@ -95,15 +95,17 @@ files:
|
|
95
95
|
- Rakefile
|
96
96
|
- assets/webservice-32x32.png
|
97
97
|
- lib/webservice.rb
|
98
|
-
- lib/webservice/base.rb
|
99
|
-
- lib/webservice/response_handler.rb
|
98
|
+
- lib/webservice/base/base.rb
|
99
|
+
- lib/webservice/base/response_handler.rb
|
100
|
+
- lib/webservice/metal.rb
|
100
101
|
- lib/webservice/version.rb
|
101
102
|
- test/helper.rb
|
102
103
|
- test/service/app.rb
|
103
|
-
- test/
|
104
|
+
- test/service/debug.rb
|
105
|
+
- test/test_metal.rb
|
104
106
|
- test/test_mustermann.rb
|
105
|
-
- test/
|
106
|
-
- test/
|
107
|
+
- test/test_service_app.rb
|
108
|
+
- test/test_service_debug.rb
|
107
109
|
homepage: https://github.com/rubylibs/webservice
|
108
110
|
licenses:
|
109
111
|
- Public Domain
|
data/test/test_samples.rb
DELETED
@@ -1,34 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
###
|
4
|
-
# to run use
|
5
|
-
# ruby -I ./lib -I ./test test/test_samples.rb
|
6
|
-
|
7
|
-
|
8
|
-
require 'helper'
|
9
|
-
|
10
|
-
|
11
|
-
class TestSamples < MiniTest::Test
|
12
|
-
|
13
|
-
def test_beer
|
14
|
-
app_class = Webservice.load_file( "#{Webservice.root}/samples/beer.rb" )
|
15
|
-
pp app_class.routes
|
16
|
-
|
17
|
-
assert true # if we get here - test success
|
18
|
-
end
|
19
|
-
|
20
|
-
def test_football
|
21
|
-
app_class = Webservice.load_file( "#{Webservice.root}/samples/football.rb" )
|
22
|
-
pp app_class.routes
|
23
|
-
|
24
|
-
assert true # if we get here - test success
|
25
|
-
end
|
26
|
-
|
27
|
-
def test_world
|
28
|
-
app_class = Webservice.load_file( "#{Webservice.root}/samples/world.rb" )
|
29
|
-
pp app_class.routes
|
30
|
-
|
31
|
-
assert true # if we get here - test success
|
32
|
-
end
|
33
|
-
|
34
|
-
end # class TestSamples
|
data/test/test_samples_debug.rb
DELETED
@@ -1,20 +0,0 @@
|
|
1
|
-
# encoding: utf-8
|
2
|
-
|
3
|
-
###
|
4
|
-
# to run use
|
5
|
-
# ruby -I ./lib -I ./test test/test_samples_debug.rb
|
6
|
-
|
7
|
-
|
8
|
-
require 'helper'
|
9
|
-
|
10
|
-
|
11
|
-
class TestSamplesDebug < MiniTest::Test
|
12
|
-
|
13
|
-
def test_debug
|
14
|
-
app_class = Webservice.load_file( "#{Webservice.root}/samples/debug.rb" )
|
15
|
-
pp app_class.routes
|
16
|
-
|
17
|
-
assert true # if we get here - test success
|
18
|
-
end
|
19
|
-
|
20
|
-
end # class TestSamplesDebug
|