webmachine 0.3.0 → 0.4.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.
- 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