hatetepe 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. data/README.md +21 -16
  2. data/hatetepe.gemspec +1 -2
  3. data/lib/hatetepe/body.rb +5 -3
  4. data/lib/hatetepe/builder.rb +6 -3
  5. data/lib/hatetepe/cli.rb +27 -5
  6. data/lib/hatetepe/client.rb +157 -82
  7. data/lib/hatetepe/client/keep_alive.rb +58 -0
  8. data/lib/hatetepe/client/pipeline.rb +19 -0
  9. data/lib/hatetepe/connection.rb +42 -0
  10. data/lib/hatetepe/deferred_status_fix.rb +11 -0
  11. data/lib/hatetepe/message.rb +4 -4
  12. data/lib/hatetepe/parser.rb +3 -4
  13. data/lib/hatetepe/request.rb +19 -6
  14. data/lib/hatetepe/response.rb +11 -3
  15. data/lib/hatetepe/server.rb +115 -85
  16. data/lib/hatetepe/{app.rb → server/app.rb} +7 -2
  17. data/lib/hatetepe/server/keep_alive.rb +61 -0
  18. data/lib/hatetepe/server/pipeline.rb +24 -0
  19. data/lib/hatetepe/{proxy.rb → server/proxy.rb} +5 -11
  20. data/lib/hatetepe/version.rb +1 -1
  21. data/lib/rack/handler/hatetepe.rb +1 -4
  22. data/spec/integration/cli/start_spec.rb +75 -123
  23. data/spec/integration/client/keep_alive_spec.rb +74 -0
  24. data/spec/integration/server/keep_alive_spec.rb +99 -0
  25. data/spec/spec_helper.rb +41 -16
  26. data/spec/unit/app_spec.rb +16 -5
  27. data/spec/unit/builder_spec.rb +4 -4
  28. data/spec/unit/client/pipeline_spec.rb +40 -0
  29. data/spec/unit/client_spec.rb +355 -199
  30. data/spec/unit/connection_spec.rb +64 -0
  31. data/spec/unit/parser_spec.rb +3 -2
  32. data/spec/unit/proxy_spec.rb +9 -18
  33. data/spec/unit/rack_handler_spec.rb +2 -12
  34. data/spec/unit/server_spec.rb +154 -60
  35. metadata +31 -36
  36. data/.rspec +0 -1
  37. data/.travis.yml +0 -3
  38. data/.yardopts +0 -1
  39. data/lib/hatetepe/pipeline.rb +0 -27
@@ -0,0 +1,64 @@
1
+ require "spec_helper"
2
+ require "hatetepe/connection"
3
+
4
+ describe Hatetepe::Connection do
5
+ let(:conn) { Hatetepe::Connection.allocate }
6
+
7
+ let(:peername) { "\x02\x00\x86\xF6\x7F\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00" }
8
+ let(:address) { "127.0.0.1" }
9
+ let(:port) { 34550 }
10
+
11
+ before { conn.stub :get_peername => peername }
12
+
13
+ it "inherits from EM::Connection" do
14
+ conn.should be_an(EM::Connection)
15
+ end
16
+
17
+ describe "#remote_address" do
18
+ it "returns the remote peer's address" do
19
+ conn.remote_address.should == address
20
+ end
21
+ end
22
+
23
+ describe "#remote_port" do
24
+ it "returns the remote peer's port" do
25
+ conn.remote_port.should == port
26
+ end
27
+ end
28
+
29
+ describe "#close_connection" do
30
+ before { EM::Connection.any_instance.stub :close_connection }
31
+
32
+ it "sets the closed-by-self flag" do
33
+ pending "How to test a call to super?"
34
+
35
+ conn.close_connection
36
+ conn.should be_closed
37
+ conn.should be_closed_by_self
38
+ end
39
+
40
+ let(:arg) { stub "arg" }
41
+
42
+ it "calls EM::Connection.close_connection" do
43
+ pending "How to test a call to super?"
44
+
45
+ EM::Connection.any_instance.should_receive(:close_connection).with arg
46
+ conn.close_connection arg
47
+ end
48
+ end
49
+
50
+ describe "#unbind" do
51
+ it "sets the closed-by-remote flag" do
52
+ conn.unbind
53
+ conn.should be_closed
54
+ conn.should be_closed_by_remote
55
+ end
56
+
57
+ it "doesn't overwrite an existing closed-by flag" do
58
+ conn.stub :closed? => true
59
+ conn.unbind
60
+ conn.should be_closed
61
+ conn.should_not be_closed_by_remote
62
+ end
63
+ end
64
+ end
@@ -90,14 +90,15 @@ describe Hatetepe::Parser do
90
90
  }
