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
data/Gemfile
CHANGED
@@ -6,20 +6,22 @@ gemspec
|
|
6
6
|
|
7
7
|
gem 'bundler'
|
8
8
|
|
9
|
-
|
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
|
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
|
-
|
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
|
-
|
12
|
+
class Mongrel < Adapter
|
13
|
+
|
13
14
|
# Starts the Mongrel adapter
|
14
|
-
def
|
15
|
-
c = Webmachine.configuration
|
15
|
+
def run
|
16
16
|
options = {
|
17
|
-
:port =>
|
18
|
-
:host =>
|
19
|
-
}.merge(
|
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
|
-
|
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
|
-
|
79
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
11
|
+
class WEBrick < Adapter
|
12
|
+
|
12
13
|
# Starts the WEBrick adapter
|
13
|
-
def
|
14
|
-
c = Webmachine.configuration
|
14
|
+
def run
|
15
15
|
options = {
|
16
|
-
:Port =>
|
17
|
-
:BindAddress =>
|
18
|
-
}.merge(
|
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
|
-
|
40
|
+
@dispatcher.dispatch(request, response)
|
36
41
|
wres.status = response.code.to_i
|
37
|
-
response.headers.each
|
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
|
-
|
84
|
-
|
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
|