httpkit 0.6.0.pre.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. data/.gitignore +7 -0
  2. data/.rspec +3 -0
  3. data/.travis.yml +15 -0
  4. data/.yardopts +2 -0
  5. data/Gemfile +19 -0
  6. data/Gemfile.devtools +67 -0
  7. data/Procfile +1 -0
  8. data/README.md +66 -0
  9. data/Rakefile +5 -0
  10. data/UNLICENSE +24 -0
  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 +114 -0
  16. data/config/rubocop.yml +56 -0
  17. data/config/yardstick.yml +2 -0
  18. data/examples/echo_server.rb +36 -0
  19. data/examples/getting_started.rb +34 -0
  20. data/httpkit.gemspec +27 -0
  21. data/lib/httpkit/body.rb +67 -0
  22. data/lib/httpkit/client/keep_alive.rb +10 -0
  23. data/lib/httpkit/client/timeouts.rb +14 -0
  24. data/lib/httpkit/client.rb +94 -0
  25. data/lib/httpkit/connection/eventmachine.rb +72 -0
  26. data/lib/httpkit/connection/status.rb +28 -0
  27. data/lib/httpkit/promise.rb +19 -0
  28. data/lib/httpkit/request.rb +19 -0
  29. data/lib/httpkit/response.rb +110 -0
  30. data/lib/httpkit/serializer/encoding.rb +43 -0
  31. data/lib/httpkit/serializer.rb +75 -0
  32. data/lib/httpkit/server/keep_alive.rb +58 -0
  33. data/lib/httpkit/server/timeouts.rb +13 -0
  34. data/lib/httpkit/server.rb +62 -0
  35. data/lib/httpkit/support/handler_manager.rb +25 -0
  36. data/lib/httpkit/support/message.rb +66 -0
  37. data/lib/httpkit/version.rb +5 -0
  38. data/lib/httpkit.rb +49 -0
  39. data/spec/integration/error_handling_spec.rb +76 -0
  40. data/spec/integration/keep_alive_spec.rb +101 -0
  41. data/spec/integration/smoke_spec.rb +21 -0
  42. data/spec/integration/streaming_spec.rb +57 -0
  43. data/spec/integration/timeouts_spec.rb +82 -0
  44. data/spec/shared/integration/server_client_pair.rb +29 -0
  45. data/spec/spec_helper.rb +45 -0
  46. data/spec/support/handler.rb +48 -0
  47. data/spec/support/helper.rb +70 -0
  48. data/spec/unit/client_spec.rb +230 -0
  49. data/spec/unit/connection/eventmachine_spec.rb +211 -0
  50. data/spec/unit/connection/status_spec.rb +83 -0
  51. data/spec/unit/httpkit_spec.rb +41 -0
  52. data/spec/unit/promise_spec.rb +56 -0
  53. data/spec/unit/request_spec.rb +35 -0
  54. data/spec/unit/response_spec.rb +108 -0
  55. data/spec/unit/server/keep_alive_spec.rb +69 -0
  56. data/spec/unit/server_spec.rb +128 -0
  57. data/spec/unit/support/handler_manager_spec.rb +21 -0
  58. data/spec/unit/support/message_spec.rb +115 -0
  59. metadata +190 -0
