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
data/Gemfile CHANGED
@@ -6,20 +6,22 @@ gemspec
6
6
 
7
7
  gem 'bundler'
8
8
 
9
- unless ENV['TRAVIS']
9
+ group :guard do
10
10
  gem 'guard-rspec'
11
+ case RbConfig::CONFIG['host_os']
12
+ when /darwin/
13
+ gem 'rb-fsevent'
14
+ # gem 'growl_notify'
15
+ gem 'growl'
16
+ when /linux/
17
+ gem 'rb-inotify'
18
+ gem 'libnotify'
19
+ end
20
+ end
11
21
 
22
+ group :docs do
12
23
  platform :mri do
13
24
  gem 'redcarpet'
14
-
15
- case RbConfig::CONFIG['host_os']
16
- when /darwin/
17
- gem 'rb-fsevent'
18
- gem 'growl_notify'
19
- when /linux/
20
- gem 'rb-inotify'
21
- gem 'libnotify'
22
- end
23
25
  end
24
26
  end
25
27
 
data/Guardfile CHANGED
@@ -1,7 +1,7 @@
1
1
  gemset = ENV['RVM_GEMSET'] || 'webmachine'
2
2
  gemset = "@#{gemset}" unless gemset.to_s == ''
3
3
 
4
- rvms = %W[ 1.8.7 1.9.2 jruby rbx ].map {|v| "#{v}#{gemset}" }
4
+ rvms = %W[ 1.8.7 1.9.2 1.9.3 jruby rbx ].map {|v| "#{v}#{gemset}" }
5
5
 
6
6
  guard 'rspec', :cli => "--color --profile", :growl => true, :rvm => rvms do
7
7
  watch(%r{^lib/webmachine/locale/.+$}) { "spec" }
data/README.md CHANGED
@@ -17,7 +17,7 @@ Webmachine has a Rack adapter -- thanks to Jamis Buck -- but when
17
17
  using it, we recommend you ensure that NO middleware is used. The
18
18
  behaviors that are encapsulated in Webmachine could be broken by
19
19
  middlewares that sit above it, and there is no way to detect them at
20
- runtime. _Caveat emptor_. That said, Webmachine should behave properly
20
+ runtime. _Caveat implementor_. That said, Webmachine should behave properly
21
21
  when given a clear stack.
22
22
 
23
23
  ## Getting Started
@@ -0,0 +1,35 @@
1
+ require 'webmachine'
2
+
3
+ class RouteDebugResource < Webmachine::Resource
4
+ def to_html
5
+ <<-HTML
6
+ <html>
7
+ <head><title>Test from Webmachine</title></head>
8
+ <body>
9
+ <h5>request.disp_path</h5>
10
+ <pre>#{request.disp_path}</pre>
11
+ <h5>request.path_info</h5>
12
+ <pre>#{request.path_info}</pre>
13
+ <h5>request.path_tokens</h5>
14
+ <pre>#{request.path_tokens}</pre>
15
+ </body>
16
+ </html>
17
+ HTML
18
+ end
19
+ end
20
+
21
+ MyApp = Webmachine::Application.new do |app|
22
+ # Configure your app like this:
23
+ app.configure do |config|
24
+ config.port = 8888
25
+ config.adapter = :WEBrick
26
+ end
27
+ # And add routes like this:
28
+ app.add_route ['fizz', :buzz, '*'], RouteDebugResource
29
+ # OR add routes this way:
30
+ app.routes do
31
+ add [:test, :foo, '*'], RouteDebugResource
32
+ end
33
+ end
34
+
35
+ MyApp.run
data/lib/webmachine.rb CHANGED
@@ -5,17 +5,18 @@ require 'webmachine/response'
5
5
  require 'webmachine/errors'
6
6
  require 'webmachine/decision'
7
7
  require 'webmachine/streaming'
8
+ require 'webmachine/adapter'
8
9
  require 'webmachine/adapters'
9
10
  require 'webmachine/dispatcher'
11
+ require 'webmachine/application'
10
12
  require 'webmachine/resource'
11
13
  require 'webmachine/version'
