hatetepe 0.5.2 → 0.6.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/.gitignore +7 -0
  2. data/.rspec +3 -0
  3. data/.travis.yml +4 -0
  4. data/.yardopts +1 -0
  5. data/Gemfile +9 -4
  6. data/Gemfile.devtools +55 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +39 -192
  9. data/Rakefile +3 -2
  10. data/bin/hatetepe +35 -2
  11. data/config/devtools.yml +2 -0
  12. data/config/flay.yml +3 -0
  13. data/config/flog.yml +2 -0
  14. data/config/mutant.yml +3 -0
  15. data/config/reek.yml +103 -0
  16. data/config/rubocop.yml +58 -0
  17. data/config/yardstick.yml +2 -0
  18. data/hatetepe.gemspec +23 -27
  19. data/lib/hatetepe/client/keep_alive.rb +59 -0
  20. data/lib/hatetepe/client/timeouts.rb +19 -0
  21. data/lib/hatetepe/client.rb +54 -302
  22. data/lib/hatetepe/connection/eventmachine.rb +61 -0
  23. data/lib/hatetepe/connection/status.rb +28 -0
  24. data/lib/hatetepe/errors.rb +7 -0
  25. data/lib/hatetepe/promise.rb +86 -0
  26. data/lib/hatetepe/request.rb +15 -39
  27. data/lib/hatetepe/response.rb +82 -22
  28. data/lib/hatetepe/serializer/encoding.rb +58 -0
  29. data/lib/hatetepe/serializer.rb +61 -0
  30. data/lib/hatetepe/server/keep_alive.rb +53 -13
  31. data/lib/hatetepe/server/timeouts.rb +17 -0
  32. data/lib/hatetepe/server.rb +37 -85
  33. data/lib/hatetepe/support/handlers.rb +19 -0
  34. data/lib/hatetepe/support/keep_alive.rb +14 -0
  35. data/lib/hatetepe/support/message.rb +40 -0
  36. data/lib/hatetepe/version.rb +3 -1
  37. data/lib/hatetepe.rb +29 -7
  38. data/spec/integration/error_handling_spec.rb +7 -0
  39. data/spec/integration/keep_alive_spec.rb +106 -0
  40. data/spec/integration/smoke_spec.rb +21 -0
  41. data/spec/integration/streaming_spec.rb +61 -0
  42. data/spec/integration/timeouts_spec.rb +82 -0
  43. data/spec/shared/integration/server_client_pair.rb +26 -0
  44. data/spec/spec_helper.rb +41 -10
  45. data/spec/support/handler.rb +55 -0
  46. data/spec/support/helper.rb +74 -0
  47. data/spec/unit/client_spec.rb +115 -156
  48. data/spec/unit/connection/eventmachine_spec.rb +146 -0
  49. data/spec/unit/request_spec.rb +35 -0
  50. data/spec/unit/response_spec.rb +42 -0
  51. data/spec/unit/server_spec.rb +65 -100
  52. data/spec/unit/support/keep_alive_spec.rb +52 -0
  53. data/spec/unit/support/message_spec.rb +41 -0
  54. metadata +68 -103
  55. data/Gemfile.lock +0 -46
  56. data/LICENSE +0 -19
  57. data/Procfile +0 -1
  58. data/config.ru +0 -7
  59. data/examples/parallel_requests.rb +0 -32
  60. data/lib/hatetepe/body.rb +0 -182
  61. data/lib/hatetepe/builder.rb +0 -171
  62. data/lib/hatetepe/cli.rb +0 -61
  63. data/lib/hatetepe/connection.rb +0 -73
  64. data/lib/hatetepe/events.rb +0 -35
  65. data/lib/hatetepe/message.rb +0 -13
  66. data/lib/hatetepe/parser.rb +0 -83
  67. data/lib/hatetepe/server/pipeline.rb +0 -20
  68. data/lib/hatetepe/server/rack_app.rb +0 -39
  69. data/lib/rack/handler/hatetepe.rb +0 -33
  70. data/spec/integration/cli/start_spec.rb +0 -113
  71. data/spec/integration/client/keep_alive_spec.rb +0 -23
  72. data/spec/integration/client/timeout_spec.rb +0 -97
  73. data/spec/integration/server/keep_alive_spec.rb +0 -27
  74. data/spec/integration/server/timeout_spec.rb +0 -51
  75. data/spec/unit/body_spec.rb +0 -205
  76. data/spec/unit/builder_spec.rb +0 -372
  77. data/spec/unit/connection_spec.rb +0 -62
  78. data/spec/unit/events_spec.rb +0 -96
  79. data/spec/unit/parser_spec.rb +0 -209
  80. data/spec/unit/rack_handler_spec.rb +0 -60
