webmachine 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. data/Gemfile +5 -4
  2. data/README.md +25 -2
  3. data/examples/debugger.rb +7 -0
  4. data/examples/logging.rb +41 -0
  5. data/lib/webmachine/adapters.rb +1 -1
  6. data/lib/webmachine/adapters/hatetepe.rb +5 -1
  7. data/lib/webmachine/adapters/lazy_request_body.rb +7 -7
  8. data/lib/webmachine/adapters/mongrel.rb +27 -12
  9. data/lib/webmachine/adapters/rack.rb +42 -6
  10. data/lib/webmachine/adapters/reel.rb +91 -23
  11. data/lib/webmachine/adapters/webrick.rb +12 -6
  12. data/lib/webmachine/application.rb +1 -0
  13. data/lib/webmachine/cookie.rb +1 -1
  14. data/lib/webmachine/dispatcher.rb +7 -1
  15. data/lib/webmachine/events.rb +179 -0
  16. data/lib/webmachine/events/instrumented_event.rb +19 -0
  17. data/lib/webmachine/response.rb +1 -1
  18. data/lib/webmachine/streaming/io_encoder.rb +5 -1
  19. data/lib/webmachine/trace.rb +18 -0
  20. data/lib/webmachine/trace/fsm.rb +4 -1
  21. data/lib/webmachine/trace/listener.rb +12 -0
  22. data/lib/webmachine/version.rb +1 -1
  23. data/spec/spec_helper.rb +11 -0
  24. data/spec/support/adapter_lint.rb +125 -0
  25. data/spec/support/test_resource.rb +73 -0
  26. data/spec/webmachine/adapters/hatetepe_spec.rb +2 -2
  27. data/spec/webmachine/adapters/mongrel_spec.rb +6 -51
  28. data/spec/webmachine/adapters/rack_spec.rb +22 -155
  29. data/spec/webmachine/adapters/reel_spec.rb +59 -7
  30. data/spec/webmachine/adapters/webrick_spec.rb +7 -13
  31. data/spec/webmachine/cookie_spec.rb +1 -1
  32. data/spec/webmachine/decision/helpers_spec.rb +10 -0
  33. data/spec/webmachine/events_spec.rb +58 -0
  34. data/spec/webmachine/request_spec.rb +41 -0
  35. data/webmachine.gemspec +1 -1
  36. 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) { EM.stop }
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
- Enumerator.new(enum).to_a.join
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
- 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
- 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 'spec_helper'
1
+ require 'webmachine/adapter'
2
2
  require 'webmachine/adapters/rack'
3
- require 'rack'
4
- require 'rack/test'
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
- def to_proc_stream
40
- Proc.new { "Stream" }
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
- include Rack::Test::Methods
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
- adapter.run
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
- it "should proxy request to webmachine" do
83
- get "/test"
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
- it "should build an enumerable request body" do
98
- chunks = []
99
- dispatcher.should_receive(:dispatch) do |request, response|
100
- request.body.each { |chunk| chunks << chunk }
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
- it 'inherits from Webmachine::Adapter' do
12
- adapter.should be_a_kind_of(Webmachine::Adapter)
13
- end
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
- it 'implements #run' do
16
- adapter.should respond_to(:run)
49
+ message.should eq server_message
50
+ end
51
+ end
17
52
  end
18
53
 
19
- it 'implements #process' do
20
- adapter.should respond_to(:process)
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
- 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
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