12
14
 
13
15
  # Webmachine is a toolkit for making well-behaved HTTP applications.
14
16
  # It is based on the Erlang library of the same name.
15
17
  module Webmachine
16
- # Starts Webmachine serving requests
18
+ # Starts Webmachine's default global Application serving requests
17
19
  def self.run
18
- configure unless configuration
19
- Adapters.const_get(configuration.adapter).run
20
+ application.run
20
21
  end
21
22
  end
@@ -0,0 +1,36 @@
1
+ module Webmachine
2
+
3
+ # The abstract class for definining a Webmachine adapter.
4
+ #
5
+ # @abstract Subclass and override {#run} to implement a custom adapter.
6
+ class Adapter
7
+
8
+ # @return [Webmachine::Configuration] the application's configuration.
9
+ attr_reader :configuration
10
+
11
+ # @return [Webmachine::Dispatcher] the application's dispatcher.
12
+ attr_reader :dispatcher
13
+
14
+ # @param [Webmachine::Configuration] configuration the application's
15
+ # configuration.
16
+ # @param [Webmachine::Dispatcher] dispatcher the application's dispatcher.
17
+ def initialize(configuration, dispatcher)
18
+ @configuration = configuration
19
+ @dispatcher = dispatcher
20
+ end
21
+
22
+ # Create a new adapter and run it.
23
+ def self.run(configuration, dispatcher)
24
+ new(configuration, dispatcher).run
25
+ end
26
+
27
+ # Start the adapter.
28
+ #
29
+ # @abstract Subclass and override {#run} to implement a custom adapter.
30
+ # @raise [NotImplementedError]
31
+ def run
32
+ raise NotImplementedError
33
+ end
34
+
35
+ end
36
+ end
@@ -9,17 +9,17 @@ require 'webmachine/chunked_body'
9
9
  module Webmachine
10
10
  module Adapters
11
11
  # Connects Webmachine to Mongrel.
12
- module Mongrel
12
+ class Mongrel < Adapter
13
+
13
14
  # Starts the Mongrel adapter
14
- def self.run
15
- c = Webmachine.configuration
15
+ def run
16
16
  options = {
17
- :port => c.port,
18
- :host => c.ip
19
- }.merge(c.adapter_options)
17
+ :port => configuration.port,
18
+ :host => configuration.ip
19
+ }.merge(configuration.adapter_options)
20
20
  config = ::Mongrel::Configurator.new(options) do
21
21
  listener do
22
- uri '/', :handler => Webmachine::Adapters::Mongrel::Handler.new
22
+ uri '/', :handler => Webmachine::Adapters::Mongrel::Handler.new(dispatcher)
23
23
  end
24
24
  trap("INT") { stop }
25
25
  run
@@ -29,6 +29,11 @@ module Webmachine
29
29
 
30
30
  # A Mongrel handler for Webmachine
31
31
  class Handler < ::Mongrel::HttpHandler
32
+ def initialize(dispatcher)
33
+ @dispatcher = dispatcher
34
+ super
35
+ end
36
+
32
37
  # Processes an individual request from Mongrel through Webmachine.
33
38
  def process(wreq, wres)
34
39
  header = Webmachine::Headers.from_cgi(wreq.params)
@@ -39,7 +44,7 @@ module Webmachine
39
44
  wreq.body || StringIO.new(''))
40
45
 
41
46
  response = Webmachine::Response.new
42
- Webmachine::Dispatcher.dispatch(request, response)
47
+ @dispatcher.dispatch(request, response)
43
48
 
44
49
  begin
45
50
  wres.status = response.code.to_i
@@ -74,7 +79,8 @@ module Webmachine
74
79
  response.body.close if response.body.respond_to? :close
75
80
  end
76
81
  end
77
- end
78
- end
79
- end
80
- end
82
+ end # class Handler
83
+
84
+ end # module Mongrel
85
+ end # module Adapters
86
+ end # module Webmachine
@@ -1,4 +1,4 @@
1
- require 'rack/request'
1
+ require 'rack'
2
2
  require 'webmachine/version'