@@ -1,211 +1,170 @@
1
- require "spec_helper"
2
- require "hatetepe/client"
1
+ # encoding: utf-8
3
2
 
4
- describe Hatetepe::Client do
5
- let :config do
6
- {
7
- :host => "127.0.0.1",
8
- :port => 4242
9
- }
10
- end
3
+ require 'spec_helper'
11
4
 
12
- let :client do
13
- Object.new.tap do |client|
14
- client.extend(Hatetepe::Client)
15
- client.stub(:send_data)
16
- client.stub(:comm_inactivity_timeout=)
17
- client.stub(:pending_connect_timeout=)
18
- client.stub(:send_request) { response }
5
+ describe Hatetepe::Client do
6
+ let(:client) { described_class.new(config) }
19
7
 
20
- client.send(:initialize, config)
21
- client.post_init
22
- end
8
+ let(:config) do
9
+ { address: localhost, port: random_port, handlers: [handler_class] }
10
+ end
11
+ let(:connection) do
12
+ double('connection', parse: nil,
13
+ closed: double('closed', then: nil),
14
+ serialize: nil,
15
+ close: nil)
23
16
  end
17
+ let(:handler_class) { double('handler_class', new: handler) }
18
+ let(:handler) { double('handler', post_init: nil, receive: nil) }
24
19
 
25
- describe ".start" do
26
- it "starts a new client" do
27
- EM.should_receive(:connect).
28
- with(config[:host], config[:port], Hatetepe::Client, config).
29
- and_return(client)
30
- Hatetepe::Client.start(config).should equal(client)
31
- end
20
+ before do
21
+ allow(EM).to receive(:connect) { connection }
32
22
  end
33
23
 
34
- describe "#stop" do
35
- it "waits for all requests to finish and closes the connection" do
36
- client.should_receive(:wait).ordered
37
- client.should_receive(:stop!).ordered
38
- client.stop
24
+ describe '#initialize' do
25
+ subject! { client }
26
+
27
+ its(:config) { should be(config) }
28
+
29
+ specify do
30
+ expect(EM).to have_received(:connect)
31
+ .with(config[:address], config[:port],
32
+ Hatetepe::Connection::EventMachine)
33
+ expect(connection).to have_received(:parse)
34
+ .with(subject.method(:receive))
35
+ expect(connection.closed).to have_received(:then)
36
+ .with(subject.method(:teardown))
39
37
  end
40
- end
41
38
 
42
- describe "#stop!" do
43
- it "closes the connection" do
44
- client.should_receive(:close_connection)
45
- client.stop!
39
+ specify do
40
+ expect(handler_class).to have_received(:new)
41
+ .with(config, subject, connection)
42
+ expect(handler).to have_received(:post_init)
46
43
  end
47
44
  end
48
45
 
49
- describe "#wait" do
50
- let :requests do
51
- [ Hatetepe::Request.new(:get, "/"), Hatetepe::Request.new(:post, "/") ]
46
+ describe '#request' do
47
+ let(:request) do
48
+ double('request', finished: double('finished', fulfill: nil),
49
+ served: double('served', sync: response))
52
50
  end
51
+ let(:response) { double('response') }
53
52
 
54
53
  before do
55
- client.unstub(:send_request)
56
- client << requests[0]
57
- client << requests[1]
58
- end
59
-
60
- it "waits for all requests to finish" do
61
- returned = false
62
- Fiber.new do
63
- client.wait
64
- returned = true
65
- end.resume
66
-
67
- returned.should be_false
68
-
69
- requests.each(&:succeed)
70
- returned.should be_true
54
+ allow(Hatetepe::Request).to receive(:new) { request }
55
+ allow(client).to receive(:perform)
71
56
  end
72
- end
73
57
 
74
- describe ".request" do
75
- let(:client) { double("client", request: res, stop: nil) }
76
- let(:headers) { double("headers") }
77
- let(:body) { double("body") }
78
- let(:res) { double("response") }
79
- let(:response) { Hatetepe::Client.request(:put, "/test", headers, body) }
58
+ subject! { client.request(:head, '/wat') }
80
59
 
81
- before { Hatetepe::Client.stub(start: client) }
60
+ specify do
61
+ expect(Hatetepe::Request).to have_received(:new).with(:head, '/wat')
62
+ expect(request.finished).to have_received(:fulfill)
63
+ expect(client).to have_received(:perform).with(request)
82
64
 
