hatetepe 0.0.4 → 0.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.
- data/.rspec +1 -0
- data/.travis.yml +3 -0
- data/README.md +21 -5
- data/Rakefile +3 -10
- data/bin/hatetepe +4 -0
- data/hatetepe.gemspec +10 -3
- data/lib/hatetepe.rb +5 -0
- data/lib/hatetepe/app.rb +44 -0
- data/lib/hatetepe/body.rb +79 -0
- data/lib/hatetepe/builder.rb +19 -3
- data/lib/hatetepe/cli.rb +50 -0
- data/lib/hatetepe/client.rb +95 -0
- data/lib/hatetepe/events.rb +35 -0
- data/lib/hatetepe/message.rb +13 -0
- data/lib/hatetepe/parser.rb +41 -71
- data/lib/hatetepe/prefork.rb +11 -0
- data/lib/hatetepe/proxy.rb +58 -0
- data/lib/hatetepe/request.rb +31 -0
- data/lib/hatetepe/response.rb +20 -0
- data/lib/hatetepe/server.rb +98 -0
- data/lib/hatetepe/thread_pool.rb +4 -0
- data/lib/hatetepe/version.rb +1 -1
- data/lib/rack/handler/hatetepe.rb +33 -0
- data/spec/integration/cli/start_spec.rb +169 -0
- data/spec/spec_helper.rb +37 -0
- data/spec/unit/app_spec.rb +108 -0
- data/spec/unit/body_spec.rb +198 -0
- data/spec/unit/client_spec.rb +270 -0
- data/spec/unit/events_spec.rb +96 -0
- data/spec/unit/parser_spec.rb +215 -0
- data/spec/unit/rack_handler_spec.rb +70 -0
- data/spec/unit/server_spec.rb +255 -0
- metadata +141 -56
- data/example.rb +0 -29
- data/test/builder_test.rb +0 -7
- data/test/parser_test.rb +0 -7
- data/test/test_helper.rb +0 -7
@@ -0,0 +1,96 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "hatetepe/events"
|
3
|
+
|
4
|
+
describe Hatetepe::Events do
|
5
|
+
let(:klass) {
|
6
|
+
Class.new {
|
7
|
+
include Hatetepe::Events
|
8
|
+
event :foo
|
9
|
+
}
|
10
|
+
}
|
11
|
+
let(:obj) { klass.new }
|
12
|
+
|
13
|
+
context "#event(name, *args)" do
|
14
|
+
let(:args) { ["arg#1", "arg#2", "arg#3"] }
|
15
|
+
let(:called) { [] }
|
16
|
+
|
17
|
+
before {
|
18
|
+
obj.on_foo {|*args| called << [:bar, args] }
|
19
|
+
obj.on_foo {|*args| called << [:baz, args] }
|
20
|
+
}
|
21
|
+
|
22
|
+
it "calls the listeners" do
|
23
|
+
obj.event :foo, *args
|
24
|
+
called.should == [[:bar, args], [:baz, args]]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "#event!(name, *args)" do
|
29
|
+
let(:args) { [:foo, "arg#1", "arg#2"] }
|
30
|
+
|
31
|
+
it "forwards to #event" do
|
32
|
+
obj.should_receive(:event).with(*args)
|
33
|
+
obj.event! *args
|
34
|
+
end
|
35
|
+
|
36
|
+
it "changes the state to specified name" do
|
37
|
+
obj.on_foo {
|
38
|
+
obj.foo?.should be_true
|
39
|
+
}
|
40
|
+
obj.event! :foo
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context ".event(name, *more_names)" do
|
45
|
+
before { klass.event :bar, :baz }
|
46
|
+
|
47
|
+
it "adds #on_name method" do
|
48
|
+
obj.should respond_to(:on_bar)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "adds #name? method" do
|
52
|
+
obj.should respond_to(:bar?)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "calls itself for each additional name" do
|
56
|
+
obj.should respond_to(:on_baz)
|
57
|
+
obj.should respond_to(:baz?)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context "#on_name {|*args| ... }" do
|
62
|
+
let(:block) { proc {} }
|
63
|
+
|
64
|
+
it "adds the block to the listener stack" do
|
65
|
+
obj.on_foo &block
|
66
|
+
obj.on_foo.should include(block)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context "#on_name" do
|
71
|
+
let(:blocks) { [proc {}, proc {}] }
|
72
|
+
|
73
|
+
it "returns the listener stack" do
|
74
|
+
obj.on_foo &blocks[0]
|
75
|
+
obj.on_foo &blocks[1]
|
76
|
+
|
77
|
+
obj.on_foo.should == blocks
|
78
|
+
end
|
79
|
+
|
80
|
+
it "returns an empty stack if no listeners have been added yet" do
|
81
|
+
obj.on_foo.should be_empty
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
context "#name?" do
|
86
|
+
it "returns true if the state equals :name" do
|
87
|
+
obj.stub :state => :foo
|
88
|
+
obj.foo?.should be_true
|
89
|
+
end
|
90
|
+
|
91
|
+
it "returns false if the state doesn't equal :name" do
|
92
|
+
obj.stub :state => :bar
|
93
|
+
obj.foo?.should be_false
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,215 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "hatetepe/parser"
|
3
|
+
|
4
|
+
describe Hatetepe::Parser do
|
5
|
+
let(:parser) { Hatetepe::Parser.new }
|
6
|
+
|
7
|
+
context "#initialize" do
|
8
|
+
it "calls #reset" do
|
9
|
+
Hatetepe::Parser.allocate.tap {|ins|
|
10
|
+
ins.should_receive :reset
|
11
|
+
ins.__send__ :initialize
|
12
|
+
}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context "#initialize {|instance| ... }" do
|
17
|
+
it "yields the new instance" do
|
18
|
+
instance = nil
|
19
|
+
Hatetepe::Parser.new {|ins|
|
20
|
+
instance = ins
|
21
|
+
}.should equal(instance)
|
22
|
+
end
|
23
|
+
|
24
|
+
it "evals the block in its original context" do
|
25
|
+
expected, actual = nil, nil
|
26
|
+
Object.new.instance_eval {
|
27
|
+
expected = self
|
28
|
+
::Hatetepe::Parser.new {|ins|
|
29
|
+
actual = self
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
actual.should equal(expected)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "#initialize {|| ... }" do
|
38
|
+
it "evals the block in the new instance' context" do
|
39
|
+
actual = nil
|
40
|
+
expected = Hatetepe::Parser.new {
|
41
|
+
actual = self
|
42
|
+
}
|
43
|
+
|
44
|
+
actual.should equal(expected)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context "#reset" do
|
49
|
+
before { parser << "GET / HTTP/1.1\r\n\r\n" }
|
50
|
+
|
51
|
+
it "resets the message" do
|
52
|
+
parser.reset
|
53
|
+
parser.message.should be_nil
|
54
|
+
end
|
55
|
+
|
56
|
+
it "resets the state to :reset" do
|
57
|
+
parser.reset
|
58
|
+
parser.reset?.should be_true
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
context "#<<(data)" do
|
63
|
+
it "raises a ParserError if parsing fails" do
|
64
|
+
expect {
|
65
|
+
parser << "herp derp\r\n"
|
66
|
+
}.to raise_error(Hatetepe::ParserError)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
let(:block) {
|
71
|
+
stub("block").tap {|blk|
|
72
|
+
blk.stub :to_proc => proc {|*args| blk.call *args }
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
let(:do_request) {
|
77
|
+
parser << "POST / HTTP/1.1\r\n"
|
78
|
+
parser << "Transfer-Encoding: chunked\r\n"
|
79
|
+
parser << "Bar: baz\r\n"
|
80
|
+
parser << "\r\n"
|
81
|
+
parser << "6\r\n"
|
82
|
+
parser << "Hello!\r\n"
|
83
|
+
parser << "0\r\n"
|
84
|
+
parser << "\r\n"
|
85
|
+
}
|
86
|
+
|
87
|
+
let(:do_response) {
|
88
|
+
parser << "HTTP/1.1 200 OK\r\n"
|
89
|
+
parser << "\r\n"
|
90
|
+
}
|
91
|
+
|
92
|
+
context "#on_request {|request| ... }" do
|
93
|
+
it "evals the block when a request line comes in" do
|
94
|
+
block.should_receive(:call) {|request|
|
95
|
+
request.should equal(parser.message)
|
96
|
+
|
97
|
+
request.verb.should == "POST"
|
98
|
+
request.uri.should == "/"
|
99
|
+
request.http_version.should == "1.1"
|
100
|
+
request.headers.should be_empty
|
101
|
+
}
|
102
|
+
|
103
|
+
parser.on_request &block
|
104
|
+
do_request
|
105
|
+
end
|
106
|
+
|
107
|
+
it "changes the state to :request" do
|
108
|
+
block.should_receive(:call) {
|
109
|
+
parser.request?.should be_true
|
110
|
+
}
|
111
|
+
|
112
|
+
parser.on_request &block
|
113
|
+
do_request
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context "#on_response {|response| ... }" do
|
118
|
+
it "evals the block when a response line comes in" do
|
119
|
+
block.should_receive(:call) {|response|
|
120
|
+
response.should equal(parser.message)
|
121
|
+
|
122
|
+
response.status.should == 200
|
123
|
+
response.http_version.should == "1.1"
|
124
|
+
}
|
125
|
+
|
126
|
+
parser.on_response &block
|
127
|
+
do_response
|
128
|
+
end
|
129
|
+
|
130
|
+
it "changes the state to :response" do
|
131
|
+
block.should_receive(:call) {
|
132
|
+
parser.response?.should be_true
|
133
|
+
}
|
134
|
+
|
135
|
+
parser.on_response &block
|
136
|
+
do_response
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
context "#on_headers {|headers| ... }" do
|
141
|
+
it "evals the block when the headers are complete" do
|
142
|
+
block.should_receive(:call) {|headers|
|
143
|
+
headers.should equal(parser.message.headers)
|
144
|
+
|
145
|
+
headers["Transfer-Encoding"].should == "chunked"
|
146
|
+
headers["Bar"].should == "baz"
|
147
|
+
}
|
148
|
+
|
149
|
+
parser.on_headers &block
|
150
|
+
do_request
|
151
|
+
end
|
152
|
+
|
153
|
+
it "changes the state to :headers" do
|
154
|
+
block.should_receive(:call) {
|
155
|
+
parser.headers?.should be_true
|
156
|
+
}
|
157
|
+
|
158
|
+
parser.on_headers &block
|
159
|
+
do_request
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
context "#on_body {|body| ... }" do
|
164
|
+
it "evals the block when the body starts" do
|
165
|
+
block.should_receive(:call) {|body|
|
166
|
+
body.should equal(parser.message.body)
|
167
|
+
|
168
|
+
body.should be_empty
|
169
|
+
}
|
170
|
+
|
171
|
+
parser.on_body &block
|
172
|
+
do_request
|
173
|
+
|
174
|
+
parser.message.body.tap {|b|
|
175
|
+
b.rewind
|
176
|
+
b.length.should == 6
|
177
|
+
b.read.should == "Hello!"
|
178
|
+
}
|
179
|
+
end
|
180
|
+
|
181
|
+
it "changes the state to :body" do
|
182
|
+
block.should_receive(:call) {
|
183
|
+
parser.body?.should be_true
|
184
|
+
}
|
185
|
+
|
186
|
+
parser.on_body &block
|
187
|
+
do_request
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
context "#on_complete { ... }" do
|
192
|
+
it "evals the block when the message is completely parsed" do
|
193
|
+
block.should_receive(:call)
|
194
|
+
|
195
|
+
parser.on_complete &block
|
196
|
+
do_request
|
197
|
+
end
|
198
|
+
|
199
|
+
it "changes the state to :complete" do
|
200
|
+
block.should_receive(:call) {
|
201
|
+
parser.complete?.should be_true
|
202
|
+
}
|
203
|
+
|
204
|
+
parser.on_complete &block
|
205
|
+
do_request
|
206
|
+
end
|
207
|
+
|
208
|
+
it "finishes the body" do
|
209
|
+
parser.on_body {|body|
|
210
|
+
body.should_receive :close_write
|
211
|
+
}
|
212
|
+
do_request
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "rack/handler/hatetepe"
|
3
|
+
|
4
|
+
describe Rack::Handler::Hatetepe do
|
5
|
+
let(:app) { stub "app" }
|
6
|
+
let(:options) {
|
7
|
+
{
|
8
|
+
:Host => stub("host"),
|
9
|
+
:Port => stub("port")
|
10
|
+
}
|
11
|
+
}
|
12
|
+
let(:server) { stub "server" }
|
13
|
+
|
14
|
+
describe ".run(app, options) {|server| ... }" do
|
15
|
+
before {
|
16
|
+
EM.stub :epoll
|
17
|
+
Signal.stub :trap
|
18
|
+
Hatetepe::Server.stub :start
|
19
|
+
}
|
20
|
+
|
21
|
+
it "starts an Hatetepe server" do
|
22
|
+
EM.should_receive(:run) {|&block|
|
23
|
+
EM.should_receive(:epoll)
|
24
|
+
Hatetepe::Server.should_receive(:start) {|opts|
|
25
|
+
opts[:host].should equal(options[:Host])
|
26
|
+
opts[:port].should equal(options[:Port])
|
27
|
+
opts[:app].should equal(app)
|
28
|
+
}
|
29
|
+
block.call
|
30
|
+
}
|
31
|
+
Rack::Handler::Hatetepe.run app, options
|
32
|
+
end
|
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
|
+
it "can be stopped by sending SIGTERM or SIGINT" do
|
46
|
+
EM.should_receive(:run) {|&block| block.call }
|
47
|
+
|
48
|
+
trapped_signals = []
|
49
|
+
Signal.should_receive(:trap).twice {|sig, &block|
|
50
|
+
trapped_signals << sig
|
51
|
+
EM.should_receive :stop
|
52
|
+
block.call
|
53
|
+
}
|
54
|
+
Rack::Handler::Hatetepe.run app
|
55
|
+
|
56
|
+
trapped_signals.should include("TERM")
|
57
|
+
trapped_signals.should include("INT")
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe ".run(app) {|server| ... }" do
|
62
|
+
it "defaults Host to 0.0.0.0 and Port to 8080" do
|
63
|
+
Hatetepe::Server.should_receive(:start) {|opts|
|
64
|
+
opts[:host].should == "0.0.0.0"
|
65
|
+
opts[:port].should == 8080
|
66
|
+
}
|
67
|
+
Rack::Handler::Hatetepe.run(app) { EM.stop }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,255 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "hatetepe/server"
|
3
|
+
|
4
|
+
describe Hatetepe::Server do
|
5
|
+
let(:server) {
|
6
|
+
Hatetepe::Server.allocate.tap {|s|
|
7
|
+
s.send :initialize, config
|
8
|
+
s.post_init
|
9
|
+
s.requests << request
|
10
|
+
}
|
11
|
+
}
|
12
|
+
let(:request) { stub "request", :to_hash => env }
|
13
|
+
let(:env) {
|
14
|
+
{
|
15
|
+
"rack.input" => Hatetepe::Body.new
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
let(:app) { stub "app" }
|
20
|
+
let(:host) { "127.0.4.1" }
|
21
|
+
let(:port) { 8081 }
|
22
|
+
let(:errors) { stub "errors", :<< => nil, :flush => nil }
|
23
|
+
let(:config) {
|
24
|
+
{
|
25
|
+
:app => app,
|
26
|
+
:host => host,
|
27
|
+
:port => port,
|
28
|
+
:errors => errors
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
context ".start(config)" do
|
33
|
+
it "starts an EventMachine server" do
|
34
|
+
args = [host, port, Hatetepe::Server, config]
|
35
|
+
EM.should_receive(:start_server).with(*args) { server }
|
36
|
+
|
37
|
+
Hatetepe::Server.start(config).should equal(server)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "#initialize(config)" do
|
42
|
+
let(:server) { Hatetepe::Server.allocate }
|
43
|
+
let(:builder) { stub "app builder" }
|
44
|
+
|
45
|
+
it "builds the app" do
|
46
|
+
Rack::Builder.stub :new => builder
|
47
|
+
builder.should_receive(:use).with Hatetepe::App
|
48
|
+
builder.should_receive(:run).with app
|
49
|
+
|
50
|
+
server.send :initialize, config
|
51
|
+
server.app.should equal(builder)
|
52
|
+
end
|
53
|
+
|
54
|
+
it "sets up the error stream" do
|
55
|
+
server.send :initialize, config
|
56
|
+
server.errors.should equal(errors)
|
57
|
+
end
|
58
|
+
|
59
|
+
it "uses stderr as default error stream" do
|
60
|
+
config.delete :errors
|
61
|
+
server.send :initialize, config
|
62
|
+
server.errors.should equal($stderr)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "#post_init" do
|
67
|
+
let(:server) {
|
68
|
+
Hatetepe::Server.allocate.tap {|s| s.post_init }
|
69
|
+
}
|
70
|
+
|
71
|
+
it "sets up the request queue" do
|
72
|
+
server.requests.should be_an(Array)
|
73
|
+
server.requests.should be_empty
|
74
|
+
end
|
75
|
+
|
76
|
+
it "sets up the parser" do
|
77
|
+
server.parser.should respond_to(:<<)
|
78
|
+
server.parser.on_request[0].should == server.requests.method(:<<)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "sets up the builder" do
|
82
|
+
server.builder.on_write[0].should == server.method(:send_data)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "#receive_data(data)" do
|
87
|
+
before { server.stub :close_connection_after_writing }
|
88
|
+
|
89
|
+
it "feeds data into the parser" do
|
90
|
+
data = stub("data")
|
91
|
+
server.parser.should_receive(:<<).with data
|
92
|
+
server.receive_data data
|
93
|
+
end
|
94
|
+
|
95
|
+
it "closes the connection if parsing fails" do
|
96
|
+
server.parser.should_receive(:<<).and_raise(Hatetepe::ParserError)
|
97
|
+
server.should_receive :close_connection
|
98
|
+
|
99
|
+
server.receive_data "irrelevant data"
|
100
|
+
end
|
101
|
+
|
102
|
+
it "closes the connection when catching an exception" do
|
103
|
+
server.parser.should_receive(:<<).and_raise
|
104
|
+
server.should_receive :close_connection_after_writing
|
105
|
+
|
106
|
+
server.receive_data ""
|
107
|
+
end
|
108
|
+
|
109
|
+
it "logs caught exceptions" do
|
110
|
+
server.parser.should_receive(:<<).and_raise "error message"
|
111
|
+
errors.should_receive(:<<) {|str|
|
112
|
+
str.should include("error message")
|
113
|
+
}
|
114
|
+
errors.should_receive :flush
|
115
|
+
|
116
|
+
server.receive_data ""
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
context "#process" do
|
121
|
+
it "puts useful stuff into env[]" do
|
122
|
+
app.should_receive(:call) {|e|
|
123
|
+
e.should equal(env)
|
124
|
+
e["rack.url_scheme"].should == "http"
|
125
|
+
e["hatetepe.connection"].should equal(server)
|
126
|
+
e["rack.input"].source.should equal(server)
|
127
|
+
e["rack.errors"].should equal(server.errors)
|
128
|
+
e["rack.multithread"].should be_false
|
129
|
+
e["rack.multiprocess"].should be_false
|
130
|
+
e["rack.run_once"].should be_false
|
131
|
+
|
132
|
+
e["SERVER_NAME"].should == host
|
133
|
+
e["SERVER_NAME"].should_not equal(host)
|
134
|
+
e["SERVER_PORT"].should == String(port)
|
135
|
+
e["HTTP_HOST"].should == "#{host}:#{port}"
|
136
|
+
|
137
|
+
[-1]
|
138
|
+
}
|
139
|
+
server.process
|
140
|
+
end
|
141
|
+
|
142
|
+
it "calls the app within a new Fiber" do
|
143
|
+
outer_fiber = Fiber.current
|
144
|
+
app.should_receive(:call) {
|
145
|
+
Fiber.current.should_not equal(outer_fiber)
|
146
|
+
[-1]
|
147
|
+
}
|
148
|
+
server.process
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context "env[stream.start].call(response)" do
|
153
|
+
let(:previous) { EM::DefaultDeferrable.new }
|
154
|
+
let(:response) {
|
155
|
+
[200, {"Key" => "value"}, Rack::STREAMING]
|
156
|
+
}
|
157
|
+
|
158
|
+
before {
|
159
|
+
server.requests.unshift previous
|
160
|
+
app.stub(:call) {|e| response }
|
161
|
+
request.stub :succeed
|
162
|
+
server.builder.stub :response
|
163
|
+
}
|
164
|
+
|
165
|
+
it "deletes itself from env[] to prevent multiple calls" do
|
166
|
+
app.stub(:call) {|e|
|
167
|
+
e["stream.start"].call response
|
168
|
+
e.key?("stream.start").should be_false
|
169
|
+
[-1]
|
170
|
+
}
|
171
|
+
previous.succeed
|
172
|
+
server.process
|
173
|
+
end
|
174
|
+
|
175
|
+
it "waits for the previous request's response to finish" do
|
176
|
+
server.builder.should_not_receive :response
|
177
|
+
server.process
|
178
|
+
end
|
179
|
+
|
180
|
+
it "initiates the response" do
|
181
|
+
server.builder.should_receive(:response) {|res|
|
182
|
+
res[0].should equal(response[0])
|
183
|
+
res[1]["Key"].should equal(response[1]["Key"])
|
184
|
+
res[1]["Server"].should == "hatetepe/#{Hatetepe::VERSION}"
|
185
|
+
}
|
186
|
+
previous.succeed
|
187
|
+
server.process
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
context "env[stream.send].call(chunk)" do
|
192
|
+
it "passes data to the builder" do
|
193
|
+
app.stub(:call) {|e|
|
194
|
+
e["stream.send"].should == server.builder.method(:body)
|
195
|
+
[-1]
|
196
|
+
}
|
197
|
+
server.process
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
context "env[stream.close].call" do
|
202
|
+
before {
|
203
|
+
server.stub :close_connection
|
204
|
+
server.builder.stub :complete
|
205
|
+
request.stub :succeed
|
206
|
+
}
|
207
|
+
|
208
|
+
it "completes the response" do
|
209
|
+
server.builder.should_receive :complete
|
210
|
+
app.stub(:call) {|e|
|
211
|
+
e["stream.close"].call
|
212
|
+
[-1]
|
213
|
+
}
|
214
|
+
server.process
|
215
|
+
end
|
216
|
+
|
217
|
+
it "succeeds the request" do
|
218
|
+
request.should_receive :succeed
|
219
|
+
app.stub(:call) {|e|
|
220
|
+
e["stream.close"].call
|
221
|
+
[-1]
|
222
|
+
}
|
223
|
+
server.process
|
224
|
+
end
|
225
|
+
|
226
|
+
it "leaves the connection open" do
|
227
|
+
server.should_not_receive :close_connection
|
228
|
+
app.stub(:call) {|e|
|
229
|
+
server.requests << stub("another request")
|
230
|
+
e["stream.close"].call
|
231
|
+
[-1]
|
232
|
+
}
|
233
|
+
server.process
|
234
|
+
end
|
235
|
+
|
236
|
+
it "closes the connection if there are no more requests" do
|
237
|
+
server.should_receive(:close_connection).with true
|
238
|
+
app.stub(:call) {|e|
|
239
|
+
e["stream.close"].call
|
240
|
+
[-1]
|
241
|
+
}
|
242
|
+
server.process
|
243
|
+
end
|
244
|
+
|
245
|
+
it "deletes itself and stream.send from env[] to prevent multiple calls" do
|
246
|
+
app.stub(:call) {|e|
|
247
|
+
e["stream.close"].call
|
248
|
+
e.key?("stream.send").should be_false
|
249
|
+
e.key?("stream.close").should be_false
|
250
|
+
[-1]
|
251
|
+
}
|
252
|
+
server.process
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|