httpkit 0.6.0.pre.3

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.
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