91
91
 
92
92
  context "#on_request {|request| ... }" do
93
- it "evals the block when a request line comes in" do
93
+ it "evals the block when the request headers have arrived" do
94
94
  block.should_receive(:call) {|request|
95
95
  request.should equal(parser.message)
96
96
 
97
97
  request.verb.should == "POST"
98
98
  request.uri.should == "/"
99
99
  request.http_version.should == "1.1"
100
- request.headers.should be_empty
100
+ request.headers.should == {"Transfer-Encoding" => "chunked",
101
+ "Bar" => "baz"}
101
102
  }
102
103
 
103
104
  parser.on_request &block
@@ -1,16 +1,16 @@
1
1
  require "spec_helper"
2
- require "hatetepe/proxy"
2
+ require "hatetepe/server"
3
3
 
4
- describe Hatetepe::Proxy do
4
+ describe Hatetepe::Server::Proxy do
5
5
  let(:app) { stub "app" }
6
6
 
7
7
  describe "#initialize(app)" do
8
8
  it "sets the app" do
9
- Hatetepe::Proxy.new(app).app.should equal(app)
9
+ Hatetepe::Server::Proxy.new(app).app.should equal(app)
10
10
  end
11
11
  end
12
12
 
13
- let(:proxy) { Hatetepe::Proxy.new app }
13
+ let(:proxy) { Hatetepe::Server::Proxy.new app }
14
14
  let(:target) { stub "target" }
15
15
  let(:env) { {} }
16
16
  let(:client) { stub "client", :<< => nil }
@@ -133,22 +133,13 @@ describe Hatetepe::Proxy do
133
133
  proxy.build_request(env, target).uri.should == "/bar/foo"
134
134
  end
135
135
 
136
- it "sets X-Forwarded-For header" do
137
- xff = proxy.build_request(env, target).headers["X-Forwarded-For"]
138
- env["REMOTE_ADDR"].should == xff
139
- end
140
-
141
- it "adds the target to Host header" do
142
- host = "localhost:3000"
143
- proxy.build_request(env, target).headers["Host"].should == host
144
-
145
- base_request.headers["Host"] = host
146
- host = "localhost:3000, localhost:3000"
147
- proxy.build_request(env, target).headers["Host"].should == host
148
- end
149
-
150
136
  it "builds a new request" do
151
137
  proxy.build_request(env, target).should_not equal(base_request)
152
138
  end
139
+
140
+ it "sets version to HTTP/1.1" do
141
+ base_request.http_version = "1.0"
142
+ proxy.build_request(env, target).http_version.should == "1.1"
143
+ end
153
144
  end
154
145
  end
@@ -31,17 +31,6 @@ describe Rack::Handler::Hatetepe do
31
31
  Rack::Handler::Hatetepe.run app, options
32
32
  end
33
33
 
34
- it "yields the server" do
35
- Hatetepe::Server.stub :start => server
36
-
37
- srvr = nil
38
- Rack::Handler::Hatetepe.run(app) {|s|
39
- srvr = s
40
- EM.stop
41
- }
42
- srvr.should equal(server)
43
- end
44
-
45
34
  it "can be stopped by sending SIGTERM or SIGINT" do
46
35
  EM.should_receive(:synchrony) {|&block| block.call }
47
36
 
@@ -63,8 +52,9 @@ describe Rack::Handler::Hatetepe do
63
52
  Hatetepe::Server.should_receive(:start) {|opts|
64
53
  opts[:host].should == "0.0.0.0"
65
54
  opts[:port].should == 8080
55
+ EM.stop
66
56
  }
67
- Rack::Handler::Hatetepe.run(app) { EM.stop }
57
+ Rack::Handler::Hatetepe.run app
68
58
  end
69
59
  end
70
60
  end
