webmachine 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|