webmachine 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/Gemfile +12 -10
  2. data/Guardfile +1 -1
  3. data/README.md +1 -1
  4. data/examples/application.rb +35 -0
  5. data/lib/webmachine.rb +4 -3
  6. data/lib/webmachine/adapter.rb +36 -0
  7. data/lib/webmachine/adapters/mongrel.rb +18 -12
  8. data/lib/webmachine/adapters/rack.rb +26 -7
  9. data/lib/webmachine/adapters/webrick.rb +20 -16
  10. data/lib/webmachine/application.rb +108 -0
  11. data/lib/webmachine/chunked_body.rb +2 -2
  12. data/lib/webmachine/configuration.rb +24 -14
  13. data/lib/webmachine/decision/conneg.rb +9 -10
  14. data/lib/webmachine/decision/flow.rb +25 -28
  15. data/lib/webmachine/decision/fsm.rb +21 -22
  16. data/lib/webmachine/decision/helpers.rb +3 -3
  17. data/lib/webmachine/dispatcher.rb +18 -10
  18. data/lib/webmachine/dispatcher/route.rb +54 -17
  19. data/lib/webmachine/errors.rb +1 -1
  20. data/lib/webmachine/headers.rb +2 -2
  21. data/lib/webmachine/media_type.rb +2 -2
  22. data/lib/webmachine/request.rb +78 -3
  23. data/lib/webmachine/resource.rb +3 -2
  24. data/lib/webmachine/resource/authentication.rb +4 -3
  25. data/lib/webmachine/resource/callbacks.rb +4 -3
  26. data/lib/webmachine/resource/encodings.rb +4 -3
  27. data/lib/webmachine/response.rb +3 -2
  28. data/lib/webmachine/streaming.rb +4 -4
  29. data/lib/webmachine/version.rb +1 -1
  30. data/spec/webmachine/adapter_spec.rb +40 -0
  31. data/spec/webmachine/adapters/mongrel_spec.rb +22 -0
  32. data/spec/webmachine/adapters/rack_spec.rb +34 -8
  33. data/spec/webmachine/adapters/webrick_spec.rb +18 -0
  34. data/spec/webmachine/application_spec.rb +73 -0
  35. data/spec/webmachine/dispatcher/route_spec.rb +59 -2
  36. data/spec/webmachine/dispatcher_spec.rb +17 -5
  37. data/spec/webmachine/request_spec.rb +158 -1
  38. data/webmachine.gemspec +6 -27
  39. metadata +304 -112
  40. 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
- class << self
14
- # @return [Configuration] the current configuration
15
- attr_accessor :configuration
13
+ # @return [Configuration] the default configuration
14
+ def Configuration.default
15
+ new("0.0.0.0", 8080, :WEBrick, {})
16
16
  end
17
17
 
18
- # Sets configuration for the web server via the passed
19
- # block. Returns Webmachine so you can chain it with
20
- # Webmachine.run.
21
- # @yield [config] a block in which to set configuration values
22
- # @yieldparam [Configuration] config the Configuration instance
23
- # @return [Webmachine]
24
- def self.configure
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
- end
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
- begin
230
- mt = MediaType.parse(c)
231
- q = mt.params.delete('q') || 1.0
232
- add(q.to_f, mt)
233
- rescue ArgumentError
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
- end
239
- end
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.method == "OPTIONS"
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
- begin
258
- date = Time.httpdate(request.if_unmodified_since)
259
- metadata['If-Unmodified-Since'] = date
260
- rescue ArgumentError
261
- :i12
262
- else
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.method == "PUT" ? :i4 : :k7
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
- %w{GET HEAD}.include?(request.method) ? 304 : 412
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.method == "POST" ? :m7 : 404
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
- begin
355
- date = Time.httpdate(request.if_modified_since)
356
- metadata['If-Modified-Since'] = date
357
- rescue ArgumentError
358
- :m16
359
- else
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.method == "POST" ? :n5 : 410
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.method == "DELETE" ? :m20 : :n16
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.method == "POST" ? :n11 : :o16
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.method == "PUT" ? :o14 : :o18
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.method =~ /^(GET|HEAD)$/
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
- end
505
- end
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
- begin
24
- state = Flow::START
25
- loop do
26
- response.trace << state
27
- result = send(state)
28
- case result
29
- when Fixnum # Response code
30
- respond(result)
31
- break
32
- when Symbol # Next state
33
- state = result
34
- else # You bwoke it
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
- end
70
- end
71
- end
67
+
68
+ end # class FSM
69
+ end # module Decision
70
+ end # module Webmachine
@@ -99,6 +99,6 @@ module Webmachine
99
99
  response.headers['Last-Modified'] = modified.httpdate
100
100
  end
101
101
  end
102
- end
103
- end
104
- end
102
+ end # module Helpers
103
+ end # module Decision
104
+ 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
- module Dispatcher
8
- extend self
9
- @routes = []
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.add_route
56
+ # @see Webmachine::Dispatcher#add_route
49
57
  def self.routes(&block)
50
- Dispatcher.module_eval(&block)
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
- module Dispatcher
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
- # @param [Array<String|Symbol>] path_spec a list of path
24
- # segments (String) and identifiers (Symbol) to bind.
25
- # Strings will be simply matched for equality. Symbols in
26
- # the path spec will be extracted into {Request#path_info} for use
27
- # inside your {Resource}. The special segment {MATCH_ALL} will match
28
- # all remaining segments.
29
- # @param [Class] resource the {Resource} to dispatch to
30
- # @param [Hash] bindings additional information to add to
31
- # {Request#path_info} when this route matches
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, resource, bindings={})
34
- @path_spec, @resource, @bindings = path_spec, resource, bindings
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
- end
88
- end
89
- end
123
+
124
+ end # class Route
125
+ end # module Dispatcher
126
+ end # module Webmachine
@@ -35,4 +35,4 @@ module Webmachine
35
35
  # the case where a request header is improperly formed. Raising this
36
36
  # exception will result in a 400 response.
37
37
  class MalformedRequest < Error; end
38
- end
38
+ end # module Webmachine
@@ -36,5 +36,5 @@ module Webmachine
36
36
  def transform_key(key)
37
37
  key.to_s.downcase
38
38
  end
39
- end
40
- end
39
+ end # class Headers
40
+ end # module Webmachine