webmachine 1.1.0 → 1.2.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 +5 -4
- data/README.md +25 -2
- data/examples/debugger.rb +7 -0
- data/examples/logging.rb +41 -0
- data/lib/webmachine/adapters.rb +1 -1
- data/lib/webmachine/adapters/hatetepe.rb +5 -1
- data/lib/webmachine/adapters/lazy_request_body.rb +7 -7
- data/lib/webmachine/adapters/mongrel.rb +27 -12
- data/lib/webmachine/adapters/rack.rb +42 -6
- data/lib/webmachine/adapters/reel.rb +91 -23
- data/lib/webmachine/adapters/webrick.rb +12 -6
- data/lib/webmachine/application.rb +1 -0
- data/lib/webmachine/cookie.rb +1 -1
- data/lib/webmachine/dispatcher.rb +7 -1
- data/lib/webmachine/events.rb +179 -0
- data/lib/webmachine/events/instrumented_event.rb +19 -0
- data/lib/webmachine/response.rb +1 -1
- data/lib/webmachine/streaming/io_encoder.rb +5 -1
- data/lib/webmachine/trace.rb +18 -0
- data/lib/webmachine/trace/fsm.rb +4 -1
- data/lib/webmachine/trace/listener.rb +12 -0
- data/lib/webmachine/version.rb +1 -1
- data/spec/spec_helper.rb +11 -0
- data/spec/support/adapter_lint.rb +125 -0
- data/spec/support/test_resource.rb +73 -0
- data/spec/webmachine/adapters/hatetepe_spec.rb +2 -2
- data/spec/webmachine/adapters/mongrel_spec.rb +6 -51
- data/spec/webmachine/adapters/rack_spec.rb +22 -155
- data/spec/webmachine/adapters/reel_spec.rb +59 -7
- data/spec/webmachine/adapters/webrick_spec.rb +7 -13
- data/spec/webmachine/cookie_spec.rb +1 -1
- data/spec/webmachine/decision/helpers_spec.rb +10 -0
- data/spec/webmachine/events_spec.rb +58 -0
- data/spec/webmachine/request_spec.rb +41 -0
- data/webmachine.gemspec +1 -1
- metadata +63 -24
@@ -0,0 +1,73 @@
|
|
1
|
+
module Test
|
2
|
+
class Resource < Webmachine::Resource
|
3
|
+
def allowed_methods
|
4
|
+
["GET", "PUT", "POST"]
|
5
|
+
end
|
6
|
+
|
7
|
+
def content_types_accepted
|
8
|
+
[
|
9
|
+
["test/request.stringbody", :from_string],
|
10
|
+
["test/request.enumbody", :from_enum]
|
11
|
+
]
|
12
|
+
end
|
13
|
+
|
14
|
+
def content_types_provided
|
15
|
+
[
|
16
|
+
["test/response.stringbody", :to_string],
|
17
|
+
["test/response.enumbody", :to_enum],
|
18
|
+
["test/response.procbody", :to_proc],
|
19
|
+
["test/response.fiberbody", :to_fiber],
|
20
|
+
["test/response.iobody", :to_io],
|
21
|
+
["test/response.cookies", :to_cookies]
|
22
|
+
]
|
23
|
+
end
|
24
|
+
|
25
|
+
def from_string
|
26
|
+
response.body = "String: #{request.body.to_s}"
|
27
|
+
end
|
28
|
+
|
29
|
+
def from_enum
|
30
|
+
response.body = "Enum: "
|
31
|
+
request.body.each do |part|
|
32
|
+
response.body += part
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Response intentionally left blank to test 204 support
|
37
|
+
def process_post
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def to_string
|
42
|
+
"String response body"
|
43
|
+
end
|
44
|
+
|
45
|
+
def to_enum
|
46
|
+
["Enumerable ", "response " "body"]
|
47
|
+
end
|
48
|
+
|
49
|
+
def to_proc
|
50
|
+
Proc.new { "Proc response body" }
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_fiber
|
54
|
+
Fiber.new do
|
55
|
+
Fiber.yield "Fiber "
|
56
|
+
Fiber.yield "response "
|
57
|
+
"body"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def to_io
|
62
|
+
StringIO.new("IO response body")
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_cookies
|
66
|
+
response.set_cookie("cookie", "monster")
|
67
|
+
response.set_cookie("rodeo", "clown")
|
68
|
+
# FIXME: Mongrel/WEBrick fail if this method returns nil
|
69
|
+
# Might be a net/http issue. Is this a bug?
|
70
|
+
request.cookies["echo"] || ""
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -11,7 +11,7 @@ examples = proc do
|
|
11
11
|
|
12
12
|
describe "#run" do
|
13
13
|
it "starts a server" do
|
14
|
-
Hatetepe::Server.should_receive(:start).with(adapter.options) {
|
14
|
+
Hatetepe::Server.should_receive(:start).with(adapter.options) { adapter.shutdown }
|
15
15
|
adapter.run
|
16
16
|
end
|
17
17
|
end
|
@@ -55,7 +55,7 @@ examples = proc do
|
|
55
55
|
end
|
56
56
|
|
57
57
|
def enum_to_s(enum)
|
58
|
-
|
58
|
+
enum.to_enum.to_a.join
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
@@ -1,59 +1,14 @@
|
|
1
1
|
require "spec_helper"
|
2
|
+
require "support/adapter_lint"
|
2
3
|
|
3
4
|
begin
|
4
5
|
describe Webmachine::Adapters::Mongrel do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
-
adapter.should respond_to(:run)
|
18
|
-
end
|
19
|
-
|
20
|
-
describe "request handler" do
|
21
|
-
let(:request_params) do
|
22
|
-
{
|
23
|
-
"REQUEST_METHOD" => "GET",
|
24
|
-
"REQUEST_URI" => "http://www.example.com/test?query=string"
|
25
|
-
}
|
6
|
+
it_should_behave_like :adapter_lint do
|
7
|
+
it "should set Server header" do
|
8
|
+
response = client.request(Net::HTTP::Get.new("/test"))
|
9
|
+
response["Server"].should match(/Webmachine/)
|
10
|
+
response["Server"].should match(/Mongrel/)
|
26
11
|
end
|
27
|
-
let(:request_body) { StringIO.new("Hello, World!") }
|
28
|
-
let(:mongrel_request) { stub(:params => request_params, :body => request_body) }
|
29
|
-
let(:mongrel_response) { stub.as_null_object }
|
30
|
-
|
31
|
-
subject { Webmachine::Adapters::Mongrel::Handler.new(dispatcher) }
|
32
|
-
|
33
|
-
it "should build a string-like request body" do
|
34
|
-
dispatcher.should_receive(:dispatch) do |request, response|
|
35
|
-
request.body.to_s.should eq("Hello, World!")
|
36
|
-
end
|
37
|
-
subject.process(mongrel_request, mongrel_response)
|
38
|
-
end
|
39
|
-
|
40
|
-
it "should build an enumerable request body" do
|
41
|
-
chunks = []
|
42
|
-
dispatcher.should_receive(:dispatch) do |request, response|
|
43
|
-
request.body.each { |chunk| chunks << chunk }
|
44
|
-
end
|
45
|
-
subject.process(mongrel_request, mongrel_response)
|
46
|
-
chunks.join.should eq("Hello, World!")
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
it "can run" do
|
51
|
-
# Prevent webserver thread from taking over
|
52
|
-
Thread.stub!(:new).and_return(stub(:join => nil))
|
53
|
-
|
54
|
-
expect {
|
55
|
-
adapter.run
|
56
|
-
}.not_to raise_error
|
57
12
|
end
|
58
13
|
end
|
59
14
|
rescue LoadError
|
@@ -1,169 +1,36 @@
|
|
1
|
-
require '
|
1
|
+
require 'webmachine/adapter'
|
2
2
|
require 'webmachine/adapters/rack'
|
3
|
-
require '
|
4
|
-
require '
|
5
|
-
require 'rack/lint'
|
6
|
-
|
7
|
-
module Test
|
8
|
-
class Resource < Webmachine::Resource
|
9
|
-
def allowed_methods
|
10
|
-
["GET", "PUT", "POST"]
|
11
|
-
end
|
12
|
-
|
13
|
-
def content_types_accepted
|
14
|
-
[["application/json", :from_json]]
|
15
|
-
end
|
16
|
-
|
17
|
-
def content_types_provided
|
18
|
-
[
|
19
|
-
["text/html", :to_html],
|
20
|
-
["application/vnd.webmachine.streaming+enum", :to_enum_stream],
|
21
|
-
["application/vnd.webmachine.streaming+proc", :to_proc_stream]
|
22
|
-
]
|
23
|
-
end
|
24
|
-
|
25
|
-
def process_post
|
26
|
-
true
|
27
|
-
end
|
28
|
-
|
29
|
-
def to_html
|
30
|
-
response.set_cookie('cookie', 'monster')
|
31
|
-
response.set_cookie('rodeo', 'clown')
|
32
|
-
"<html><body>#{request.cookies['string'] || 'testing'}</body></html>"
|
33
|
-
end
|
34
|
-
|
35
|
-
def to_enum_stream
|
36
|
-
%w{Hello, World!}
|
37
|
-
end
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'support/adapter_lint'
|
38
5
|
|
39
|
-
|
40
|
-
|
6
|
+
describe Webmachine::Adapters::Rack do
|
7
|
+
it_should_behave_like :adapter_lint do
|
8
|
+
it "should set Server header" do
|
9
|
+
response = client.request(Net::HTTP::Get.new("/test"))
|
10
|
+
response["Server"].should match(/Webmachine/)
|
11
|
+
response["Server"].should match(/Rack/)
|
41
12
|
end
|
42
|
-
|
43
|
-
def from_json; end
|
44
13
|
end
|
45
14
|
end
|
46
15
|
|
47
|
-
describe Webmachine::Adapters::Rack do
|
48
|
-
|
49
|
-
|
50
|
-
let(:configuration) { Webmachine::Configuration.new('0.0.0.0', 8080, :Rack, {}) }
|
51
|
-
let(:dispatcher) { Webmachine::Dispatcher.new }
|
52
|
-
let(:adapter) do
|
53
|
-
described_class.new(configuration, dispatcher)
|
54
|
-
end
|
55
|
-
let(:app) { Rack::Lint.new(adapter) }
|
56
|
-
|
57
|
-
before do
|
58
|
-
dispatcher.add_route ['test'], Test::Resource
|
59
|
-
end
|
60
|
-
|
61
|
-
it "inherits from Webmachine::Adapter" do
|
62
|
-
adapter.should be_a_kind_of(Webmachine::Adapter)
|
63
|
-
end
|
64
|
-
|
65
|
-
describe "#run" do
|
66
|
-
before do
|
67
|
-
configuration.adapter_options[:debug] = true
|
68
|
-
end
|
69
|
-
|
70
|
-
it "starts a rack server with the correct options" do
|
71
|
-
Rack::Server.should_receive(:start).with(
|
72
|
-
:app => adapter,
|
73
|
-
:Port => configuration.port,
|
74
|
-
:Host => configuration.ip,
|
75
|
-
:debug => true
|
76
|
-
)
|
16
|
+
describe Webmachine::Adapters::Rack::RackResponse do
|
17
|
+
context "on Rack < 1.5 release" do
|
18
|
+
before { Rack.stub(:release => "1.4") }
|
77
19
|
|
78
|
-
|
20
|
+
it "should add Content-Type header on not acceptable response" do
|
21
|
+
rack_response = described_class.new(double(:body), 406, {})
|
22
|
+
rack_status, rack_headers, rack_body = rack_response.finish
|
23
|
+
rack_headers.should have_key("Content-Type")
|
79
24
|
end
|
80
25
|
end
|
81
26
|
|
82
|
-
|
83
|
-
|
84
|
-
last_response.status.should == 200
|
85
|
-
last_response.original_headers["Content-Type"].should == "text/html"
|
86
|
-
last_response.body.should == "<html><body>testing</body></html>"
|
87
|
-
end
|
88
|
-
|
89
|
-
it "should build a string-like request body" do
|
90
|
-
dispatcher.should_receive(:dispatch) do |request, response|
|
91
|
-
request.body.to_s.should eq("Hello, World!")
|
92
|
-
response.headers["Content-Type"] = "text/plain"
|
93
|
-
end
|
94
|
-
request "/test", :method => "GET", :input => "Hello, World!"
|
95
|
-
end
|
27
|
+
context "on Rack >= 1.5 release" do
|
28
|
+
before { Rack.stub(:release => "1.5") }
|
96
29
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
response.headers["Content-Type"] = "text/plain"
|
30
|
+
it "should not add Content-Type header on not acceptable response" do
|
31
|
+
rack_response = described_class.new(double(:body), 406, {})
|
32
|
+
rack_status, rack_headers, rack_body = rack_response.finish
|
33
|
+
rack_headers.should_not have_key("Content-Type")
|
102
34
|
end
|
103
|
-
request "/test", :method => "GET", :input => "Hello, World!"
|
104
|
-
chunks.join.should eq("Hello, World!")
|
105
|
-
end
|
106
|
-
|
107
|
-
it "should understand the Content-Type header correctly" do
|
108
|
-
header "CONTENT_TYPE", "application/json"
|
109
|
-
put "/test"
|
110
|
-
last_response.status.should == 204
|
111
|
-
end
|
112
|
-
|
113
|
-
it "should set Server header" do
|
114
|
-
get "/test"
|
115
|
-
last_response.original_headers.should have_key("Server")
|
116
|
-
end
|
117
|
-
|
118
|
-
it "should set Set-Cookie header" do
|
119
|
-
get "/test"
|
120
|
-
# Yes, Rack expects multiple values for a given cookie to be
|
121
|
-
# \n separated.
|
122
|
-
last_response.original_headers["Set-Cookie"].should == "cookie=monster\nrodeo=clown"
|
123
|
-
end
|
124
|
-
|
125
|
-
it "should handle non-success correctly" do
|
126
|
-
get "/missing"
|
127
|
-
last_response.status.should == 404
|
128
|
-
last_response.content_type.should == "text/html"
|
129
|
-
end
|
130
|
-
|
131
|
-
it "should handle empty bodies correctly" do
|
132
|
-
header "CONTENT_TYPE", "application/json"
|
133
|
-
post "/test"
|
134
|
-
last_response.status.should == 204
|
135
|
-
last_response.original_headers.should_not have_key("Content-Type")
|
136
|
-
last_response.original_headers.should_not have_key("Content-Length")
|
137
|
-
last_response.body.should == ""
|
138
|
-
end
|
139
|
-
|
140
|
-
it "should handle cookies correctly" do
|
141
|
-
header "COOKIE", "string=123"
|
142
|
-
get "/test"
|
143
|
-
last_response.status.should == 200
|
144
|
-
last_response.body.should == "<html><body>123</body></html>"
|
145
|
-
end
|
146
|
-
|
147
|
-
it "should handle streaming enumerable response bodies" do
|
148
|
-
header "ACCEPT", "application/vnd.webmachine.streaming+enum"
|
149
|
-
get "/test"
|
150
|
-
last_response.status.should == 200
|
151
|
-
last_response.original_headers["Transfer-Encoding"].should == "chunked"
|
152
|
-
last_response.body.split("\r\n").should == %W{6 Hello, 6 World! 0}
|
153
|
-
end
|
154
|
-
|
155
|
-
it "should handle streaming callable response bodies" do
|
156
|
-
header "ACCEPT", "application/vnd.webmachine.streaming+proc"
|
157
|
-
get "/test"
|
158
|
-
last_response.status.should == 200
|
159
|
-
last_response.original_headers["Transfer-Encoding"].should == "chunked"
|
160
|
-
last_response.body.split("\r\n").should == %W{6 Stream 0}
|
161
|
-
end
|
162
|
-
|
163
|
-
it "should receive Content-Type on Not acceptable response" do
|
164
|
-
header "ACCEPT", "text/plain"
|
165
|
-
get "/test"
|
166
|
-
last_response.status.should == 406
|
167
|
-
last_response.original_headers.should have_key('Content-Type')
|
168
35
|
end
|
169
36
|
end
|
@@ -1,23 +1,75 @@
|
|
1
1
|
require 'spec_helper'
|
2
|
+
require 'support/adapter_lint'
|
2
3
|
|
3
4
|
if RUBY_VERSION >= "1.9"
|
4
5
|
describe Webmachine::Adapters::Reel do
|
6
|
+
it_should_behave_like :adapter_lint
|
7
|
+
|
5
8
|
let(:configuration) { Webmachine::Configuration.default }
|
6
9
|
let(:dispatcher) { Webmachine::Dispatcher.new }
|
7
10
|
let(:adapter) do
|
8
11
|
described_class.new(configuration, dispatcher)
|
9
12
|
end
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
+
context 'websockets' do
|
15
|
+
let(:example_host) { "www.example.com" }
|
16
|
+
let(:example_path) { "/example"}
|
17
|
+
let(:example_url) { "ws://#{example_host}#{example_path}" }
|
18
|
+
let :handshake_headers do
|
19
|
+
{
|
20
|
+
"Host" => example_host,
|
21
|
+
"Upgrade" => "websocket",
|
22
|
+
"Connection" => "Upgrade",
|
23
|
+
"Sec-WebSocket-Key" => "dGhlIHNhbXBsZSBub25jZQ==",
|
24
|
+
"Origin" => "http://example.com",
|
25
|
+
"Sec-WebSocket-Protocol" => "chat, superchat",
|
26
|
+
"Sec-WebSocket-Version" => "13"
|
27
|
+
}
|
28
|
+
end
|
29
|
+
let(:client_message) { "Hi server!" }
|
30
|
+
let(:server_message) { "Hi client!" }
|
31
|
+
|
32
|
+
it 'supports websockets' do
|
33
|
+
configuration.adapter_options[:websocket_handler] = proc do |socket|
|
34
|
+
socket.read.should eq client_message
|
35
|
+
socket << server_message
|
36
|
+
end
|
37
|
+
|
38
|
+
reel_server(adapter) do |client|
|
39
|
+
client << WebSocket::ClientHandshake.new(:get, example_url, handshake_headers).to_data
|
40
|
+
|
41
|
+
# Discard handshake response
|
42
|
+
# FIXME: hax
|
43
|
+
client.readpartial(4096)
|
44
|
+
|
45
|
+
client << WebSocket::Message.new(client_message).to_data
|
46
|
+
parser = WebSocket::Parser.new
|
47
|
+
parser.append client.readpartial(4096) until message = parser.next_message
|
14
48
|
|
15
|
-
|
16
|
-
|
49
|
+
message.should eq server_message
|
50
|
+
end
|
51
|
+
end
|
17
52
|
end
|
18
53
|
|
19
|
-
|
20
|
-
|
54
|
+
def reel_server(adptr = adapter)
|
55
|
+
thread = Thread.new { adptr.run }
|
56
|
+
begin
|
57
|
+
timeout(5) do
|
58
|
+
begin
|
59
|
+
sock = TCPSocket.new(adptr.configuration.ip, adptr.configuration.port)
|
60
|
+
begin
|
61
|
+
yield(sock)
|
62
|
+
ensure
|
63
|
+
sock.close
|
64
|
+
end
|
65
|
+
rescue Errno::ECONNREFUSED
|
66
|
+
Thread.pass
|
67
|
+
retry
|
68
|
+
end
|
69
|
+
end
|
70
|
+
ensure
|
71
|
+
adptr.shutdown
|
72
|
+
end
|
21
73
|
end
|
22
74
|
end
|
23
75
|
end
|
@@ -1,18 +1,12 @@
|
|
1
1
|
require "spec_helper"
|
2
|
+
require "support/adapter_lint"
|
2
3
|
|
3
4
|
describe Webmachine::Adapters::WEBrick do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
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
|
5
|
+
it_should_behave_like :adapter_lint do
|
6
|
+
it "should set Server header" do
|
7
|
+
response = client.request(Net::HTTP::Get.new("/test"))
|
8
|
+
response["Server"].should match(/Webmachine/)
|
9
|
+
response["Server"].should match(/WEBrick/)
|
10
|
+
end
|
17
11
|
end
|
18
12
|
end
|