httpkit 0.6.0.pre.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|