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
data/lib/webmachine/request.rb
CHANGED
@@ -9,6 +9,8 @@ module Webmachine
|
|
9
9
|
attr_reader :method, :uri, :headers, :body
|
10
10
|
attr_accessor :disp_path, :path_info, :path_tokens
|
11
11
|
|
12
|
+
STANDARD_HTTP_METHODS = %w[GET HEAD POST PUT DELETE TRACE CONNECT OPTIONS]
|
13
|
+
|
12
14
|
# @param [String] method the HTTP request method
|
13
15
|
# @param [URI] uri the requested URI, including host, scheme and
|
14
16
|
# port
|
@@ -55,12 +57,85 @@ module Webmachine
|
|
55
57
|
def query
|
56
58
|
unless @query
|
57
59
|
@query = {}
|
58
|
-
uri.query.split(/&/).each do |kv|
|
60
|
+
(uri.query || '').split(/&/).each do |kv|
|
59
61
|
k, v = CGI.unescape(kv).split(/=/)
|
60
62
|
@query[k] = v if k && v
|
61
63
|
end
|
62
64
|
end
|
63
65
|
@query
|
64
66
|
end
|
65
|
-
|
66
|
-
|
67
|
+
|
68
|
+
# Is this an HTTPS request?
|
69
|
+
#
|
70
|
+
# @return [Boolean]
|
71
|
+
# true if this request was made via HTTPS
|
72
|
+
def https?
|
73
|
+
uri.scheme == "https"
|
74
|
+
end
|
75
|
+
|
76
|
+
# Is this a GET request?
|
77
|
+
#
|
78
|
+
# @return [Boolean]
|
79
|
+
# true if this request was made with the GET method
|
80
|
+
def get?
|
81
|
+
method == "GET"
|
82
|
+
end
|
83
|
+
|
84
|
+
# Is this a HEAD request?
|
85
|
+
#
|
86
|
+
# @return [Boolean]
|
87
|
+
# true if this request was made with the HEAD method
|
88
|
+
def head?
|
89
|
+
method == "HEAD"
|
90
|
+
end
|
91
|
+
|
92
|
+
# Is this a POST request?
|
93
|
+
#
|
94
|
+
# @return [Boolean]
|
95
|
+
# true if this request was made with the GET method
|
96
|
+
def post?
|
97
|
+
method == "POST"
|
98
|
+
end
|
99
|
+
|
100
|
+
# Is this a PUT request?
|
101
|
+
#
|
102
|
+
# @return [Boolean]
|
103
|
+
# true if this request was made with the PUT method
|
104
|
+
def put?
|
105
|
+
method == "PUT"
|
106
|
+
end
|
107
|
+
|
108
|
+
# Is this a DELETE request?
|
109
|
+
#
|
110
|
+
# @return [Boolean]
|
111
|
+
# true if this request was made with the DELETE method
|
112
|
+
def delete?
|
113
|
+
method == "DELETE"
|
114
|
+
end
|
115
|
+
|
116
|
+
# Is this a TRACE request?
|
117
|
+
#
|
118
|
+
# @return [Boolean]
|
119
|
+
# true if this request was made with the TRACE method
|
120
|
+
def trace?
|
121
|
+
method == "TRACE"
|
122
|
+
end
|
123
|
+
|
124
|
+
# Is this a CONNECT request?
|
125
|
+
#
|
126
|
+
# @return [Boolean]
|
127
|
+
# true if this request was made with the CONNECT method
|
128
|
+
def connect?
|
129
|
+
method == "CONNECT"
|
130
|
+
end
|
131
|
+
|
132
|
+
# Is this an OPTIONS request?
|
133
|
+
#
|
134
|
+
# @return [Boolean]
|
135
|
+
# true if this request was made with the OPTIONS method
|
136
|
+
def options?
|
137
|
+
method == "OPTIONS"
|
138
|
+
end
|
139
|
+
|
140
|
+
end # class Request
|
141
|
+
end # module Webmachine
|
data/lib/webmachine/resource.rb
CHANGED
data/lib/webmachine/response.rb
CHANGED
data/lib/webmachine/streaming.rb
CHANGED
@@ -26,7 +26,7 @@ module Webmachine
|
|
26
26
|
yield resource.send(encoder, resource.send(charsetter, block.to_s))
|
27
27
|
end
|
28
28
|
end
|
29
|
-
end
|
29
|
+
end # class EnumerableEncoder
|
30
30
|
|
31
31
|
# Implements a streaming encoder for callable bodies, such as
|
32
32
|
# Proc. (essentially futures)
|
@@ -44,7 +44,7 @@ module Webmachine
|
|
44
44
|
def to_proc
|
45
45
|
method(:call).to_proc
|
46
46
|
end
|
47
|
-
end
|
47
|
+
end # class CallableEncoder
|
48
48
|
|
49
49
|
# Implements a streaming encoder for Fibers with the same API as the
|
50
50
|
# EnumerableEncoder. This will resume the Fiber until it terminates
|
@@ -59,5 +59,5 @@ module Webmachine
|
|
59
59
|
yield resource.send(encoder, resource.send(charsetter, chunk.to_s))
|
60
60
|
end
|
61
61
|
end
|
62
|
-
end
|
63
|
-
end
|
62
|
+
end # class FiberEncoder
|
63
|
+
end # module Webmachine
|
data/lib/webmachine/version.rb
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Webmachine::Adapter do
|
4
|
+
let(:configuration) { Webmachine::Configuration.default }
|
5
|
+
let(:dispatcher) { Webmachine::Dispatcher.new }
|
6
|
+
let(:adapter) do
|
7
|
+
described_class.new(configuration, dispatcher)
|
8
|
+
end
|
9
|
+
|
10
|
+
describe "#initialize" do
|
11
|
+
it "stores the provided configuration" do
|
12
|
+
adapter.configuration.should eql configuration
|
13
|
+
end
|
14
|
+
|
15
|
+
it "stores the provided dispatcher" do
|
16
|
+
adapter.dispatcher.should eql dispatcher
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe ".run" do
|
21
|
+
it "creates a new adapter and runs it" do
|
22
|
+
adapter = mock(described_class)
|
23
|
+
|
24
|
+
described_class.should_receive(:new).
|
25
|
+
with(configuration, dispatcher).
|
26
|
+
and_return(adapter)
|
27
|
+
|
28
|
+
adapter.should_receive(:run)
|
29
|
+
|
30
|
+
described_class.run(configuration, dispatcher)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe "#run" do
|
35
|
+
it "raises a NotImplementedError" do
|
36
|
+
lambda { adapter.run }.should raise_exception(NotImplementedError)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
begin
|
4
|
+
describe Webmachine::Adapters::Mongrel do
|
5
|
+
let(:configuration) { Webmachine::Configuration.default }
|
6
|
+
let(:dispatcher) { Webmachine::Dispatcher.new }
|
7
|
+
|
8
|
+
let(:adapter) do
|
9
|
+
described_class.new(configuration, dispatcher)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "inherits from Webmachine::Adapter" do
|
13
|
+
adapter.should be_a_kind_of(Webmachine::Adapter)
|
14
|
+
end
|
15
|
+
|
16
|
+
it "implements #run" do
|
17
|
+
described_class.instance_methods(false).map {|m| m.to_sym }.should include :run
|
18
|
+
end
|
19
|
+
end
|
20
|
+
rescue LoadError
|
21
|
+
warn "Platform is #{RUBY_PLATFORM}: skipping mongrel adapter spec."
|
22
|
+
end
|
@@ -11,8 +11,6 @@ module Test
|
|
11
11
|
end
|
12
12
|
|
13
13
|
describe Webmachine::Adapters::Rack do
|
14
|
-
let(:adapter) { described_class }
|
15
|
-
|
16
14
|
let(:env) do
|
17
15
|
{ "REQUEST_METHOD" => "GET",
|
18
16
|
"SCRIPT_NAME" => "",
|
@@ -29,33 +27,61 @@ describe Webmachine::Adapters::Rack do
|
|
29
27
|
"rack.run_once" => false }
|
30
28
|
end
|
31
29
|
|
30
|
+
let(:configuration) { Webmachine::Configuration.new('0.0.0.0', 8080, :Rack, {}) }
|
31
|
+
let(:dispatcher) { Webmachine::Dispatcher.new }
|
32
|
+
let(:adapter) do
|
33
|
+
described_class.new(configuration, dispatcher)
|
34
|
+
end
|
35
|
+
|
36
|
+
subject { adapter }
|
37
|
+
|
32
38
|
before do
|
33
|
-
|
34
|
-
|
39
|
+
dispatcher.add_route ['test'], Test::Resource
|
40
|
+
end
|
41
|
+
|
42
|
+
it "inherits from Webmachine::Adapter" do
|
43
|
+
adapter.should be_a_kind_of(Webmachine::Adapter)
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "#run" do
|
47
|
+
before do
|
48
|
+
configuration.adapter_options[:debug] = true
|
49
|
+
end
|
50
|
+
|
51
|
+
it "starts a rack server with the correct options" do
|
52
|
+
Rack::Server.should_receive(:start).with(
|
53
|
+
:app => adapter,
|
54
|
+
:Port => configuration.port,
|
55
|
+
:Host => configuration.ip,
|
56
|
+
:debug => true
|
57
|
+
)
|
58
|
+
|
59
|
+
adapter.run
|
60
|
+
end
|
35
61
|
end
|
36
62
|
|
37
63
|
it "should proxy request to webmachine" do
|
38
|
-
code, headers, body =
|
64
|
+
code, headers, body = subject.call(env)
|
39
65
|
code.should == 200
|
40
66
|
headers["Content-Type"].should == "text/html"
|
41
67
|
body.should include "<html><body>testing</body></html>"
|
42
68
|
end
|
43
69
|
|
44
70
|
it "should set Server header" do
|
45
|
-
code, headers, body =
|
71
|
+
code, headers, body = subject.call(env)
|
46
72
|
headers.should have_key "Server"
|
47
73
|
end
|
48
74
|
|
49
75
|
it "should handle non-success correctly" do
|
50
76
|
env["PATH_INFO"] = "/missing"
|
51
|
-
code, headers, body =
|
77
|
+
code, headers, body = subject.call(env)
|
52
78
|
code.should == 404
|
53
79
|
headers["Content-Type"].should == "text/html"
|
54
80
|
end
|
55
81
|
|
56
82
|
it "should handle empty bodies correctly" do
|
57
83
|
env["HTTP_ACCEPT"] = "application/json"
|
58
|
-
code, headers, body =
|
84
|
+
code, headers, body = subject.call(env)
|
59
85
|
code.should == 406
|
60
86
|
headers.should_not have_key "Content-Type"
|
61
87
|
headers.should_not have_key "Content-Length"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Webmachine::Adapters::WEBrick do
|
4
|
+
let(:configuration) { Webmachine::Configuration.default }
|
5
|
+
let(:dispatcher) { Webmachine::Dispatcher.new }
|
6
|
+
|
7
|
+
let(:adapter) do
|
8
|
+
described_class.new(configuration, dispatcher)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "inherits from Webmachine::Adapter" do
|
12
|
+
adapter.should be_a_kind_of(Webmachine::Adapter)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "implements #run" do
|
16
|
+
described_class.instance_methods(false).map {|m| m.to_sym }.should include :run
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Webmachine::Application do
|
4
|
+
let(:application) { described_class.new }
|
5
|
+
let(:test_resource) { Class.new(Webmachine::Resource) }
|
6
|
+
|
7
|
+
it "accepts a Configuration when initialized" do
|
8
|
+
config = Webmachine::Configuration.new('1.1.1.1', 9999, :Mongrel, {})
|
9
|
+
described_class.new(config).configuration.should be(config)
|
10
|
+
end
|
11
|
+
|
12
|
+
it "is yielded into a block provided during initialization" do
|
13
|
+
yielded_app = nil
|
14
|
+
described_class.new do |app|
|
15
|
+
app.should be_kind_of(Webmachine::Application)
|
16
|
+
yielded_app = app
|
17
|
+
end.should be(yielded_app)
|
18
|
+
end
|
19
|
+
|
20
|
+
it "is initialized with the default Configration if none is given" do
|
21
|
+
application.configuration.should eq(Webmachine::Configuration.default)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "returns the receiver from the configure call so you can chain it" do
|
25
|
+
application.configure { |c| }.should equal(application)
|
26
|
+
end
|
27
|
+
|
28
|
+
it "is configurable" do
|
29
|
+
application.configure do |config|
|
30
|
+
config.should be_kind_of(Webmachine::Configuration)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
it "is initialized with an empty Dispatcher" do
|
35
|
+
application.dispatcher.routes.should be_empty
|
36
|
+
end
|
37
|
+
|
38
|
+
it "can have routes added" do
|
39
|
+
route = nil
|
40
|
+
resource = test_resource # overcome instance_eval :/
|
41
|
+
|
42
|
+
application.routes.should be_empty
|
43
|
+
|
44
|
+
application.routes do
|
45
|
+
route = add ['*'], resource
|
46
|
+
end
|
47
|
+
|
48
|
+
route.should be_kind_of(Webmachine::Dispatcher::Route)
|
49
|
+
application.routes.should eq([route])
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#adapter" do
|
53
|
+
let(:adapter_class) { application.adapter_class }
|
54
|
+
|
55
|
+
it "returns an instance of it's adapter class" do
|
56
|
+
application.adapter.should be_an_instance_of(adapter_class)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "is memoized" do
|
60
|
+
application.adapter.should eql application.adapter
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
it "can be run" do
|
65
|
+
application.adapter.should_receive(:run)
|
66
|
+
application.run
|
67
|
+
end
|
68
|
+
|
69
|
+
it "can be queried about its configured adapter" do
|
70
|
+
expected = Webmachine::Adapters.const_get(application.configuration.adapter)
|
71
|
+
application.adapter_class.should equal(expected)
|
72
|
+
end
|
73
|
+
end
|
@@ -1,7 +1,9 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Webmachine::Dispatcher::Route do
|
4
|
-
let(:
|
4
|
+
let(:method) { "GET" }
|
5
|
+
let(:uri) { URI.parse("http://localhost:8080/") }
|
6
|
+
let(:request){ Webmachine::Request.new(method, uri, Webmachine::Headers.new, "") }
|
5
7
|
let(:resource){ Class.new(Webmachine::Resource) }
|
6
8
|
|
7
9
|
matcher :match_route do |*expected|
|
@@ -37,6 +39,61 @@ describe Webmachine::Dispatcher::Route do
|
|
37
39
|
it { should_not match_route [] }
|
38
40
|
it { should_not match_route %w{bar *} }
|
39
41
|
end
|
42
|
+
|
43
|
+
context "with a guard on the request method" do
|
44
|
+
let(:uri){ URI.parse("http://localhost:8080/notes") }
|
45
|
+
let(:route) do
|
46
|
+
described_class.new(
|
47
|
+
["notes"],
|
48
|
+
lambda { |request| request.method == "POST" },
|
49
|
+
resource
|
50
|
+
)
|
51
|
+
end
|
52
|
+
subject { route }
|
53
|
+
|
54
|
+
context "when guard passes" do
|
55
|
+
let(:method){ "POST" }
|
56
|
+
it { should be_match(request) }
|
57
|
+
|
58
|
+
context "but the path match fails" do
|
59
|
+
let(:uri){ URI.parse("http://localhost:8080/other") }
|
60
|
+
it { should_not be_match(request) }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
context "when guard fails" do
|
65
|
+
let(:method) { "GET" }
|
66
|
+
it { should_not be_match(request) }
|
67
|
+
end
|
68
|
+
|
69
|
+
context "when the guard responds to #call" do
|
70
|
+
let(:guard_class) do
|
71
|
+
Class.new do
|
72
|
+
def initialize(method)
|
73
|
+
@method = method
|
74
|
+
end
|
75
|
+
|
76
|
+
def call(request)
|
77
|
+
request.method == @method
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
let(:route) do
|
83
|
+
described_class.new(["notes"], guard_class.new("POST"), resource)
|
84
|
+
end
|
85
|
+
|
86
|
+
context "when the guard passes" do
|
87
|
+
let(:method){ "POST" }
|
88
|
+
it { should be_match(request) }
|
89
|
+
end
|
90
|
+
|
91
|
+
context "when the guard fails" do
|
92
|
+
# let(:method){ "GET" }
|
93
|
+
it { should_not be_match(request) }
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
40
97
|
end
|
41
98
|
|
42
99
|
context "applying bindings" do
|
@@ -66,7 +123,7 @@ describe Webmachine::Dispatcher::Route do
|
|
66
123
|
|
67
124
|
context "with a splat" do
|
68
125
|
subject { described_class.new(['*'], resource) }
|
69
|
-
|
126
|
+
|
70
127
|
it "should assign empty path tokens" do
|
71
128
|
request.path_tokens.should == []
|
72
129
|
end
|