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.
- data/.gitignore +7 -0
- data/.rspec +3 -0
- data/.travis.yml +15 -0
- data/.yardopts +2 -0
- data/Gemfile +19 -0
- data/Gemfile.devtools +67 -0
- data/Procfile +1 -0
- data/README.md +66 -0
- data/Rakefile +5 -0
- data/UNLICENSE +24 -0
- data/config/devtools.yml +2 -0
- data/config/flay.yml +3 -0
- data/config/flog.yml +2 -0
- data/config/mutant.yml +3 -0
- data/config/reek.yml +114 -0
- data/config/rubocop.yml +56 -0
- data/config/yardstick.yml +2 -0
- data/examples/echo_server.rb +36 -0
- data/examples/getting_started.rb +34 -0
- data/httpkit.gemspec +27 -0
- data/lib/httpkit/body.rb +67 -0
- data/lib/httpkit/client/keep_alive.rb +10 -0
- data/lib/httpkit/client/timeouts.rb +14 -0
- data/lib/httpkit/client.rb +94 -0
- data/lib/httpkit/connection/eventmachine.rb +72 -0
- data/lib/httpkit/connection/status.rb +28 -0
- data/lib/httpkit/promise.rb +19 -0
- data/lib/httpkit/request.rb +19 -0
- data/lib/httpkit/response.rb +110 -0
- data/lib/httpkit/serializer/encoding.rb +43 -0
- data/lib/httpkit/serializer.rb +75 -0
- data/lib/httpkit/server/keep_alive.rb +58 -0
- data/lib/httpkit/server/timeouts.rb +13 -0
- data/lib/httpkit/server.rb +62 -0
- data/lib/httpkit/support/handler_manager.rb +25 -0
- data/lib/httpkit/support/message.rb +66 -0
- data/lib/httpkit/version.rb +5 -0
- data/lib/httpkit.rb +49 -0
- data/spec/integration/error_handling_spec.rb +76 -0
- data/spec/integration/keep_alive_spec.rb +101 -0
- data/spec/integration/smoke_spec.rb +21 -0
- data/spec/integration/streaming_spec.rb +57 -0
- data/spec/integration/timeouts_spec.rb +82 -0
- data/spec/shared/integration/server_client_pair.rb +29 -0
- data/spec/spec_helper.rb +45 -0
- data/spec/support/handler.rb +48 -0
- data/spec/support/helper.rb +70 -0
- data/spec/unit/client_spec.rb +230 -0
- data/spec/unit/connection/eventmachine_spec.rb +211 -0
- data/spec/unit/connection/status_spec.rb +83 -0
- data/spec/unit/httpkit_spec.rb +41 -0
- data/spec/unit/promise_spec.rb +56 -0
- data/spec/unit/request_spec.rb +35 -0
- data/spec/unit/response_spec.rb +108 -0
- data/spec/unit/server/keep_alive_spec.rb +69 -0
- data/spec/unit/server_spec.rb +128 -0
- data/spec/unit/support/handler_manager_spec.rb +21 -0
- data/spec/unit/support/message_spec.rb +115 -0
- metadata +190 -0
data/lib/httpkit.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'eventmachine'
|
4
|
+
require 'fiber'
|
5
|
+
require 'promise'
|
6
|
+
require 'http/parser'
|
7
|
+
require 'time'
|
8
|
+
|
9
|
+
require 'httpkit/version'
|
10
|
+
|
11
|
+
require 'httpkit/support/handler_manager'
|
12
|
+
require 'httpkit/support/message'
|
13
|
+
|
14
|
+
require 'httpkit/promise'
|
15
|
+
require 'httpkit/body'
|
16
|
+
require 'httpkit/request'
|
17
|
+
require 'httpkit/response'
|
18
|
+
|
19
|
+
require 'httpkit/connection/eventmachine'
|
20
|
+
require 'httpkit/connection/status'
|
21
|
+
require 'httpkit/serializer/encoding'
|
22
|
+
require 'httpkit/serializer'
|
23
|
+
|
24
|
+
require 'httpkit/client'
|
25
|
+
require 'httpkit/client/keep_alive'
|
26
|
+
require 'httpkit/client/timeouts'
|
27
|
+
require 'httpkit/server'
|
28
|
+
require 'httpkit/server/keep_alive'
|
29
|
+
require 'httpkit/server/timeouts'
|
30
|
+
|
31
|
+
module HTTPkit
|
32
|
+
def self.run
|
33
|
+
start do
|
34
|
+
yield
|
35
|
+
stop
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.start
|
40
|
+
EM.run do
|
41
|
+
Fiber.new { yield }.resume
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.stop
|
46
|
+
EM.stop
|
47
|
+
EM.next_tick {}
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe 'Client error handling', reactor: true do
|
6
|
+
include_context :server_client_pair
|
7
|
+
|
8
|
+
let(:served) { client.perform(open_request) }
|
9
|
+
|
10
|
+
subject! { client.close(reason) }
|
11
|
+
|
12
|
+
describe 'on connection error' do
|
13
|
+
let(:reason) { Errno::ENETUNREACH }
|
14
|
+
|
15
|
+
it 'rejects promises' do
|
16
|
+
expect(client).to be_closed
|
17
|
+
expect(client).to be_error
|
18
|
+
expect(client).to be_network_fault
|
19
|
+
|
20
|
+
expect(served).to be_rejected
|
21
|
+
expect(served.reason).to be(reason)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
describe 'on timeout' do
|
26
|
+
let(:reason) { Errno::ETIMEDOUT }
|
27
|
+
|
28
|
+
it 'rejects promises' do
|
29
|
+
expect(client).to be_closed
|
30
|
+
expect(client).to be_error
|
31
|
+
expect(client).to be_timeout
|
32
|
+
|
33
|
+
expect(served).to be_rejected
|
34
|
+
expect(served.reason).to be(reason)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'Server error handling', reactor: true do
|
40
|
+
include_context :server_client_pair
|
41
|
+
|
42
|
+
describe 'on application error' do
|
43
|
+
let(:server_config) { { handlers: [handler] } }
|
44
|
+
let(:handler) { double('handler') }
|
45
|
+
let(:reason) { RuntimeError.new }
|
46
|
+
|
47
|
+
before do
|
48
|
+
allow(handler).to receive(:serve) { raise reason }
|
49
|
+
end
|
50
|
+
|
51
|
+
subject! do
|
52
|
+
client.perform(open_request)
|
53
|
+
tick(2)
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'rejects promises' do
|
57
|
+
expect(server).to be_closed
|
58
|
+
expect(server).to be_error
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe 'on timeout' do
|
63
|
+
subject! do
|
64
|
+
client.perform(open_request)
|
65
|
+
tick(1)
|
66
|
+
server.close(Errno::ETIMEDOUT)
|
67
|
+
tick(1)
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'rejects promises' do
|
71
|
+
expect(server).to be_closed
|
72
|
+
expect(server).to be_error
|
73
|
+
expect(server).to be_timeout
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe reactor: true do
|
6
|
+
include_context :server_client_pair
|
7
|
+
|
8
|
+
let(:server_config) do
|
9
|
+
{ handlers: [HTTPkit::Server::KeepAlive.new, SpecHandler.new] }
|
10
|
+
end
|
11
|
+
let(:client_config) do
|
12
|
+
{ handlers: [HTTPkit::Client::KeepAlive.new] }
|
13
|
+
end
|
14
|
+
|
15
|
+
let!(:served) do
|
16
|
+
requests.map { |request| client.perform(request) }
|
17
|
+
end
|
18
|
+
let(:responses) { served.map(&:sync) }
|
19
|
+
|
20
|
+
describe HTTPkit::Server, 'with KeepAlive' do
|
21
|
+
let(:requests) do
|
22
|
+
[closed_request(:get, '/sleep'), closed_request]
|
23
|
+
end
|
24
|
+
|
25
|
+
# XXX: tends to fail
|
26
|
+
it 'sends responses in the correct order' do
|
27
|
+
expect(responses[0].body.to_s).to eq('/sleep')
|
28
|
+
expect(responses[1].body.to_s).to eq('/')
|
29
|
+
end
|
30
|
+
|
31
|
+
describe 'and HTTP/1.1 request' do
|
32
|
+
let(:requests) { [closed_request] }
|
33
|
+
|
34
|
+
it 'assumes Connection: keep-alive' do
|
35
|
+
expect(client).not_to be_closed
|
36
|
+
expect(server).not_to be_closed
|
37
|
+
|
38
|
+
expect(responses[0].headers).to include('Connection' => 'keep-alive')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'and HTTP/1.0 request' do
|
43
|
+
let(:requests) do
|
44
|
+
[closed_request.tap { |r| r.http_version = 1.0 }]
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'assumes Connection: close' do
|
48
|
+
responses
|
49
|
+
tick(2)
|
50
|
+
expect(client).to be_closed
|
51
|
+
expect(server).to be_closed
|
52
|
+
|
53
|
+
expect(responses[0].headers).to include('Connection' => 'close')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
describe 'and Connection: keep-alive' do
|
58
|
+
let(:requests) do
|
59
|
+
[closed_request(:get, '/', 'Connection' => 'keep-alive')
|
60
|
+
.tap { |r| r.http_version = 1.0 }]
|
61
|
+
end
|
62
|
+
|
63
|
+
it 'keeps the connection open' do
|
64
|
+
expect(client).not_to be_closed
|
65
|
+
expect(server).not_to be_closed
|
66
|
+
|
67
|
+
expect(responses[0].headers).to include('Connection' => 'keep-alive')
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe 'and Connection: close' do
|
72
|
+
let(:requests) do
|
73
|
+
[closed_request(:get, '/', 'Connection' => 'close')]
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'closes the connection after sending the response' do
|
77
|
+
responses
|
78
|
+
tick(2)
|
79
|
+
expect(client).to be_closed
|
80
|
+
expect(server).to be_closed
|
81
|
+
|
82
|
+
expect(responses[0].headers).to include('Connection' => 'close')
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe HTTPkit::Client, 'with KeepAlive' do
|
88
|
+
let(:requests) do
|
89
|
+
[open_request(:get, '/?1'), closed_request(:get, '/?2')]
|
90
|
+
end
|
91
|
+
|
92
|
+
before do
|
93
|
+
requests[0].close
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'sends one request at a time' do
|
97
|
+
expect(responses[0].body.to_s).to eq('/?1')
|
98
|
+
expect(responses[1].body.to_s).to eq('/?2')
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe 'Smoke test', reactor: true do
|
6
|
+
include_context :server_client_pair
|
7
|
+
|
8
|
+
let(:response) { client.request(:get, '/') }
|
9
|
+
let(:request) { intercepted_requests[0] }
|
10
|
+
|
11
|
+
it 'exchanges request and response' do
|
12
|
+
expect(response.status).to be(200)
|
13
|
+
expect(response.headers).to include('Content-Length' => '1')
|
14
|
+
expect(response.body.to_s).to eq('/')
|
15
|
+
|
16
|
+
expect(request.http_method).to be(:get)
|
17
|
+
expect(request.uri).to eq('/')
|
18
|
+
expect(request.headers).to include('Content-Length' => '0')
|
19
|
+
expect(request.body.to_s).to eq('')
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe 'Request body streaming', reactor: true do
|
6
|
+
include_context :server_client_pair
|
7
|
+
|
8
|
+
let(:chunks) { [] }
|
9
|
+
|
10
|
+
before do
|
11
|
+
client.perform(request)
|
12
|
+
tick(2)
|
13
|
+
Fiber.new do
|
14
|
+
intercepted_requests[0].body.each do |chunk|
|
15
|
+
chunks << chunk
|
16
|
+
end
|
17
|
+
end.resume
|
18
|
+
end
|
19
|
+
|
20
|
+
let(:request) { open_request }
|
21
|
+
|
22
|
+
subject! do
|
23
|
+
%w[foo bar baz].each do |chunk|
|
24
|
+
request.body.closed.progress(chunk)
|
25
|
+
tick
|
26
|
+
end
|
27
|
+
request.close
|
28
|
+
tick
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'progressively sends the body' do
|
32
|
+
expect(intercepted_requests[0].headers)
|
33
|
+
.to include('Transfer-Encoding' => 'chunked')
|
34
|
+
|
35
|
+
expect(chunks).to eq(%w[foo bar baz])
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
describe 'Response body streaming', reactor: true do
|
40
|
+
include_context :server_client_pair
|
41
|
+
|
42
|
+
let(:chunks) { [] }
|
43
|
+
|
44
|
+
let(:response) { client.request(:get, '/streaming') }
|
45
|
+
|
46
|
+
subject! { response }
|
47
|
+
|
48
|
+
before do
|
49
|
+
response.body.each do |chunk|
|
50
|
+
chunks << chunk
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'progressively receives the body' do
|
55
|
+
expect(chunks).to eq(%w[foo bar baz])
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
describe HTTPkit::Client, 'in connecting state', reactor: true do
|
6
|
+
# connect to a non-existing host to provoke connection timeout
|
7
|
+
let(:address) { '1.2.3.4' }
|
8
|
+
let(:client_config) do
|
9
|
+
{ address: address, port: random_port,
|
10
|
+
handlers: [HTTPkit::Client::Timeouts.new] }
|
11
|
+
end
|
12
|
+
let!(:client) { HTTPkit::Client.start(client_config) }
|
13
|
+
|
14
|
+
after { client.close }
|
15
|
+
|
16
|
+
it 'times out after 2 seconds' do
|
17
|
+
# only test the config, and don't actually wait for 2 seconds
|
18
|
+
expect(client.config).to include(connect_timeout: 2.0)
|
19
|
+
end
|
20
|
+
|
21
|
+
describe 'with :connect_timeout option' do
|
22
|
+
let(:client_config) do
|
23
|
+
{ address: address, port: random_port, connect_timeout: 0.01,
|
24
|
+
handlers: [HTTPkit::Client::Timeouts.new] }
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'times out after $n seconds' do
|
28
|
+
async_sleep(0.015)
|
29
|
+
if client.network_fault?
|
30
|
+
pending 'Requires network connection'
|
31
|
+
else
|
32
|
+
expect(client).to be_timeout
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe HTTPkit::Client, 'in idle state', reactor: true do
|
39
|
+
include_context :server_client_pair
|
40
|
+
|
41
|
+
let(:client_config) { { handlers: [HTTPkit::Client::Timeouts.new] } }
|
42
|
+
|
43
|
+
it 'times out after 2 seconds' do
|
44
|
+
# only test the config, and don't actually wait for 2 seconds
|
45
|
+
expect(client.config).to include(connect_timeout: 2.0)
|
46
|
+
end
|
47
|
+
|
48
|
+
describe 'with :timeout option' do
|
49
|
+
let(:client_config) do
|
50
|
+
{ timeout: 0.01, handlers: [HTTPkit::Client::Timeouts.new] }
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'times out after $n seconds' do
|
54
|
+
async_sleep(0.015)
|
55
|
+
expect(client).to be_timeout
|
56
|
+
expect(server).to be_closed
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe HTTPkit::Server, 'in idle state', reactor: true do
|
62
|
+
include_context :server_client_pair
|
63
|
+
|
64
|
+
let(:server_config) { { handlers: [HTTPkit::Server::Timeouts.new] } }
|
65
|
+
|
66
|
+
it 'times out after 2 seconds' do
|
67
|
+
# only test the config, and don't actually wait for 2 seconds
|
68
|
+
expect(server.config).to include(timeout: 2.0)
|
69
|
+
end
|
70
|
+
|
71
|
+
describe 'with :timeout option' do
|
72
|
+
let(:server_config) do
|
73
|
+
{ timeout: 0.01, handlers: [HTTPkit::Server::Timeouts.new] }
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'times out after $n seconds' do
|
77
|
+
async_sleep(0.015)
|
78
|
+
expect(client).to be_closed
|
79
|
+
expect(server).to be_timeout
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
shared_context :server_client_pair do
|
4
|
+
let(:server_config) { { handlers: [SpecHandler.new] } }
|
5
|
+
let(:client_config) { {} }
|
6
|
+
let(:interceptor) { double('interceptor') }
|
7
|
+
|
8
|
+
let(:server_and_client) do
|
9
|
+
server_client_pair(server_config, client_config, interceptor)
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:intercepted_requests) { [] }
|
13
|
+
let(:intercepted_promises) { [] }
|
14
|
+
|
15
|
+
before do
|
16
|
+
allow(interceptor).to receive(:serve) { |request, served|
|
17
|
+
intercepted_requests << request
|
18
|
+
intercepted_promises << served
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
let!(:server) { server_and_client[0] }
|
23
|
+
let!(:client) { server_and_client[1] }
|
24
|
+
|
25
|
+
after do
|
26
|
+
server.close
|
27
|
+
client.close
|
28
|
+
end
|
29
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
if ENV['COVERAGE'] == 'true'
|
4
|
+
require 'simplecov'
|
5
|
+
require 'coveralls'
|
6
|
+
|
7
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
8
|
+
SimpleCov::Formatter::HTMLFormatter,
|
9
|
+
Coveralls::SimpleCov::Formatter
|
10
|
+
]
|
11
|
+
|
12
|
+
SimpleCov.start do
|
13
|
+
command_name 'spec:unit'
|
14
|
+
|
15
|
+
add_filter 'config'
|
16
|
+
add_filter 'spec'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'httpkit'
|
21
|
+
require 'weakref'
|
22
|
+
|
23
|
+
require 'awesome_print'
|
24
|
+
|
25
|
+
require 'devtools/spec_helper'
|
26
|
+
|
27
|
+
RSpec.configure do |config|
|
28
|
+
config.include(SpecHelper)
|
29
|
+
|
30
|
+
config.around do |example|
|
31
|
+
if example.metadata[:reactor]
|
32
|
+
EM.run do
|
33
|
+
EM.add_timer(0.5) { raise 'Example timed out' }
|
34
|
+
|
35
|
+
Fiber.new do
|
36
|
+
example.call
|
37
|
+
EM.stop
|
38
|
+
EM.next_tick {}
|
39
|
+
end.resume
|
40
|
+
end
|
41
|
+
else
|
42
|
+
Timeout.timeout(0.5) { example.call }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class SpecHandler
|
4
|
+
def serve(request, served)
|
5
|
+
served.fulfill(response_for(request))
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def response_for(request)
|
11
|
+
case request.uri
|
12
|
+
when '/sleep' then sleep_response
|
13
|
+
when '/close' then close_response
|
14
|
+
when '/streaming' then streaming_response
|
15
|
+
else echo_response(request)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def echo_response(request)
|
20
|
+
HTTPkit::Response.new(200, { 'Content-Type' => 'text/plain' },
|
21
|
+
request.uri)
|
22
|
+
end
|
23
|
+
|
24
|
+
def sleep_response
|
25
|
+
SpecHelper.async_sleep(0.01)
|
26
|
+
HTTPkit::Response.new(200, { 'Content-Type' => 'text/plain' }, '/sleep')
|
27
|
+
end
|
28
|
+
|
29
|
+
def close_response
|
30
|
+
HTTPkit::Response.new(200,
|
31
|
+
{ 'Content-Type' => 'text/plain',
|
32
|
+
'Connection' => 'close' },
|
33
|
+
'/close')
|
34
|
+
end
|
35
|
+
|
36
|
+
def streaming_response
|
37
|
+
response = HTTPkit::Response.new(200, { 'Content-Type' => 'text/plain' },
|
38
|
+
HTTPkit::Body.new)
|
39
|
+
SpecHelper.defer do
|
40
|
+
%w[foo bar baz].each do |chunk|
|
41
|
+
SpecHelper.tick(2)
|
42
|
+
response.body.closed.progress(chunk)
|
43
|
+
end
|
44
|
+
response.close
|
45
|
+
end
|
46
|
+
response
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module SpecHelper
|
4
|
+
def async_sleep(seconds)
|
5
|
+
fiber = Fiber.current
|
6
|
+
EM.add_timer(seconds) { fiber.resume }
|
7
|
+
Fiber.yield
|
8
|
+
end
|
9
|
+
module_function :async_sleep
|
10
|
+
|
11
|
+
def tick(ticks = 1)
|
12
|
+
fiber = Fiber.current
|
13
|
+
EM.next_tick { fiber.resume }
|
14
|
+
Fiber.yield
|
15
|
+
|
16
|
+
tick(ticks - 1) if ticks > 1
|
17
|
+
end
|
18
|
+
module_function :tick
|
19
|
+
|
20
|
+
def defer
|
21
|
+
EM.next_tick do
|
22
|
+
Fiber.new { yield }.resume
|
23
|
+
end
|
24
|
+
end
|
25
|
+
module_function :defer
|
26
|
+
|
27
|
+
def open_request(http_method = :get, uri = '/', headers = {})
|
28
|
+
HTTPkit::Request.new(http_method, uri, headers, HTTPkit::Body.new)
|
29
|
+
end
|
30
|
+
|
31
|
+
def closed_request(*args)
|
32
|
+
open_request(*args).tap(&:close)
|
33
|
+
end
|
34
|
+
|
35
|
+
def open_response(status = 200, headers = {})
|
36
|
+
HTTPkit::Response.new(status, headers, HTTPkit::Body.new)
|
37
|
+
end
|
38
|
+
|
39
|
+
def closed_response(*args)
|
40
|
+
open_response(*args).tap(&:close)
|
41
|
+
end
|
42
|
+
|
43
|
+
def localhost
|
44
|
+
'127.0.0.1'
|
45
|
+
end
|
46
|
+
|
47
|
+
def random_port
|
48
|
+
server = TCPServer.new(localhost, 0)
|
49
|
+
server.addr[1]
|
50
|
+
ensure
|
51
|
+
server.shutdown
|
52
|
+
end
|
53
|
+
|
54
|
+
def server_client_pair(server_config, client_config, interceptor)
|
55
|
+
server = nil
|
56
|
+
inspect_server(server_config, interceptor) { |_, s, _| server = s }
|
57
|
+
|
58
|
+
config = { address: localhost, port: random_port }
|
59
|
+
HTTPkit::Server.start(config.merge(server_config))
|
60
|
+
client = HTTPkit::Client.start(config.merge(client_config))
|
61
|
+
|
62
|
+
tick
|
63
|
+
[server, client]
|
64
|
+
end
|
65
|
+
|
66
|
+
def inspect_server(server_config, interceptor, &block)
|
67
|
+
allow(interceptor).to receive(:setup, &block)
|
68
|
+
server_config[:handlers].unshift(interceptor)
|
69
|
+
end
|
70
|
+
end
|