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.
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