3
3
  require 'webmachine/headers'
4
4
  require 'webmachine/request'
@@ -19,11 +19,29 @@ module Webmachine
19
19
  # # put your own Webmachine resources in another file:
20
20
  # require 'my/resources'
21
21
  #
22
- # run Webmachine::Adapters::Rack.new
22
+ # run MyApplication.adapter
23
23
  #
24
24
  # Servers like pow and unicorn will read config.ru by default and it should
25
25
  # all "just work".
26
- class Rack
26
+ #
27
+ # And for development or testing your application can be run with Rack's
28
+ # builtin Server identically to the Mongrel and WEBrick adapters with:
29
+ #
30
+ # MyApplication.run
31
+ #
32
+ class Rack < Adapter
33
+
34
+ # Start the Rack adapter
35
+ def run
36
+ options = {
37
+ :app => self,
38
+ :Port => configuration.port,
39
+ :Host => configuration.ip
40
+ }.merge(configuration.adapter_options)
41
+
42
+ ::Rack::Server.start(options)
43
+ end
44
+
27
45
  # Handles a Rack-based request.
28
46
  # @param [Hash] env the Rack environment
29
47
  def call(env)
@@ -36,7 +54,7 @@ module Webmachine
36
54
  rack_req.body)
37
55
 
38
56
  response = Webmachine::Response.new
39
- Webmachine::Dispatcher.dispatch request, response
57
+ @dispatcher.dispatch(request, response)
40
58
 
41
59
  response.headers['Server'] = [Webmachine::SERVER_STRING, "Rack/#{::Rack.version}"].join(" ")
42
60
 
@@ -45,6 +63,7 @@ module Webmachine
45
63
 
46
64
  [response.code.to_i, response.headers, body || []]
47
65
  end
48
- end
49
- end
50
- end
66
+ end # class Rack
67
+
68
+ end # module Adapters
69
+ end # module Webmachine
@@ -8,21 +8,26 @@ require 'webmachine/dispatcher'
8
8
  module Webmachine
9
9
  module Adapters
10
10
  # Connects Webmachine to WEBrick.
11
- module WEBrick
11
+ class WEBrick < Adapter
12
+
12
13
  # Starts the WEBrick adapter
13
- def self.run
14
- c = Webmachine.configuration
14
+ def run
15
15
  options = {
16
- :Port => c.port,
17
- :BindAddress => c.ip
18
- }.merge(c.adapter_options)
19
- server = Webmachine::Adapters::WEBrick::Server.new options
16
+ :Port => configuration.port,
17
+ :BindAddress => configuration.ip
18
+ }.merge(configuration.adapter_options)
19
+ server = Webmachine::Adapters::WEBrick::Server.new(dispatcher, options)
20
20
  trap("INT"){ server.shutdown }
21
21
  Thread.new { server.start }.join
22
22
  end
23
23
 
24
24
  # WEBRick::HTTPServer that is run by the WEBrick adapter.
25
25
  class Server < ::WEBrick::HTTPServer
26
+ def initialize(dispatcher, options)
27
+ @dispatcher = dispatcher
28
+ super(options)
29
+ end
30
+
26
31
  # Handles a request
27
32
  def service(wreq, wres)
28
33
  header = Webmachine::Headers.new
@@ -32,11 +37,9 @@ module Webmachine
32
37
  header,
33
38
  RequestBody.new(wreq))
34
39
  response = Webmachine::Response.new
35
- Webmachine::Dispatcher.dispatch(request, response)
40
+ @dispatcher.dispatch(request, response)
36
41
  wres.status = response.code.to_i
37
- response.headers.each do |k,v|
38
- wres[k] = v
39
- end
42
+ response.headers.each { |k,v| wres[k] = v }
40
43
  wres['Server'] = [Webmachine::SERVER_STRING, wres.config[:ServerSoftware]].join(" ")
41
44
  case response.body
42
45
  when String
@@ -51,7 +54,7 @@ module Webmachine
51
54
  end
52
55
  end
53
56
  end
54
- end
57
+ end # class Server
55
58
 