@@ -5,16 +5,13 @@ describe Hatetepe::Server do
5
5
  let(:server) {
6
6
  Hatetepe::Server.allocate.tap {|s|
7
7
  s.send :initialize, config
8
+ s.stub :comm_inactivity_timeout=
8
9
  s.post_init
9
10
  s.requests << request
10
11
  }
11
12
  }
12
- let(:request) { stub "request", :to_hash => env }
13
- let(:env) {
14
- {
15
- "rack.input" => Hatetepe::Body.new
16
- }
17
- }
13
+ let(:request) { Hatetepe::Request.new :get, "/" }
14
+ let(:env) { request.to_h }
18
15
 
19
16
  let(:app) { stub "app" }
20
17
  let(:host) { "127.0.4.1" }
@@ -25,10 +22,24 @@ describe Hatetepe::Server do
25
22
  :app => app,
26
23
  :host => host,
27
24
  :port => port,
28
- :errors => errors
25
+ :errors => errors,
26
+ :timeout => 0.0123
29
27
  }
30
28
  }
31
29
 
30
+ before do
31
+ server.stub :sockaddr => [42424, "127.0.42.1"]
32
+ @old_env, ENV["RACK_ENV"] = ENV["RACK_ENV"], "testing"
33
+ end
34
+
35
+ after do
36
+ ENV["RACK_ENV"] = @old_env
37
+ end
38
+
39
+ it "inherits from Hatetepe::Connection" do
40
+ server.should be_a(Hatetepe::Connection)
41
+ end
42
+
32
43
  context ".start(config)" do
33
44
  it "starts an EventMachine server" do
34
45
  args = [host, port, Hatetepe::Server, config]
@@ -40,23 +51,11 @@ describe Hatetepe::Server do
40
51
 
41
52
  context "#initialize(config)" do
42
53
  let(:server) { Hatetepe::Server.allocate }
43
- let(:builder) { stub "app builder", :to_app => to_app }
44
- let(:to_app) { stub "to_app" }
45
-
46
- it "builds the app" do
47
- Rack::Builder.stub :new => builder
48
- builder.should_receive(:use).with Hatetepe::Pipeline
49
- builder.should_receive(:use).with Hatetepe::App
50
- builder.should_receive(:use).with Hatetepe::Proxy
51
- builder.should_receive(:run).with app
52
-
53
- server.send :initialize, config
54
- server.app.should equal(to_app)
55
- end
56
54
 
57
55
  it "sets up the error stream" do
58
56
  server.send :initialize, config
59
57
  server.errors.should equal(errors)
58
+ server.config[:errors].should be_nil
60
59
  end
61
60
 
62
61
  it "uses stderr as default error stream" do
@@ -64,30 +63,63 @@ describe Hatetepe::Server do
64
63
  server.send :initialize, config
65
64
  server.errors.should equal($stderr)
66
65
  end
66
+
67
+ it "assumes a default connection inactivity timeout of 1 seconds" do
68
+ server.send :initialize, {}
69
+ server.config[:timeout].should equal(1)
70
+ end
67
71
  end
68
72
 
69
73
  context "#post_init" do
70
- let(:server) {
71
- Hatetepe::Server.allocate.tap {|s| s.post_init }
72
- }
74
+ let :server do
75
+ Hatetepe::Server.allocate.tap do |s|
76
+ s.send :initialize, config
77
+ s.stub :comm_inactivity_timeout=
78
+ end
79
+ end
73
80
 
74
81
  it "sets up the request queue" do
82
+ server.post_init
75
83
  server.requests.should be_an(Array)
76
84
  server.requests.should be_empty
77
85
  end
78
86
 
79
87
  it "sets up the parser" do
88
+ server.post_init
80
89
  server.parser.should respond_to(:<<)
81
90
  server.parser.on_request[0].should == server.requests.method(:<<)
82
91
  end
83
92
 
84
93
  it "sets up the builder" do
94
+ server.post_init
85
95
  server.builder.on_write[0].should == server.method(:send_data)
86
96
  end