83
- it "it returns the response" do
84
- client.should_receive(:request).with(:put, URI("/test"), headers, body)
85
- response.should equal(res)
65
+ expect(subject).to be(response)
86
66
  end
87
67
  end
88
68
 
89
- describe "#request" do
90
- let(:body) { [ "Hello,", " world!" ] }
91
- let(:headers) { { "Content-Type" => "text/plain" } }
92
- let(:request) { double("request") }
93
- let(:response) { double("response") }
69
+ describe '#perform' do
70
+ let(:request) { double('request') }
94
71
 
95
72
  before do
96
- client.stub(:<<)
97
- Hatetepe::Request.stub(:new => request)
73
+ @fibers = []
74
+ allow(handler).to receive(:perform) { @fibers << Fiber.current }
75
+ allow(connection).to receive(:serialize) { @fibers << Fiber.current }
98
76
  end
99
77
 
100
- it "sends the request" do
101
- Hatetepe::Request.should_receive(:new) do |verb, uri, headers, body|
102
- verb.should eq(:head)
103
- uri.path.should eq("/test")
104
- uri.query.should eq("key=value")
105
- headers["Content-Type"].should eq("text/plain")
106
- Enumerator.new(body).to_a.join.should eq("Hello, world!")
107
-
108
- request
109
- end
110
-
111
- client.should_receive(:<<).with(request)
112
- EM::Synchrony.should_receive(:sync).with(request)
78
+ subject! { client.perform(request) }
113
79
 
114
- client.request(:head, "/test?key=value", headers, body)
115
- end
80
+ specify do
81
+ expect(handler).to have_received(:perform).with(request)
82
+ expect(connection).to have_received(:serialize).with(request)
116
83
 
117
- it "returns the response" do
118
- EM::Synchrony.stub(:sync => response)
119
- client.request(:get, "/").should equal(response)
84
+ expect(@fibers).not_to include(Fiber.current)
85
+ expect(@fibers.uniq).to be_one
120
86
  end
121
87
  end
122
88
 
123
- describe "#request!" do
124
- subject do
125
- proc { client.request!(:get, "/") }
89
+ describe '#close' do
90
+ subject! { client.close }
91
+
92
+ specify do
93
+ expect(connection).to have_received(:close)
126
94
  end
95
+ end
127
96
 
128
- let(:status) { 200 }
129
- let(:response) { double("response", :status => status) }
97
+ describe '#receive' do
98
+ let(:request) { default_request }
99
+ let(:response) { default_response }
130
100
 
131
- before do
132
- client.stub(:request).with(:get, "/", {}, []) { response }
133
- end
101
+ describe 'with outstanding request' do
102
+ before { client.perform(request) }
134
103
 
135
- it "forwards to #request" do
136
- subject.call.should eq(response)
137
- end
104
+ subject! { client.receive(response) }
138
105
 
139
- describe "for a 4xx response" do
140
- let(:status) { 404 }
106
+ it 'correlates response with request, and notifies handlers' do
107
+ expect(request.served).to be_fulfilled
108
+ expect(request.served.value).to be(response)
141
109
 
142
- it "raises a ClientError" do
143
- subject.should raise_error(Hatetepe::ClientError)
110
+ expect(handler).to have_received(:receive).with(request, response)
144
111
  end
145
112
  end
146
113
 
147
- describe "for a 5xx response" do
148
- let(:status) { 502 }
114
+ describe 'without outstanding request' do
115
+ subject { client.receive(response) }
149
116
 
150
- it "raises a ServerError" do
151
- subject.should raise_error(Hatetepe::ServerError)
117
+ it 'fails' do
118
+ expect { subject }.to raise_error(Hatetepe::ClientError, /correlate/)
152
119
  end
153
120
  end
154
121
 
155
- describe "if no response could be received" do
156
- it "raises a ServerError" do
157
- client.stub(:request) { nil }
158
- subject.should raise_error(Hatetepe::RequestError)
122
+ [:fulfill, :reject].each do |action|
123
+ describe "after #{action}ed response" do
124
+ let(:request) { WeakRef.new(finished_request) }
125
+ let(:response) { WeakRef.new(default_response) }
126
+
127
+ before do
128
+ client.perform(request.__getobj__)
129
+ client.receive(response.__getobj__)
130
+ end
131
+
132
+ subject do
133
+ response.finished.send(action)
134
+ tick
135
+ RSpec::Mocks.teardown
136
+ GC.start
137
+ end
138
+
139
+ it 'cleans up' do
140
+ if RUBY_VERSION == '1.9.3' && RUBY_PATCHLEVEL == 392
141
+ expect { subject }.to change { request.weakref_alive? }.to(false)
142
+ else
143
+ pending
144
+ end
145
+ end
159
146
  end
