webmachine 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +12 -10
- data/Guardfile +1 -1
- data/README.md +1 -1
- data/examples/application.rb +35 -0
- data/lib/webmachine.rb +4 -3
- data/lib/webmachine/adapter.rb +36 -0
- data/lib/webmachine/adapters/mongrel.rb +18 -12
- data/lib/webmachine/adapters/rack.rb +26 -7
- data/lib/webmachine/adapters/webrick.rb +20 -16
- data/lib/webmachine/application.rb +108 -0
- data/lib/webmachine/chunked_body.rb +2 -2
- data/lib/webmachine/configuration.rb +24 -14
- data/lib/webmachine/decision/conneg.rb +9 -10
- data/lib/webmachine/decision/flow.rb +25 -28
- data/lib/webmachine/decision/fsm.rb +21 -22
- data/lib/webmachine/decision/helpers.rb +3 -3
- data/lib/webmachine/dispatcher.rb +18 -10
- data/lib/webmachine/dispatcher/route.rb +54 -17
- data/lib/webmachine/errors.rb +1 -1
- data/lib/webmachine/headers.rb +2 -2
- data/lib/webmachine/media_type.rb +2 -2
- data/lib/webmachine/request.rb +78 -3
- data/lib/webmachine/resource.rb +3 -2
- data/lib/webmachine/resource/authentication.rb +4 -3
- data/lib/webmachine/resource/callbacks.rb +4 -3
- data/lib/webmachine/resource/encodings.rb +4 -3
- data/lib/webmachine/response.rb +3 -2
- data/lib/webmachine/streaming.rb +4 -4
- data/lib/webmachine/version.rb +1 -1
- data/spec/webmachine/adapter_spec.rb +40 -0
- data/spec/webmachine/adapters/mongrel_spec.rb +22 -0
- data/spec/webmachine/adapters/rack_spec.rb +34 -8
- data/spec/webmachine/adapters/webrick_spec.rb +18 -0
- data/spec/webmachine/application_spec.rb +73 -0
- data/spec/webmachine/dispatcher/route_spec.rb +59 -2
- data/spec/webmachine/dispatcher_spec.rb +17 -5
- data/spec/webmachine/request_spec.rb +158 -1
- data/webmachine.gemspec +6 -27
- metadata +304 -112
- data/spec/tests.org +0 -80
@@ -10,21 +10,31 @@ module Webmachine
|
|
10
10
|
# @attr [Hash] adapter_options adapter-specific options, defaults to {}
|
11
11
|
Configuration = Struct.new(:ip, :port, :adapter, :adapter_options)
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
# @return [Configuration] the default configuration
|
14
|
+
def Configuration.default
|
15
|
+
new("0.0.0.0", 8080, :WEBrick, {})
|
16
16
|
end
|
17
17
|
|
18
|
-
#
|
19
|
-
#
|
20
|
-
#
|
21
|
-
# @
|
22
|
-
# @
|
23
|
-
|
24
|
-
|
25
|
-
@configuration ||= Configuration.new("0.0.0.0", 8080, :WEBrick, {})
|
26
|
-
yield @configuration if block_given?
|
18
|
+
# Yields the current configuration to the passed block.
|
19
|
+
# @yield [config] a block that will modify the configuration
|
20
|
+
# @yieldparam [Configuration] config the adapter configuration
|
21
|
+
# @return self
|
22
|
+
# @see Application#configure
|
23
|
+
def self.configure(&block)
|
24
|
+
application.configure(&block)
|
27
25
|
self
|
28
26
|
end
|
29
|
-
|
30
|
-
|
27
|
+
|
28
|
+
# @return [Configuration] the current configuration
|
29
|
+
# @see Application#configuration
|
30
|
+
def self.configuration
|
31
|
+
application.configuration
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sets the current configuration
|
35
|
+
# @param [Configuration] configuration the new config
|
36
|
+
# @see Application#configuration=
|
37
|
+
def self.configuration=(configuration)
|
38
|
+
application.configuration = configuration
|
39
|
+
end
|
40
|
+
end # Webmachine
|
@@ -226,15 +226,14 @@ module Webmachine
|
|
226
226
|
# {MediaType} items instead of Strings.
|
227
227
|
# @see PriorityList#add_header_val
|
228
228
|
def add_header_val(c)
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
raise MalformedRequest, t('invalid_media_type', :type => c)
|
235
|
-
end
|
229
|
+
mt = MediaType.parse(c)
|
230
|
+
q = mt.params.delete('q') || 1.0
|
231
|
+
add(q.to_f, mt)
|
232
|
+
rescue ArgumentError
|
233
|
+
raise MalformedRequest, t('invalid_media_type', :type => c)
|
236
234
|
end
|
237
235
|
end
|
238
|
-
|
239
|
-
|
240
|
-
end
|
236
|
+
|
237
|
+
end # module Conneg
|
238
|
+
end # module Decision
|
239
|
+
end # module Webmachine
|
@@ -131,7 +131,7 @@ module Webmachine
|
|
131
131
|
|
132
132
|
# OPTIONS?
|
133
133
|
def b3
|
134
|
-
if request.
|
134
|
+
if request.options?
|
135
135
|
response.headers.merge!(resource.options)
|
136
136
|
200
|
137
137
|
else
|
@@ -254,14 +254,12 @@ module Webmachine
|
|
254
254
|
|
255
255
|
# If-Unmodified-Since is valid date?
|
256
256
|
def h11
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
:h12
|
264
|
-
end
|
257
|
+
date = Time.httpdate(request.if_unmodified_since)
|
258
|
+
metadata['If-Unmodified-Since'] = date
|
259
|
+
rescue ArgumentError
|
260
|
+
:i12
|
261
|
+
else
|
262
|
+
:h12
|
265
263
|
end
|
266
264
|
|
267
265
|
# Last-Modified > I-UM-S?
|
@@ -284,7 +282,7 @@ module Webmachine
|
|
284
282
|
|
285
283
|
# PUT?
|
286
284
|
def i7
|
287
|
-
request.
|
285
|
+
request.put? ? :i4 : :k7
|
288
286
|
end
|
289
287
|
|
290
288
|
# If-none-match exists?
|
@@ -299,7 +297,7 @@ module Webmachine
|
|
299
297
|
|
300
298
|
# GET or HEAD?
|
301
299
|
def j18
|
302
|
-
|
300
|
+
(request.get? || request.head?) ? 304 : 412
|
303
301
|
end
|
304
302
|
|
305
303
|
# Moved permanently?
|
@@ -341,7 +339,7 @@ module Webmachine
|
|
341
339
|
|
342
340
|
# POST?
|
343
341
|
def l7
|
344
|
-
request.
|
342
|
+
request.post? ? :m7 : 404
|
345
343
|
end
|
346
344
|
|
347
345
|
# If-Modified-Since exists?
|
@@ -351,14 +349,12 @@ module Webmachine
|
|
351
349
|
|
352
350
|
# IMS is valid date?
|
353
351
|
def l14
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
:l15
|
361
|
-
end
|
352
|
+
date = Time.httpdate(request.if_modified_since)
|
353
|
+
metadata['If-Modified-Since'] = date
|
354
|
+
rescue ArgumentError
|
355
|
+
:m16
|
356
|
+
else
|
357
|
+
:l15
|
362
358
|
end
|
363
359
|
|
364
360
|
# IMS > Now?
|
@@ -373,7 +369,7 @@ module Webmachine
|
|
373
369
|
|
374
370
|
# POST?
|
375
371
|
def m5
|
376
|
-
request.
|
372
|
+
request.post? ? :n5 : 410
|
377
373
|
end
|
378
374
|
|
379
375
|
# Server allows POST to missing resource?
|
@@ -383,7 +379,7 @@ module Webmachine
|
|
383
379
|
|
384
380
|
# DELETE?
|
385
381
|
def m16
|
386
|
-
request.
|
382
|
+
request.delete? ? :m20 : :n16
|
387
383
|
end
|
388
384
|
|
389
385
|
# DELETE enacted immediately? (Also where DELETE is forced.)
|
@@ -439,7 +435,7 @@ module Webmachine
|
|
439
435
|
|
440
436
|
# POST?
|
441
437
|
def n16
|
442
|
-
request.
|
438
|
+
request.post? ? :n11 : :o16
|
443
439
|
end
|
444
440
|
|
445
441
|
# Conflict?
|
@@ -454,13 +450,13 @@ module Webmachine
|
|
454
450
|
|
455
451
|
# PUT?
|
456
452
|
def o16
|
457
|
-
request.
|
453
|
+
request.put? ? :o14 : :o18
|
458
454
|
end
|
459
455
|
|
460
456
|
# Multiple representations?
|
461
457
|
# Also where body generation for GET and HEAD is done.
|
462
458
|
def o18
|
463
|
-
if request.
|
459
|
+
if request.get? || request.head?
|
464
460
|
add_caching_headers
|
465
461
|
content_type = metadata['Content-Type']
|
466
462
|
handler = resource.content_types_provided.find {|ct, _| content_type.type_matches?(MediaType.parse(ct)) }.last
|
@@ -501,6 +497,7 @@ module Webmachine
|
|
501
497
|
def p11
|
502
498
|
!response.headers["Location"] ? :o20 : 201
|
503
499
|
end
|
504
|
-
|
505
|
-
|
506
|
-
end
|
500
|
+
|
501
|
+
end # module Flow
|
502
|
+
end # module Decision
|
503
|
+
end # module Webmachine
|
@@ -20,27 +20,25 @@ module Webmachine
|
|
20
20
|
|
21
21
|
# Processes the request, iteratively invoking the decision methods in {Flow}.
|
22
22
|
def run
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
raise InvalidResource, t('fsm_broke', :state => state, :result => result.inspect)
|
36
|
-
end
|
23
|
+
state = Flow::START
|
24
|
+
loop do
|
25
|
+
response.trace << state
|
26
|
+
result = send(state)
|
27
|
+
case result
|
28
|
+
when Fixnum # Response code
|
29
|
+
respond(result)
|
30
|
+
break
|
31
|
+
when Symbol # Next state
|
32
|
+
state = result
|
33
|
+
else # You bwoke it
|
34
|
+
raise InvalidResource, t('fsm_broke', :state => state, :result => result.inspect)
|
37
35
|
end
|
38
|
-
rescue MalformedRequest => malformed
|
39
|
-
Webmachine.render_error(400, request, response, :message => malformed.message)
|
40
|
-
respond(400)
|
41
|
-
rescue => e # Handle all exceptions without crashing the server
|
42
|
-
error_response(e, state)
|
43
36
|
end
|
37
|
+
rescue MalformedRequest => malformed
|
38
|
+
Webmachine.render_error(400, request, response, :message => malformed.message)
|
39
|
+
respond(400)
|
40
|
+
rescue => e # Handle all exceptions without crashing the server
|
41
|
+
error_response(e, state)
|
44
42
|
end
|
45
43
|
|
46
44
|
private
|
@@ -66,6 +64,7 @@ module Webmachine
|
|
66
64
|
Webmachine.render_error(500, request, response)
|
67
65
|
respond(500)
|
68
66
|
end
|
69
|
-
|
70
|
-
|
71
|
-
end
|
67
|
+
|
68
|
+
end # class FSM
|
69
|
+
end # module Decision
|
70
|
+
end # module Webmachine
|
@@ -1,23 +1,31 @@
|
|
1
|
+
require 'forwardable'
|
1
2
|
require 'webmachine/decision'
|
2
3
|
require 'webmachine/dispatcher/route'
|
3
4
|
|
4
5
|
module Webmachine
|
5
6
|
# Handles dispatching incoming requests to the proper registered
|
6
7
|
# resources and initializing the decision logic.
|
7
|
-
|
8
|
-
|
9
|
-
|
8
|
+
class Dispatcher
|
9
|
+
# @return [Array<Route>] the list of routes that will be
|
10
|
+
# dispatched to
|
11
|
+
# @see #add_route
|
12
|
+
attr_reader :routes
|
13
|
+
|
14
|
+
# Initialize a Dispatcher instance
|
15
|
+
def initialize
|
16
|
+
@routes = []
|
17
|
+
end
|
10
18
|
|
11
19
|
# Adds a route to the dispatch list. Routes will be matched in the
|
12
20
|
# order they are added.
|
13
21
|
# @see Route#new
|
14
|
-
def add_route(*args)
|
15
|
-
route = Route.new(*args)
|
22
|
+
def add_route(*args, &block)
|
23
|
+
route = Route.new(*args, &block)
|
16
24
|
@routes << route
|
17
25
|
route
|
18
26
|
end
|
19
27
|
alias :add :add_route
|
20
|
-
|
28
|
+
|
21
29
|
# Dispatches a request to the appropriate {Resource} in the
|
22
30
|
# dispatch list. If a matching resource is not found, a "404 Not
|
23
31
|
# Found" will be rendered.
|
@@ -37,7 +45,7 @@ module Webmachine
|
|
37
45
|
# Resets, removing all routes. Useful for testing or reloading the
|
38
46
|
# application.
|
39
47
|
def reset
|
40
|
-
@routes
|
48
|
+
@routes.clear
|
41
49
|
end
|
42
50
|
end
|
43
51
|
|
@@ -45,9 +53,9 @@ module Webmachine
|
|
45
53
|
# {Webmachine::Dispatcher} for use in adding a number of routes at
|
46
54
|
# once.
|
47
55
|
# @return [Webmachine] self
|
48
|
-
# @see Webmachine::Dispatcher
|
56
|
+
# @see Webmachine::Dispatcher#add_route
|
49
57
|
def self.routes(&block)
|
50
|
-
|
58
|
+
application.routes(&block)
|
51
59
|
self
|
52
60
|
end
|
53
|
-
end
|
61
|
+
end # module Webmachine
|
@@ -2,7 +2,7 @@ require 'webmachine/resource'
|
|
2
2
|
require 'webmachine/translation'
|
3
3
|
|
4
4
|
module Webmachine
|
5
|
-
|
5
|
+
class Dispatcher
|
6
6
|
# Pairs URIs with {Resource} classes in the {Dispatcher}. To
|
7
7
|
# create routes, use {Dispatcher#add_route}.
|
8
8
|
class Route
|
@@ -14,24 +14,60 @@ module Webmachine
|
|
14
14
|
# used to define this route (see #initialize).
|
15
15
|
attr_reader :path_spec
|
16
16
|
|
17
|
+
# @return [Array<Proc>] the list of guard blocks used to define this
|
18
|
+
# route (see #initialize).
|
19
|
+
attr_reader :guards
|
20
|
+
|
17
21
|
# When used in a path specification, will match all remaining
|
18
22
|
# segments
|
19
23
|
MATCH_ALL = '*'.freeze
|
20
24
|
|
21
25
|
# Creates a new Route that will associate a pattern to a
|
22
26
|
# {Resource}.
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
27
|
+
#
|
28
|
+
# @example Standard route
|
29
|
+
# Route.new(["*"], MyResource)
|
30
|
+
#
|
31
|
+
# @example Guarded route
|
32
|
+
# Route.new ["/notes"],
|
33
|
+
# ->(request) { request.method == "POST" },
|
34
|
+
# Resources::Note
|
35
|
+
# Route.new ["/notes"], Resources::NoteList
|
36
|
+
# Route.new ["/notes", :id], Resources::Note
|
37
|
+
# Route.new ["/notes"], Resources::Note do |req|
|
38
|
+
# req.query['foo']
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# @overload initialize(path_spec, *guards, resource, bindings = {}, &block)
|
42
|
+
# @param [Array<String|Symbol>] path_spec a list of path
|
43
|
+
# segments (String) and identifiers (Symbol) to bind.
|
44
|
+
# Strings will be simply matched for equality. Symbols in
|
45
|
+
# the path spec will be extracted into {Request#path_info} for use
|
46
|
+
# inside your {Resource}. The special segment {MATCH_ALL} will match
|
47
|
+
# all remaining segments.
|
48
|
+
# @param [Proc] guards optional guard blocks called with the request.
|
49
|
+
# @param [Class] resource the {Resource} to dispatch to
|
50
|
+
# @param [Hash] bindings additional information to add to
|
51
|
+
# {Request#path_info} when this route matches
|
52
|
+
# @yield [req] an optional guard block
|
53
|
+
# @yieldparam [Request] req the request object
|
32
54
|
# @see Dispatcher#add_route
|
33
|
-
def initialize(path_spec,
|
34
|
-
|
55
|
+
def initialize(path_spec, *args)
|
56
|
+
if args.last.is_a? Hash
|
57
|
+
bindings = args.pop
|
58
|
+
else
|
59
|
+
bindings = {}
|
60
|
+
end
|
61
|
+
|
62
|
+
resource = args.pop
|
63
|
+
guards = args
|
64
|
+
guards << Proc.new if block_given?
|
65
|
+
|
66
|
+
@path_spec = path_spec
|
67
|
+
@guards = guards
|
68
|
+
@resource = resource
|
69
|
+
@bindings = bindings
|
70
|
+
|
35
71
|
raise ArgumentError, t('not_resource_class', :class => resource.name) unless resource < Resource
|
36
72
|
end
|
37
73
|
|
@@ -40,7 +76,7 @@ module Webmachine
|
|
40
76
|
# @param [Reqeust] request the request object
|
41
77
|
def match?(request)
|
42
78
|
tokens = request.uri.path.match(/^\/(.*)/)[1].split('/')
|
43
|
-
bind(tokens, {})
|
79
|
+
bind(tokens, {}) && guards.all? { |guard| guard.call(request) }
|
44
80
|
end
|
45
81
|
|
46
82
|
# Decorates the request with information about the dispatch
|
@@ -75,7 +111,7 @@ module Webmachine
|
|
75
111
|
return false
|
76
112
|
when Symbol === spec.first
|
77
113
|
bindings[spec.first] = tokens.first
|
78
|
-
when spec.first == tokens.first
|
114
|
+
when spec.first == tokens.first
|
79
115
|
else
|
80
116
|
return false
|
81
117
|
end
|
@@ -84,6 +120,7 @@ module Webmachine
|
|
84
120
|
depth += 1
|
85
121
|
end
|
86
122
|
end
|
87
|
-
|
88
|
-
|
89
|
-
end
|
123
|
+
|
124
|
+
end # class Route
|
125
|
+
end # module Dispatcher
|
126
|
+
end # module Webmachine
|
data/lib/webmachine/errors.rb
CHANGED
data/lib/webmachine/headers.rb
CHANGED