97
+
98
+ it "builds the app" do
99
+ server.post_init
100
+ server.app.should be_a(Hatetepe::Server::Pipeline)
101
+ server.app.app.should be_a(Hatetepe::Server::App)
102
+ server.app.app.app.should be_a(Hatetepe::Server::KeepAlive)
103
+ server.app.app.app.app.should be_a(Hatetepe::Server::Proxy)
104
+ server.app.app.app.app.app.should equal(app)
105
+ end
106
+
107
+ it "starts the connection inactivity tracking" do
108
+ server.should_receive(:comm_inactivity_timeout=).with 0.0123
109
+ server.post_init
110
+ end
111
+
112
+ it "enables request processing" do
113
+ server.post_init
114
+ server.processing_enabled.should be_true
115
+ end
87
116
  end
88
117
 
89
118
  context "#receive_data(data)" do
90
- before { server.stub :close_connection_after_writing }
119
+ before do
120
+ ENV.delete "RACK_ENV"
121
+ server.stub :close_connection
122
+ end
91
123
 
92
124
  it "feeds data into the parser" do
93
125
  data = stub("data")
@@ -102,13 +134,29 @@ describe Hatetepe::Server do
102
134
  server.receive_data "irrelevant data"
103
135
  end
104
136
 
137
+ it "re-raises parsing errors if RACK_ENV is testing" do
138
+ ENV["RACK_ENV"] = "testing"
139
+ server.parser.should_receive(:<<).and_raise Hatetepe::ParserError
140
+
141
+ expect {
142
+ server.receive_data "irrelevant data"
143
+ }.to raise_error(Hatetepe::ParserError)
144
+ end
145
+
105
146
  it "closes the connection when catching an exception" do
106
- server.parser.should_receive(:<<).and_raise
147
+ server.parser.should_receive(:<<).and_raise Exception
107
148
  server.should_receive :close_connection_after_writing
108
149
 
109
150
  server.receive_data ""
110
151
  end
111
152
 
153
+ it "re-raises caught exceptions" do
154
+ ENV["RACK_ENV"] = "testing"
155
+ server.parser.should_receive(:<<).and_raise Exception
156
+
157
+ expect { server.receive_data "" }.to raise_error(Exception)
158
+ end
159
+
112
160
  it "logs caught exceptions" do
113
161
  server.parser.should_receive(:<<).and_raise "error message"