160
147
  end
161
148
  end
162
149
 
163
- describe "#<<" do
164
- let :request do
165
- Hatetepe::Request.new :head, "/test"
166
- end
167
-
168
- describe "if the response is a success" do
169
- let(:response) { Hatetepe::Response.new(307) }
150
+ describe '#teardown' do
151
+ let(:requests) { [finished_request, finished_request] }
152
+ let(:responses) { [default_response] }
153
+ let(:reason) { double }
170
154
 
171
- it "succeeds the request" do
172
- request.should_receive(:succeed).with(response)
173
- client << request
174
- end
175
- end
176
-
177
- describe "if the response is a failure" do
178
- let(:response) { Hatetepe::Response.new(502) }
179
-
180
- it "fails the request" do
181
- request.should_receive(:fail).with(response)
182
- client << request
183
- end
155
+ before do
156
+ client.perform(requests[0])
157
+ client.perform(requests[1])
158
+ client.receive(responses[0])
184
159
  end
185
160
 
186
- describe "if there is no response" do
187
- let(:response) { nil }
161
+ subject! { client.teardown(reason) }
188
162
 
189
- it "fails the request" do
190
- request.should_receive(:fail)
191
- client << request
192
- end
163
+ it 'rejects outstanding requests and responses' do
164
+ expect(responses[0].finished).to be_rejected
165
+ expect(responses[0].finished.reason).to be(reason)
166
+ expect(requests[1].served).to be_rejected
167
+ expect(requests[1].served.reason).to be(reason)
193
168
  end
194
169
  end
195
170
  end
