webservice 0.6.1 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|