114
162
  errors.should_receive(:<<) {|str|
@@ -121,6 +169,11 @@ describe Hatetepe::Server do
121
169
  end
122
170
 
123
171
  context "#process" do
172
+ before do
173
+ request.stub :to_h => env
174
+ app.stub :call => [-1]
175
+ end
176
+
124
177
  it "puts useful stuff into env[]" do
125
178
  app.should_receive(:call) {|e|
126
179
  e.should equal(env)
@@ -135,6 +188,9 @@ describe Hatetepe::Server do
135
188
  e["SERVER_NAME"].should == host
136
189
  e["SERVER_NAME"].should_not equal(host)
137
190
  e["SERVER_PORT"].should == String(port)
191
+ e["REMOTE_ADDR"].should == server.remote_address
192
+ e["REMOTE_ADDR"].should_not equal(server.remote_address)
193
+ e["REMOTE_PORT"].should == String(server.remote_port)
138
194
  e["HTTP_HOST"].should == "#{host}:#{port}"
139
195
 
140
196
  [-1]
@@ -150,6 +206,32 @@ describe Hatetepe::Server do
150
206
  }
151
207
  server.process
152
208
  end
209
+
210
+ it "is a no-op if processing is disabled" do
211
+ server.processing_enabled = false
212
+ app.should_not_receive :call
213
+ server.process
214
+ end
215
+
216
+ let(:another_request) { Hatetepe::Request.new :get, "/asdf" }
217
+
218
+ it "disables the connection timeout until the last request is finished" do
219
+ server.requests << another_request
220
+
221
+ server.should_receive(:comm_inactivity_timeout=).with 0
222
+ server.process
223
+
224
+ server.should_not_receive(:comm_inactivity_timeout=).with config[:timeout]
225
+ server.requests.delete request
226
+ request.succeed
227
+
228
+ server.rspec_verify
229
+ server.rspec_reset
230
+
231
+ server.should_receive(:comm_inactivity_timeout=).with config[:timeout]
232
+ server.requests.delete another_request
233
+ another_request.succeed
234
+ end
153
235
  end
154
236
 
155
237
  context "env[stream.start].call(response)" do
@@ -176,22 +258,11 @@ describe Hatetepe::Server do
176
258
  server.process
177
259
  end
178
260
 
261
+ # TODO this should be moved to a Server::Pipeline spec
179
262
  it "waits for the previous request's response to finish" do
180
263
  server.builder.should_not_receive :response
181
264
  server.process
182
265
  end
183
-
184
- it "initiates the response" do
185
- server.builder.should_receive(:response_line) {|code|
186
- code.should equal(response[0])
187
- }
188
- server.builder.should_receive(:headers) {|headers|
189
- headers["Key"].should equal(response[1]["Key"])
190
- headers["Server"].should == "hatetepe/#{Hatetepe::VERSION}"
191
- }
192
- previous.succeed
193
- server.process
194
- end
195
266
  end
196
267
 
197
268
  context "env[stream.send].call(chunk)" do
@@ -206,55 +277,78 @@ describe Hatetepe::Server do
206
277
 
207
278
  context "env[stream.close].call" do
208
279
  before {
209
- server.stub :close_connection
210
280
  server.builder.stub :complete
211
281
  request.stub :succeed
212
282
  }
213
283
 
214
- it "completes the response" do
215
- server.builder.should_receive :complete
284
+ it "leaves the connection open" do
285
+ server.should_not_receive :close_connection
216
286
  app.stub(:call) {|e|
287
+ server.requests << stub("another request")
217
288
  e["stream.close"].call
218
289
  [-1]
219
290
  }
220
291
  server.process
221
292
  end
222
293
 
223
- it "succeeds the request" do
224
- request.should_receive :succeed
294
+ it "deletes itself and stream.send from env[] to prevent multiple calls" do
225
295
  app.stub(:call) {|e|
226
296
  e["stream.close"].call
297
+ e.key?("stream.send").should be_false
298
+ e.key?("stream.close").should be_false
227
299
  [-1]
228
300
  }
229
301
  server.process
230
302
  end
303
+ end
304
+
305
+ context "#start_response(response)" do
306
+ let(:previous) { EM::DefaultDeferrable.new }
307
+ let(:response) { [200, {"Key" => "value"}, Rack::STREAMING] }
231
308
 
232
- it "leaves the connection open" do
233
- server.should_not_receive :close_connection
234
- app.stub(:call) {|e|
235
- server.requests << stub("another request")
236
- e["stream.close"].call
237
- [-1]
309
+ before {
310
+ server.requests.unshift previous
311
+ app.stub(:call) {|e| response }
312
+ request.stub :succeed
313
+ server.builder.stub :response_line
314
+ server.builder.stub :headers
315
+ }
316
+
317
+ it "initiates the response" do
318
+ server.builder.should_receive(:response_line) {|code|
319
+ code.should equal(response[0])
238
320
  }
321
+ server.builder.should_receive(:headers) {|headers|
322
+ headers["Key"].should equal(response[1]["Key"])
323
+ headers["Server"].should == "hatetepe/#{Hatetepe::VERSION}"
324
+ }
325
+ previous.succeed
239
326
  server.process
240
327
  end
241
-
242
- it "closes the connection if there are no more requests" do
243
- server.should_receive(:close_connection).with true
244
- app.stub(:call) {|e|
328
+ end
329
+
330
+ context "#close_response(request)" do
331
+ before do
332
+ server.builder.stub :complete
333
+ request.stub :succeed
334
+ app.stub :call do |e|
245
335
  e["stream.close"].call
246
336
  [-1]
247
- }
337
+ end
338
+ end
339
+
340
+ it "removes the request from the request queue" do
248
341
  server.process
342
+ server.requests.should be_empty
249
343
  end
250
344
 
251
- it "deletes itself and stream.send from env[] to prevent multiple calls" do
252
- app.stub(:call) {|e|
253
- e["stream.close"].call
254
- e.key?("stream.send").should be_false
255
- e.key?("stream.close").should be_false
256
- [-1]
257
- }
345
+ it "completes the response" do
346
+ server.builder.should_receive :complete
347
+ server.process
348
+ end
349
+
350
+ it "succeeds the request" do
351
+ request.should_receive :succeed
258
352
  server.process
259
353
  end
260
354
  end