196
-
197
- describe Hatetepe::Client, "(EventMachine API)" do
198
- describe "#initialize"
199
-
200
- describe "#post_init"
201
-
202
- describe "#receive_data"
203
-
204
- describe "#unbind"
205
- end
206
-
207
- describe Hatetepe::Client, "(private API)" do
208
- describe "#send_request"
209
-
210
- describe "#receive_response"
211
- end
@@ -0,0 +1,146 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Hatetepe::Connection::EventMachine do
6
+ let(:connection) { described_class.allocate }
7
+ let(:callback) { double('callback', call: nil) }
8
+ let(:parser) { double('parser', :<< => nil) }
9
+ let(:serializer) { double('serializer', serialize: nil) }
10
+ let(:message) { default_request }
11
+
12
+ before do
13
+ allow(connection).to receive(:close_connection_after_writing)
14
+ allow(HTTP::Parser).to receive(:new) { parser }
15
+ allow(Hatetepe::Serializer).to receive(:new) { serializer }
16
+ allow(Hatetepe::Support::Message).to receive(:build) { message }
17
+
18
+ connection.instance_variable_set(:@signature, 123)
19
+
20
+ # initialize separately, EM::Connection overloads .new
21
+ # this smell tells us that we should decouple from EM::Connection
22
+ connection.send(:initialize, callback)
23
+ end
24
+
25
+ describe '#initialize' do
26
+ subject! { connection }
27
+
28
+ it 'creates parser' do
29
+ expect(HTTP::Parser).to have_received(:new).with(connection)
30
+ end
31
+
32
+ it 'calls back' do
33
+ expect(callback).to have_received(:call).with(connection)
34
+ end
35
+
36
+ describe 'without callback' do
37
+ let(:connection) { described_class.allocate }
38
+
39
+ subject! { connection.send(:initialize) }
40
+
41
+ it 'is happy as well' do
42
+ expect(connection).to be_a(described_class)
43
+ end
44
+ end
45
+ end
46
+
47
+ describe '#serialize' do
48
+ let(:writer) { connection.method(:send_data) }
49
+
50
+ subject! { connection.serialize(message) }
51
+
52
+ it 'feeds a serializer' do
53
+ expect(Hatetepe::Serializer).to have_received(:new).with(message, writer)
54
+ expect(serializer).to have_received(:serialize)
55
+ end
56
+ end
57
+
58
+ describe '#close' do
59
+ subject! { connection.close }
60
+
61
+ it 'closes the connection' do
62
+ expect(connection).to have_received(:close_connection_after_writing)
63
+ end
64
+ end
65
+
66
+ describe '#receive_data' do
67
+ let(:data) { double }
68
+
69
+ subject! { connection.receive_data(data) }
70
+
71
+ it 'feeds the parser' do
72
+ expect(parser).to have_received(:<<).with(data)
73
+ end
74
+ end
75
+
76
+ describe '#unbind' do
77
+ let(:reason) { double('reason') }
78
+
79
+ describe 'following #close' do
80
+ before { connection.close }
81
+
82
+ subject! { connection.unbind(reason) }
83
+
84
+ it 'fulfills the promise' do
85
+ expect(connection.closed).to be_fulfilled
86
+ expect(connection.closed.value).to be(:self)
87
+ end
88
+ end
89
+
90
+ describe 'following remote termination' do
91
+ subject! { connection.unbind(reason) }
92
+
93
+ it 'fulfills the promise' do
94
+ expect(connection.closed).to be_fulfilled
95
+ expect(connection.closed.value).to be(reason)
96
+ end
97
+ end
98
+ end
99
+
100
+ describe '#on_headers_complete' do
101
+ let(:parse) { double('parse', call: nil) }
102
+
103
+ before { connection.parse(parse) }
104
+
105
+ subject! { connection.on_headers_complete(nil) }
106
+
107
+ it 'builds the message and passes it on' do
108
+ expect(Hatetepe::Support::Message).to have_received(:build).with(parser)
109
+ expect(parse).to have_received(:call).with(message)
110
+ end
111
+ end
112
+
113
+ describe '#on_body' do
114
+ let(:progress) { [] }
115
+ let(:chunks) { %w[body chunk] }
116
+
117
+ before do
118
+ connection.parse(proc {})
119
+ connection.on_headers_complete(nil)
120
+ end
121
+
122
+ subject! do
123
+ message.finished.on_progress { |chunk| progress << chunk }
124
+ connection.on_body(chunks[0])
125
+ connection.on_body(chunks[1])
126
+ end
127
+
128
+ it 'passes chunk to message body' do
129
+ expect(message.body).to eq(chunks.join)
130
+ expect(progress).to eq(chunks)
131
+ end
132
+ end
133
+
134
+ describe '#on_message_complete' do
135
+ before do
136
+ connection.parse(proc {})
137
+ connection.on_headers_complete(nil)
138
+ end
139
+
140
+ subject! { connection.on_message_complete }
141
+
142
+ it 'fulfills the promise' do
143
+ expect(message.finished).to be_fulfilled
144
+ end
145
+ end
146
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Hatetepe::Request do
6
+ describe '#initialize' do
7
+ subject do
8
+ Hatetepe::Request.new(:get, '/', { 'Key' => 'value' }, 'hello').freeze
9
+ end
10
+
11
+ its(:http_method) { should be(:get) }
12
+ its(:uri) { should eq('/') }
13
+ its(:headers) { should eq('Key' => 'value') }
14
+ its(:body) { should eq('hello') }
15
+
16
+ its(:finished) { should be_a(Hatetepe::Promise) }
17
+ its(:served) { should be_a(Hatetepe::Promise) }
18
+
19
+ describe 'defaults' do
20
+ subject { Hatetepe::Request.new(:get, '/') }
21
+
22
+ its(:headers) { should eq({}) }
23
+ its(:body) { should eq('') }
24
+ its(:http_version) { should eq(1.1) }
25
+ end
26
+ end
27
+
28
+ describe '#http_version=' do
29
+ subject { Hatetepe::Request.new(:get, '/') }
30
+
31
+ before { subject.http_version = 1.0 }
32
+
33
+ its(:http_version) { should eq(1.0) }
34
+ end
35
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Hatetepe::Response do
6
+ describe '#initialize' do
7
+ subject do
8
+ Hatetepe::Response.new(200, { 'Key' => 'value' }, 'hello').freeze
9
+ end
10
+
11
+ its(:status) { should be(200) }
12
+ its(:headers) { should eq('Key' => 'value') }
13
+ its(:body) { should eq('hello') }
14
+
15
+ its(:finished) { should be_a(Hatetepe::Promise) }
16
+
17
+ its(:status_name) { should eq('OK') }
18
+
19
+ describe 'defaults' do
20
+ subject { Hatetepe::Response.new(200) }
21
+
22
+ its(:headers) { should eq({}) }
23
+ its(:body) { should eq('') }
24
+ its(:http_version) { should eq(1.1) }
25
+ end
26
+
27
+ describe 'unknown status' do
28
+ subject { Hatetepe::Response.new(-1) }
29
+
30
+ its(:status) { should be(-1) }
31
+ its(:status_name) { should eq('Unknown Status') }
32
+ end
33
+ end
34
+
35
+ describe '#http_version=' do
36
+ subject { Hatetepe::Response.new(200) }
37
+
38
+ before { subject.http_version = 1.0 }
39
+
40
+ its(:http_version) { should eq(1.0) }
41
+ end
42
+ end