hatetepe 0.3.1 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +21 -16
- data/hatetepe.gemspec +1 -2
- data/lib/hatetepe/body.rb +5 -3
- data/lib/hatetepe/builder.rb +6 -3
- data/lib/hatetepe/cli.rb +27 -5
- data/lib/hatetepe/client.rb +157 -82
- data/lib/hatetepe/client/keep_alive.rb +58 -0
- data/lib/hatetepe/client/pipeline.rb +19 -0
- data/lib/hatetepe/connection.rb +42 -0
- data/lib/hatetepe/deferred_status_fix.rb +11 -0
- data/lib/hatetepe/message.rb +4 -4
- data/lib/hatetepe/parser.rb +3 -4
- data/lib/hatetepe/request.rb +19 -6
- data/lib/hatetepe/response.rb +11 -3
- data/lib/hatetepe/server.rb +115 -85
- data/lib/hatetepe/{app.rb → server/app.rb} +7 -2
- data/lib/hatetepe/server/keep_alive.rb +61 -0
- data/lib/hatetepe/server/pipeline.rb +24 -0
- data/lib/hatetepe/{proxy.rb → server/proxy.rb} +5 -11
- data/lib/hatetepe/version.rb +1 -1
- data/lib/rack/handler/hatetepe.rb +1 -4
- data/spec/integration/cli/start_spec.rb +75 -123
- data/spec/integration/client/keep_alive_spec.rb +74 -0
- data/spec/integration/server/keep_alive_spec.rb +99 -0
- data/spec/spec_helper.rb +41 -16
- data/spec/unit/app_spec.rb +16 -5
- data/spec/unit/builder_spec.rb +4 -4
- data/spec/unit/client/pipeline_spec.rb +40 -0
- data/spec/unit/client_spec.rb +355 -199
- data/spec/unit/connection_spec.rb +64 -0
- data/spec/unit/parser_spec.rb +3 -2
- data/spec/unit/proxy_spec.rb +9 -18
- data/spec/unit/rack_handler_spec.rb +2 -12
- data/spec/unit/server_spec.rb +154 -60
- metadata +31 -36
- data/.rspec +0 -1
- data/.travis.yml +0 -3
- data/.yardopts +0 -1
- 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
|
data/spec/unit/parser_spec.rb
CHANGED
@@ -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
|
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
|
100
|
+
request.headers.should == {"Transfer-Encoding" => "chunked",
|
101
|
+
"Bar" => "baz"}
|
101
102
|
}
|
102
103
|
|
103
104
|
parser.on_request &block
|
data/spec/unit/proxy_spec.rb
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
require "spec_helper"
|
2
|
-
require "hatetepe/
|
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
|
57
|
+
Rack::Handler::Hatetepe.run app
|
68
58
|
end
|
69
59
|
end
|
70
60
|
end
|
data/spec/unit/server_spec.rb
CHANGED
@@ -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) {
|
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
|
71
|
-
Hatetepe::Server.allocate.tap
|
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
|
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 "
|
215
|
-
server.
|
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 "
|
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
|
-
|
233
|
-
server.
|
234
|
-
app.stub(:call) {|e|
|
235
|
-
|
236
|
-
|
237
|
-
|
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
|
-
|
243
|
-
|
244
|
-
|
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 "
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
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
|