56
59
  # Wraps the WEBrick request body so that it can be passed to
57
60
  # {Request} while still lazily evaluating the body.
@@ -79,7 +82,8 @@ module Webmachine
79
82
  @request.body {|chunk| @value << chunk; yield chunk }
80
83
  end
81
84
  end
82
- end
83
- end
84
- end
85
- end
85
+ end # class RequestBody
86
+
87
+ end # module WEBrick
88
+ end # module Adapters
89
+ end # module Webmachine
@@ -0,0 +1,108 @@
1
+ require 'forwardable'
2
+ require 'webmachine/configuration'
3
+ require 'webmachine/dispatcher'
4
+
5
+ module Webmachine
6
+ # How to get your Webmachine app running:
7
+ #
8
+ # MyApp = Webmachine::Application.new do |app|
9
+ # app.routes do
10
+ # add ['*'], AssetResource
11
+ # end
12
+ #
13
+ # app.configure do |config|
14
+ # config.port = 8888
15
+ # end
16
+ # end
17
+ #
18
+ # MyApp.run
19
+ #
20
+ class Application
21
+ extend Forwardable
22
+
23
+ def_delegators :dispatcher, :add_route
24
+
25
+ # @return [Configuration] the current configuration
26
+ attr_accessor :configuration
27
+
28
+ # @return [Dispatcher] the current dispatcher
29
+ attr_reader :dispatcher
30
+
31
+ # Create an Application instance
32
+ #
33
+ # An instance of application contains Adapter configuration and
34
+ # a Dispatcher instance which can be configured with Routes.
35
+ #
36
+ # @param [Webmachine::Configuration] configuration
37
+ # a Webmachine::Configuration
38
+ #
39
+ # @yield [app]
40
+ # a block in which to configure this Application
41
+ # @yieldparam [Application]
42
+ # the Application instance being initialized
43
+ def initialize(configuration = Configuration.default)
44
+ @configuration = configuration
45
+ @dispatcher = Dispatcher.new
46
+
47
+ yield self if block_given?
48
+ end
49
+
50
+ # Starts this Application serving requests
51
+ def run
52
+ adapter.run
53
+ end
54
+
55
+ # @return an instance of the configured web-server adapter
56
+ # @see Adapters
57
+ def adapter
58
+ @adapter ||= adapter_class.new(configuration, dispatcher)
59
+ end
60
+
61
+ # @return an instance of the configured web-server adapter
62
+ # @see Adapters
63
+ def adapter_class
64
+ Adapters.const_get(configuration.adapter)
65
+ end
66
+
67
+ # Evaluates the passed block in the context of {Webmachine::Dispatcher}
68
+ # for use in adding a number of routes at once.
69
+ #
70
+ # @return [Application, Array<Route>]
71
+ # self if configuring, or an Array of Routes otherwise
72
+ #
73
+ # @see Webmachine::Dispatcher#add_route
74
+ def routes(&block)
75
+ if block_given?
76
+ dispatcher.instance_eval(&block)
77
+ self
78
+ else
79
+ dispatcher.routes
80
+ end
81
+ end
82
+
83
+ # Configure the web server adapter via the passed block
84
+ #
85
+ # Returns the receiver so you can chain it with Application#run
86
+ #
87
+ # @yield [config]
88
+ # a block in which to set configuration values
89
+ # @yieldparam [Configuration]
90
+ # config the Configuration instance
91
+ #
92
+ # @return [Application] self
93
+ def configure
94
+ yield configuration if block_given?
95
+ self
96
+ end
97
+
98
+ # @return [Configuration] the current configuration
99
+ def configuration
100
+ @configuration ||= Configuration.default
101
+ end
102
+ end
103
+
104
+ # @return [Application] the default global Application
105
+ def self.application
106
+ @application ||= Application.new
107
+ end
108
+ end
@@ -39,5 +39,5 @@ module Webmachine
39
39
  end
40
40
  yield(FINAL_CHUNK)
41
41
  end
42
- end
43
- end
42
+ end # class ChunkedBody
43
+ end # module Webmachine