@@ -0,0 +1,230 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe HTTPkit::Client do
6
+ let(:client) { described_class.new(config, connection) }
7
+
8
+ let(:config) do
9
+ { address: localhost, port: random_port, handlers: [handler] }
10
+ end
11
+ let(:connection) do
12
+ double('connection', :'on_message=' => nil,
13
+ closed: double('closed', then: nil,
14
+ fulfill: nil,
15
+ pending?: true,
16
+ reason: nil),
17
+ serialize: nil,
18
+ close: nil)
19
+ end
20
+ let(:handler) do
21
+ double('handler', setup: nil, receive: nil, finish: nil)
22
+ end
23
+
24
+ describe '.start' do
25
+ subject { described_class.start(config) }
26
+
27
+ it 'starts an EventMachine-backed client instance' do
28
+ expect(HTTPkit::Connection::EventMachine)
29
+ .to receive(:start_client).with(config, described_class) { client }
30
+ expect(subject).to be(client)
31
+ end
32
+ end
33
+
34
+ describe '#initialize' do
35
+ subject! { client }
36
+
37
+ its(:config) { should be(config) }
38
+
39
+ specify do
40
+ expect(connection).to have_received(:on_message=)
41
+ .with(subject.method(:receive))
42
+ expect(connection.closed).to have_received(:then)
43
+ .with(subject.method(:teardown), subject.method(:teardown))
44
+ end
45
+
46
+ specify do
47
+ expect(handler).to have_received(:setup)
48
+ .with(config, subject, connection)
49
+ end
50
+ end
51
+
52
+ describe '#request' do
53
+ let(:request) do
54
+ double('request', body: double(closed: double('closed', fulfill: nil)))
55
+ end
56
+ let(:response) { double('response') }
57
+ let(:served) { double('served', sync: response) }
58
+
59
+ before do
60
+ allow(HTTPkit::Request).to receive(:new) { request }
61
+ allow(client).to receive(:perform) { served }
62
+ end
63
+
64
+ subject! { client.request(:head, '/wat') }
65
+
66
+ specify do
67
+ expect(HTTPkit::Request).to have_received(:new).with(:head, '/wat')
68
+ expect(client).to have_received(:perform).with(request)
69
+
70
+ expect(subject).to be(response)
71
+ end
72
+ end
73
+
74
+ describe '#perform' do
75
+ let(:request) { double('request', add_extra_headers: nil) }
76
+ let(:closed_reason) { nil }
77
+
78
+ before do
79
+ allow(connection.closed).to receive(:pending?) { closed_reason.nil? }
80
+ allow(connection.closed).to receive(:reason) { closed_reason }
81
+
82
+ @fibers = []
83
+ allow(handler).to receive(:perform) { @fibers << Fiber.current }
84
+ allow(connection).to receive(:serialize) { @fibers << Fiber.current }
85
+ end
86
+
87
+ subject! { client.perform(request) }
88
+
89
+ it 'sends the request on the wire' do
90
+ expect(handler).to have_received(:perform).with(request)
91
+ expect(connection).to have_received(:serialize).with(request)
92
+
93
+ expect(@fibers).not_to include(Fiber.current)
94
+ expect(@fibers.uniq).to be_one
95
+
96
+ expect(request).to have_received(:add_extra_headers)
97
+ .with('User-Agent' => "httpkit/#{HTTPkit::VERSION}",
98
+ 'Host' => "#{config[:address]}:#{config[:port]}")
99
+ end
100
+
101
+ describe 'with closed connection' do
102
+ let(:closed_reason) { double }
103
+
104
+ it 'rejects the request' do
105
+ expect(subject).to be_rejected
106
+ expect(subject.reason).to be(closed_reason)
107
+ expect(connection).to_not have_received(:serialize)
108
+ end
109
+ end
110
+ end
111
+
112
+ describe '#receive' do
113
+ let(:request) { open_request }
114
+ let(:other_request) { open_request }
115
+ let(:response) { open_response }
116
+ let(:other_response) { open_response }
117
+
118
+ describe 'with outstanding request' do
119
+ let!(:served) { client.perform(request) }
120
+ let!(:other_served) { client.perform(other_request) }
121
+
122
+ subject! do
123
+ client.receive(response)
124
+ client.receive(other_response)
125
+ end
126
+
127
+ it 'correlates response with request, and notifies handlers' do
128
+ expect(served).to be_fulfilled
129
+ expect(served.value).to be(response)
130
+ expect(other_served).to be_fulfilled
131
+ expect(other_served.value).to be(other_response)
132
+
133
+ expect(handler).to have_received(:receive).with(request, response)
134
+ expect(handler)
135
+ .to have_received(:receive).with(other_request, other_response)
136
+ end
137
+ end
138
+
139
+ describe 'wiring', reactor: true do
140
+ let(:request) { closed_request }
141
+ let(:response) { open_response }
142
+
143
+ before do
144
+ client.perform(request)
145
+
146
+ allow(client).to receive(:finish)
147
+ end
148
+
149
+ subject! do
150
+ client.receive(response)
151
+ response.close
152
+ tick
153
+ end
154
+
155
+ it 'wires up #finish' do
156
+ client.should have_received(:finish).with(request)
157
+ end
158
+ end
159
+
160
+ [:fulfill, :reject].each do |action|
161
+ describe "after #{action}ed response" do
162
+ let(:request) { WeakRef.new(closed_request) }
163
+ let(:response) { WeakRef.new(open_response) }
164
+
165
+ before do
166
+ client.perform(request.__getobj__)
167
+ client.receive(response.__getobj__)
168
+ end
169
+
170
+ subject do
171
+ response.finished.send(action)
172
+ tick
173
+ RSpec::Mocks.teardown
174
+ GC.start
175
+ end
176
+
177
+ xit 'cleans up' do
178
+ expect { subject }.to change { request.weakref_alive? }.to(false)
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ describe '#finish' do
185
+ let(:request) { open_request }
186
+ let(:other_request) { open_request }
187
+ let(:response) { open_response }
188
+
189
+ before do
190
+ client.perform(request)
191
+ client.perform(other_request)
192
+ end
193
+
194
+ subject! do
195
+ client.finish(request)
196
+ client.receive(response)
197
+ end
198
+
199
+ it 'removes the request' do
200
+ expect(handler).to have_received(:receive).with(other_request, response)
201
+ end
202
+
203
+ it 'notifies the handlers' do
204
+ expect(handler).to have_received(:finish).with(request)
205
+ end
206
+ end
207
+
208
+ describe '#teardown' do
209
+ let(:requests) { [closed_request, closed_request] }
210
+ let(:responses) { [open_response] }
211
+ let(:reason) { double('reason') }
212
+
213
+ let!(:served) do
214
+ requests.map { |request| client.perform(request) }
215
+ end
216
+
217
+ before do
218
+ client.receive(responses[0])
219
+ end
220
+
221
+ subject! { client.teardown(reason) }
222
+
223
+ it 'rejects outstanding requests and responses' do
224
+ expect(responses[0].body.closed).to be_rejected
225
+ expect(responses[0].body.closed.reason).to be(reason)
226
+ expect(served[1]).to be_rejected
227
+ expect(served[1].reason).to be(reason)
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,211 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe HTTPkit::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', setup_body: nil, serialize: nil) }
10
+ let(:message) { open_request }
11
+
12
+ before do
13
+ allow(connection).to receive(:close_connection_after_writing)
14
+ allow(HTTP::Parser).to receive(:new) { parser }
15
+ allow(HTTPkit::Serializer).to receive(:new) { serializer }
16
+ allow(HTTPkit::Support::Message).to receive(:build) { message }
17
+
18
+ connection.instance_variable_set(:@signature, 123)
19
+
20
+ # XXX: 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 '.start_client' do
26
+ let(:config) { { address: double, port: double } }
27
+ let(:client_class) { double('client_class', new: client) }
28
+ let(:client) { double }
29
+
30
+ before do
31
+ allow(EM).to receive(:connect) { connection }
32
+ end
33
+
34
+ subject! { described_class.start_client(config, client_class) }
35
+
36
+ it 'starts a Client connection' do
37
+ expect(EM).to have_received(:connect)
38
+ .with(config[:address], config[:port], described_class)
39
+ expect(client_class).to have_received(:new)
40
+ .with(config, connection)
41
+
42
+ expect(subject).to be(client)
43
+ end
44
+ end
45
+
46
+ describe '.start_server' do
47
+ let(:config) { { address: double, port: double } }
48
+ let(:server_class) { double('server_class', new: nil) }
49
+
50
+ before do
51
+ allow(EM).to receive(:start_server) { |*, cb| cb.call(connection) }
52
+ end
53
+
54
+ subject! { described_class.start_server(config, server_class) }
55
+
56
+ it 'starts a Client connection' do
57
+ expect(EM).to have_received(:start_server)
58
+ .with(config[:address], config[:port], described_class, kind_of(Proc))
59
+ expect(server_class).to have_received(:new)
60
+ .with(config, connection)
61
+ end
62
+ end
63
+
64
+ describe '#initialize' do
65
+ subject! { connection }
66
+
67
+ it 'creates parser' do
68
+ expect(HTTP::Parser).to have_received(:new).with(connection)
69
+ end
70
+
71
+ it 'calls back' do
72
+ expect(callback).to have_received(:call).with(connection)
73
+ end
74
+
75
+ describe 'without callback' do
76
+ let(:connection) { described_class.allocate }
77
+
78
+ subject! { connection.send(:initialize) }
79
+
80
+ it 'is happy as well' do
81
+ expect(connection).to be_a(described_class)
82
+ end
83
+ end
84
+ end
85
+
86
+ describe '#serialize' do
87
+ let(:writer) { connection.method(:send_data) }
88
+
89
+ subject! { connection.serialize(message) }
90
+
91
+ it 'feeds a serializer' do
92
+ expect(HTTPkit::Serializer).to have_received(:new).with(message, writer)
93
+ expect(serializer).to have_received(:setup_body)
94
+ expect(serializer).to have_received(:serialize)
95
+ end
96
+ end
97
+
98
+ describe '#close' do
99
+ subject! { connection.close }
100
+
101
+ it 'closes the connection' do
102
+ expect(connection).to have_received(:close_connection_after_writing)
103
+ expect(connection.closed).to be_pending
104
+ end
105
+
106
+ describe 'with reason' do
107
+ let(:reason) { double('reason') }
108
+
109
+ subject! { connection.close(reason) }
110
+
111
+ it 'rejects the promise' do
112
+ expect(connection.closed).to be_rejected
113
+ expect(connection.closed.reason).to be(reason)
114
+ expect(connection).to have_received(:close_connection_after_writing)
115
+ end
116
+ end
117
+ end
118
+
119
+ describe '#receive_data' do
120
+ let(:data) { double }
121
+
122
+ subject { connection.receive_data(data) }
123
+
124
+ it 'feeds the parser' do
125
+ subject
126
+ expect(parser).to have_received(:<<).with(data)
127
+ end
128
+
129
+ describe 'on error' do
130
+ let(:error) { RuntimeError.new }
131
+
132
+ before do
133
+ allow(parser).to receive(:<<) { raise error }
134
+ allow(connection).to receive(:close)
135
+ end
136
+
137
+ it 'closes the connection' do
138
+ subject
139
+ expect(connection).to have_received(:close).with(error)
140
+ end
141
+ end
142
+ end
143
+
144
+ describe '#unbind' do
145
+ subject! { connection.unbind }
146
+
147
+ it 'fulfills the promise' do
148
+ expect(connection.closed).to be_fulfilled
149
+ expect(connection.closed.value).to be(nil)
150
+ end
151
+
152
+ describe 'with reason' do
153
+ let(:reason) { double('reason') }
154
+
155
+ subject! { connection.unbind(reason) }
156
+
157
+ it 'rejects the promise' do
158
+ expect(connection.closed).to be_rejected
159
+ expect(connection.closed.reason).to be(reason)
160
+ end
161
+ end
162
+ end
163
+
164
+ describe '#on_headers_complete' do
165
+ let(:parse) { double('parse', call: nil) }
166
+
167
+ before { connection.on_message = parse }
168
+
169
+ subject! { connection.on_headers_complete(nil) }
170
+
171
+ it 'builds the message and passes it on' do
172
+ expect(HTTPkit::Support::Message).to have_received(:build).with(parser)
173
+ expect(parse).to have_received(:call).with(message)
174
+ end
175
+ end
176
+
177
+ describe '#on_body' do
178
+ let(:progress) { [] }
179
+ let(:chunks) { %w[body chunk] }
180
+
181
+ before do
182
+ connection.on_message = proc {}
183
+ connection.on_headers_complete(nil)
184
+ end
185
+
186
+ subject! do
187
+ message.body.closed.on_progress { |chunk| progress << chunk }
188
+ connection.on_body(chunks[0])
189
+ connection.on_body(chunks[1])
190
+ message.close
191
+ end
192
+
193
+ it 'passes chunk to message body' do
194
+ expect(message.body.to_s).to eq(chunks.join)
195
+ expect(progress).to eq(chunks)
196
+ end
197
+ end
198
+
199
+ describe '#on_message_complete' do
200
+ before do
201
+ connection.on_message = proc {}
202
+ connection.on_headers_complete(nil)
203
+ end
204
+
205
+ subject! { connection.on_message_complete }
206
+
207
+ it 'closes the body' do
208
+ expect(message.body.closed).to be_fulfilled
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,83 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe HTTPkit::Connection::Status do
6
+ let(:closed) { HTTPkit::Promise.new }
7
+ let(:connection) { double('connection', close: nil, closed: closed) }
8
+
9
+ let(:object) do
10
+ Object.new.tap do |obj|
11
+ obj.extend(described_class)
12
+ obj.instance_variable_set(:@connection, connection)
13
+ end
14
+ end
15
+
16
+ describe '#close with reason' do
17
+ subject! { object.close }
18
+
19
+ it 'forwards to @connection' do
20
+ expect(connection).to have_received(:close).with(nil)
21
+ end
22
+ end
23
+
24
+ describe '#close with reason' do
25
+ let(:reason) { double }
26
+
27
+ subject! { object.close(reason) }
28
+
29
+ it 'forwards to @connection' do
30
+ expect(connection).to have_received(:close).with(reason)
31
+ end
32
+ end
33
+
34
+ describe '#closed?' do
35
+ subject { object.closed? }
36
+ it { should be(false) }
37
+
38
+ describe 'if rejected' do
39
+ before { closed.reject }
40
+ it { should be(true) }
41
+ end
42
+
43
+ describe 'if fulfilled' do
44
+ before { closed.reject }
45
+ it { should be(true) }
46
+ end
47
+ end
48
+
49
+ describe '#error?' do
50
+ subject { object.error? }
51
+ it { should be(false) }
52
+
53
+ describe 'if rejected' do
54
+ before { closed.reject }
55
+ it { should be(true) }
56
+ end
57
+ end
58
+
59
+ describe '#network_fault?' do
60
+ subject { object.network_fault? }
61
+ it { should be(false) }
62
+
63
+ describe 'if rejected with ENETUNREACH' do
64
+ before { closed.reject(Errno::ENETUNREACH) }
65
+ it { should be(true) }
66
+ end
67
+
68
+ describe 'if rejected with ENOTCONN' do
69
+ before { closed.reject(Errno::ENOTCONN) }
70
+ it { should be(true) }
71
+ end
72
+ end
73
+
74
+ describe '#timeout?' do
75
+ subject { object.timeout? }
76
+ it { should be(false) }
77
+
78
+ describe 'if rejected with ENETUNREACH' do
79
+ before { closed.reject(Errno::ETIMEDOUT) }
80
+ it { should be(true) }
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,41 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe HTTPkit, '#run', reactor: false do
6
+ subject! do
7
+ HTTPkit.run do
8
+ @reactor = EM.reactor_running?
9
+ @fiber = Fiber.current
10
+ end
11
+ end
12
+
13
+ it 'starts an EventMachine reactor' do
14
+ expect(@reactor).to be(true)
15
+ end
16
+
17
+ it 'yields in a new Fiber' do
18
+ expect(@fiber).not_to be(Fiber.current)
19
+ end
20
+
21
+ it 'stops the EventMachine reactor' do
22
+ expect(EM.reactor_running?).to be(false)
23
+ end
24
+ end
25
+
26
+ describe HTTPkit, '#stop', reactor: false do
27
+ subject! do
28
+ EM.run do
29
+ EM.add_timer(1.0) { @timer = true }
30
+ HTTPkit.stop
31
+ end
32
+ end
33
+
34
+ it 'stops the EventMachine reactor' do
35
+ expect(EM.reactor_running?).to be(false)
36
+ end
37
+
38
+ it 'stops EventMachine timers' do
39
+ expect(@timer).not_to be(true)
40
+ end
41
+ end
@@ -0,0 +1,56 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe HTTPkit::Promise do
6
+ let(:promise) { described_class.new }
7
+
8
+ describe '#defer' do
9
+ before do
10
+ allow(EM).to receive(:reactor_running?) { running }
11
+ allow(EM).to receive(:next_tick) { |&block| block.call }
12
+
13
+ promise.then { @called = true }
14
+ end
15
+
16
+ subject! { promise.fulfill }
17
+
18
+ describe 'with reactor' do
19
+ let(:running) { true }
20
+
21
+ it 'schedules the block for the next reactor iteration' do
22
+ expect(@called).to be(true)
23
+ end
24
+ end
25
+
26
+ describe 'without reactor' do
27
+ let(:running) { false }
28
+
29
+ it 'does nothing' do
30
+ expect(@called).not_to be(true)
31
+ end
32
+ end
33
+ end
34
+
35
+ describe '#wait', reactor: true do
36
+ let(:promise2) { described_class.new }
37
+
38
+ before do
39
+ promise.then { @fulfilled = true }
40
+ promise2.then(nil, proc { @rejected = true })
41
+
42
+ EM.next_tick { promise.fulfill }
43
+ promise.then { promise2.reject }
44
+ end
45
+
46
+ subject! do
47
+ promise.wait
48
+ promise2.wait
49
+ end
50
+
51
+ it 'suspends the fiber until the promise state changes' do
52
+ expect(@fulfilled).to be(true)
53
+ expect(@rejected).to be(true)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe HTTPkit::Request do
6
+ describe '#initialize' do
7
+ subject do
8
+ HTTPkit::Request.new(:get, '/a?b=c',
9
+ { 'Key' => 'value' }, 'hello').freeze
10
+ end
11
+
12
+ its(:http_method) { should be(:get) }
13
+ its(:uri) { should eq('/a?b=c') }
14
+ its(:headers) { should eq('Key' => 'value') }
15
+
16
+ specify { expect(subject.body.read).to eq('hello') }
17
+
18
+ describe 'defaults' do
19
+ subject { HTTPkit::Request.new(:get, '/') }
20
+
21
+ its(:headers) { should eq({}) }
22
+ its(:http_version) { should eq(1.1) }
23
+
24
+ specify { expect(subject.body.read).to be_empty }
25
+ end
26
+ end
27
+
28
+ describe '#http_version=' do
29
+ subject { HTTPkit::Request.new(:get, '/') }
30
+
31
+ before { subject.http_version = 1.0 }
32
+
33
+ its(:http_version) { should eq(1.0) }
34
+ end
35
+ end