httpkit 0.6.0.pre.3 → 0.6.0.pre.5
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.
- checksums.yaml +15 -0
- data/.rspec +0 -1
- data/.travis.yml +5 -4
- data/Gemfile +0 -2
- data/Gemfile.devtools +27 -22
- data/README.md +11 -13
- data/config/flay.yml +2 -2
- data/config/flog.yml +1 -1
- data/config/reek.yml +11 -5
- data/config/rubocop.yml +47 -5
- data/examples/echo_server.rb +2 -2
- data/examples/getting_started.rb +16 -10
- data/httpkit.gemspec +5 -4
- data/lib/httpkit.rb +15 -5
- data/lib/httpkit/body.rb +44 -33
- data/lib/httpkit/client.rb +52 -23
- data/lib/httpkit/client/body_handler.rb +21 -0
- data/lib/httpkit/client/{keep_alive.rb → keep_alive_handler.rb} +1 -1
- data/lib/httpkit/client/mandatory_handler.rb +29 -0
- data/lib/httpkit/client/{timeouts.rb → timeouts_handler.rb} +1 -1
- data/lib/httpkit/connection/eventmachine.rb +4 -4
- data/lib/httpkit/request.rb +46 -10
- data/lib/httpkit/response.rb +37 -5
- data/lib/httpkit/serializer.rb +33 -25
- data/lib/httpkit/server.rb +14 -19
- data/lib/httpkit/server/body_handler.rb +25 -0
- data/lib/httpkit/server/{keep_alive.rb → keep_alive_handler.rb} +22 -13
- data/lib/httpkit/server/mandatory_handler.rb +23 -0
- data/lib/httpkit/server/{timeouts.rb → timeouts_handler.rb} +1 -1
- data/lib/httpkit/support/handler_manager.rb +8 -5
- data/lib/httpkit/support/message.rb +28 -15
- data/lib/httpkit/version.rb +1 -1
- data/spec/integration/keep_alive_spec.rb +6 -7
- data/spec/integration/smoke_spec.rb +4 -4
- data/spec/integration/streaming_spec.rb +2 -3
- data/spec/integration/timeouts_spec.rb +6 -6
- data/spec/shared/integration/server_client_pair.rb +1 -1
- data/spec/spec_helper.rb +3 -2
- data/spec/support/handler.rb +1 -1
- data/spec/support/helper.rb +6 -4
- data/spec/unit/body_spec.rb +6 -0
- data/spec/unit/client/keep_alive_handler_spec.rb +6 -0
- data/spec/unit/client/mandatory_handler_spec.rb +31 -0
- data/spec/unit/client/timeouts_handler_spec.rb +6 -0
- data/spec/unit/client_spec.rb +83 -34
- data/spec/unit/connection/eventmachine_spec.rb +12 -13
- data/spec/unit/httpkit_spec.rb +65 -24
- data/spec/unit/promise_spec.rb +1 -1
- data/spec/unit/request_spec.rb +2 -10
- data/spec/unit/response_spec.rb +7 -15
- data/spec/unit/serializer_spec.rb +83 -0
- data/spec/unit/server/{keep_alive_spec.rb → keep_alive_handler_spec.rb} +5 -2
- data/spec/unit/server/mandatory_handler_spec.rb +30 -0
- data/spec/unit/server/timeouts_handler_spec.rb +6 -0
- data/spec/unit/server_spec.rb +26 -32
- data/spec/unit/support/handler_manager_spec.rb +38 -7
- data/spec/unit/support/message_spec.rb +45 -20
- metadata +57 -36
- data/lib/httpkit/serializer/encoding.rb +0 -43
data/lib/httpkit/server.rb
CHANGED
@@ -2,10 +2,6 @@
|
|
2
2
|
|
3
3
|
module HTTPkit
|
4
4
|
class Server
|
5
|
-
SERVER = 'Server'.freeze
|
6
|
-
SERVER_VALUE = "httpkit/#{HTTPkit::VERSION}".freeze
|
7
|
-
DATE = 'Date'.freeze
|
8
|
-
|
9
5
|
def self.start(config)
|
10
6
|
Connection::EventMachine.start_server(config, self)
|
11
7
|
end
|
@@ -16,7 +12,9 @@ module HTTPkit
|
|
16
12
|
attr_reader :config
|
17
13
|
|
18
14
|
def initialize(config, connection)
|
15
|
+
@sequence = 0
|
19
16
|
@config = config
|
17
|
+
(@config[:handlers] ||= []).push(MandatoryHandler.new, BodyHandler.new)
|
20
18
|
|
21
19
|
setup_connection(connection)
|
22
20
|
setup_handlers
|
@@ -26,37 +24,34 @@ module HTTPkit
|
|
26
24
|
served = Promise.new
|
27
25
|
served.then { |response| respond(request, response) }
|
28
26
|
|
29
|
-
|
27
|
+
serve!(request, served).resume
|
30
28
|
end
|
31
29
|
|
32
30
|
def respond(request, response)
|
33
31
|
fiber = Fiber.new do
|
34
|
-
|
35
|
-
|
36
|
-
|
32
|
+
request, response = @handlers.invoke(:respond, request, response)
|
33
|
+
@connection.serialize(response)
|
34
|
+
finish(request)
|
37
35
|
end
|
38
36
|
fiber.resume
|
39
37
|
end
|
40
38
|
|
41
39
|
def finish(request)
|
42
|
-
@handlers.
|
40
|
+
@handlers.invoke(:finish, request)
|
43
41
|
end
|
44
42
|
|
45
43
|
private
|
46
44
|
|
47
|
-
def add_extra_headers(response)
|
48
|
-
date = Time.now.httpdate
|
49
|
-
response.add_extra_headers(SERVER => SERVER_VALUE, DATE => date)
|
50
|
-
end
|
51
|
-
|
52
|
-
def respond!(request, response)
|
53
|
-
@handlers.notify(:respond, request, response)
|
54
|
-
@connection.serialize(response)
|
55
|
-
end
|
56
|
-
|
57
45
|
def setup_connection(connection)
|
58
46
|
@connection = connection
|
59
47
|
@connection.on_message = method(:serve)
|
60
48
|
end
|
49
|
+
|
50
|
+
def serve!(request, served)
|
51
|
+
Fiber.new do
|
52
|
+
request.sequence(self, @sequence += 1)
|
53
|
+
request, _ = @handlers.invoke(:serve, request, served)
|
54
|
+
end
|
55
|
+
end
|
61
56
|
end
|
62
57
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module HTTPkit
|
4
|
+
class Server::BodyHandler
|
5
|
+
def respond(request, response)
|
6
|
+
response = response.with_headers(response.body_headers)
|
7
|
+
|
8
|
+
yield request, banana_response(request, response)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def banana_response(request, response)
|
14
|
+
if !response.body_included? || head?(request)
|
15
|
+
response.with_body('')
|
16
|
+
else
|
17
|
+
response
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def head?(request)
|
22
|
+
request.http_method == :head
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,48 +1,57 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module HTTPkit
|
4
|
-
class Server::
|
4
|
+
class Server::KeepAliveHandler
|
5
5
|
CONNECTION = 'Connection'.freeze
|
6
6
|
CLOSE = 'close'.freeze
|
7
7
|
KEEP_ALIVE = 'keep-alive'.freeze
|
8
8
|
|
9
9
|
def setup(_, server, _)
|
10
10
|
@server = server
|
11
|
-
@requests
|
11
|
+
@requests = {}
|
12
12
|
end
|
13
13
|
|
14
14
|
def serve(request, served)
|
15
|
-
@requests[request] =
|
16
|
-
@previous = request
|
15
|
+
@requests[sequence(request)] = served
|
17
16
|
end
|
18
17
|
|
19
18
|
def respond(request, response)
|
20
19
|
synchronize_responses(request)
|
21
20
|
|
22
21
|
if close_connection?(request)
|
23
|
-
|
24
|
-
# XXX: possible race condition with other locations waiting for this
|
25
|
-
response.closed { @server.close }
|
22
|
+
yield request, close_response(response)
|
26
23
|
else
|
27
|
-
|
24
|
+
yield request, keep_alive_response(response)
|
28
25
|
end
|
29
26
|
end
|
30
27
|
|
31
28
|
def finish(request)
|
32
|
-
@requests.delete(request)
|
29
|
+
@requests.delete(sequence(request))
|
33
30
|
end
|
34
31
|
|
35
32
|
private
|
36
33
|
|
37
|
-
def
|
38
|
-
|
39
|
-
|
34
|
+
def sequence(request)
|
35
|
+
request.sequence(@server)
|
36
|
+
end
|
37
|
+
|
38
|
+
def close_response(response)
|
39
|
+
# XXX: possible race condition with other locations waiting for this
|
40
|
+
response.closed { @server.close }
|
41
|
+
response.with_headers(CONNECTION => CLOSE)
|
42
|
+
end
|
40
43
|
|
44
|
+
def keep_alive_response(response)
|
45
|
+
response.with_headers(CONNECTION => KEEP_ALIVE)
|
46
|
+
end
|
47
|
+
|
48
|
+
def synchronize_responses(request)
|
49
|
+
served = @requests[sequence(request) - 1]
|
41
50
|
served.sync.closed! if served
|
42
51
|
end
|
43
52
|
|
44
53
|
def connection_header(request)
|
45
|
-
response = @requests[request]
|
54
|
+
response = @requests[sequence(request)].value
|
46
55
|
request.headers[CONNECTION] || response.headers[CONNECTION]
|
47
56
|
end
|
48
57
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module HTTPkit
|
4
|
+
class Server::MandatoryHandler
|
5
|
+
SERVER = 'Server'.freeze
|
6
|
+
SERVER_VALUE = "httpkit/#{HTTPkit::VERSION}".freeze
|
7
|
+
DATE = 'Date'.freeze
|
8
|
+
|
9
|
+
def respond(request, response)
|
10
|
+
yield request, response.with_headers(missing_headers(response))
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def missing_headers(response)
|
16
|
+
headers.reject { |k, _| response.headers.key?(k) }
|
17
|
+
end
|
18
|
+
|
19
|
+
def headers
|
20
|
+
{ SERVER => SERVER_VALUE, DATE => Time.now.httpdate }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -7,17 +7,20 @@ module HTTPkit
|
|
7
7
|
@handlers = handlers
|
8
8
|
end
|
9
9
|
|
10
|
-
def
|
11
|
-
@handlers
|
12
|
-
|
13
|
-
|
10
|
+
def invoke(message, *arguments)
|
11
|
+
@handlers
|
12
|
+
.select { |handler| handler.respond_to?(message) }
|
13
|
+
.reduce(arguments) do |args, handler|
|
14
|
+
handler.send(message, *args) { |*new_args| args = new_args }
|
15
|
+
args
|
16
|
+
end
|
14
17
|
end
|
15
18
|
|
16
19
|
# XXX not mutation covered
|
17
20
|
module Setup
|
18
21
|
def setup_handlers
|
19
22
|
@handlers = Support::HandlerManager.new(@config[:handlers] || [])
|
20
|
-
@handlers.
|
23
|
+
@handlers.invoke(:setup, @config, self, @connection)
|
21
24
|
end
|
22
25
|
end
|
23
26
|
end
|
@@ -3,6 +3,10 @@
|
|
3
3
|
module HTTPkit
|
4
4
|
module Support
|
5
5
|
module Message
|
6
|
+
TRANSFER_ENCODING = 'Transfer-Encoding'.freeze
|
7
|
+
CHUNKED = 'chunked'.freeze
|
8
|
+
CONTENT_LENGTH = 'Content-Length'.freeze
|
9
|
+
|
6
10
|
def closed?
|
7
11
|
!body.closed.pending?
|
8
12
|
end
|
@@ -23,11 +27,29 @@ module HTTPkit
|
|
23
27
|
body.closed.reject(reason)
|
24
28
|
end
|
25
29
|
|
26
|
-
def
|
27
|
-
|
30
|
+
def body_headers
|
31
|
+
length, encoding = nil, nil
|
32
|
+
|
33
|
+
if body_present?
|
34
|
+
if body.length_known?
|
35
|
+
length = body.length.to_i
|
36
|
+
else
|
37
|
+
encoding = CHUNKED
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
{ CONTENT_LENGTH => length, TRANSFER_ENCODING => encoding }
|
28
42
|
end
|
29
43
|
|
30
44
|
def self.build(parser)
|
45
|
+
message = build_message(parser)
|
46
|
+
message.close if !message.body_included? || !message.body_present?
|
47
|
+
message
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def self.build_message(parser)
|
31
53
|
if parser.http_method
|
32
54
|
build_request(parser)
|
33
55
|
else
|
@@ -35,23 +57,14 @@ module HTTPkit
|
|
35
57
|
end
|
36
58
|
end
|
37
59
|
|
38
|
-
private
|
39
|
-
|
40
60
|
def self.build_request(parser)
|
41
|
-
|
42
|
-
|
43
|
-
parser.headers,
|
44
|
-
Body.new)
|
45
|
-
request.http_version = http_version_from(parser)
|
46
|
-
request
|
61
|
+
Request.new(http_method_from(parser), parser.request_url,
|
62
|
+
parser.headers, Body.new, http_version_from(parser))
|
47
63
|
end
|
48
64
|
|
49
65
|
def self.build_response(parser)
|
50
|
-
|
51
|
-
|
52
|
-
Body.new)
|
53
|
-
response.http_version = http_version_from(parser)
|
54
|
-
response
|
66
|
+
Response.new(parser.status_code, parser.headers, Body.new,
|
67
|
+
http_version_from(parser))
|
55
68
|
end
|
56
69
|
|
57
70
|
def self.http_method_from(parser)
|
data/lib/httpkit/version.rb
CHANGED
@@ -6,10 +6,10 @@ describe reactor: true do
|
|
6
6
|
include_context :server_client_pair
|
7
7
|
|
8
8
|
let(:server_config) do
|
9
|
-
{ handlers: [HTTPkit::Server::
|
9
|
+
{ handlers: [HTTPkit::Server::KeepAliveHandler.new, SpecHandler.new] }
|
10
10
|
end
|
11
11
|
let(:client_config) do
|
12
|
-
{ handlers: [HTTPkit::Client::
|
12
|
+
{ handlers: [HTTPkit::Client::KeepAliveHandler.new] }
|
13
13
|
end
|
14
14
|
|
15
15
|
let!(:served) do
|
@@ -17,7 +17,7 @@ describe reactor: true do
|
|
17
17
|
end
|
18
18
|
let(:responses) { served.map(&:sync) }
|
19
19
|
|
20
|
-
describe HTTPkit::Server, 'with
|
20
|
+
describe HTTPkit::Server, 'with KeepAliveHandler' do
|
21
21
|
let(:requests) do
|
22
22
|
[closed_request(:get, '/sleep'), closed_request]
|
23
23
|
end
|
@@ -41,7 +41,7 @@ describe reactor: true do
|
|
41
41
|
|
42
42
|
describe 'and HTTP/1.0 request' do
|
43
43
|
let(:requests) do
|
44
|
-
[closed_request
|
44
|
+
[closed_request(:get, '/', {}, nil, 1.0)]
|
45
45
|
end
|
46
46
|
|
47
47
|
it 'assumes Connection: close' do
|
@@ -56,8 +56,7 @@ describe reactor: true do
|
|
56
56
|
|
57
57
|
describe 'and Connection: keep-alive' do
|
58
58
|
let(:requests) do
|
59
|
-
[closed_request(:get, '/', 'Connection' => 'keep-alive')
|
60
|
-
.tap { |r| r.http_version = 1.0 }]
|
59
|
+
[closed_request(:get, '/', { 'Connection' => 'keep-alive' }, nil, 1.0)]
|
61
60
|
end
|
62
61
|
|
63
62
|
it 'keeps the connection open' do
|
@@ -84,7 +83,7 @@ describe reactor: true do
|
|
84
83
|
end
|
85
84
|
end
|
86
85
|
|
87
|
-
describe HTTPkit::Client, 'with
|
86
|
+
describe HTTPkit::Client, 'with KeepAliveHandler' do
|
88
87
|
let(:requests) do
|
89
88
|
[open_request(:get, '/?1'), closed_request(:get, '/?2')]
|
90
89
|
end
|
@@ -9,13 +9,13 @@ describe 'Smoke test', reactor: true do
|
|
9
9
|
let(:request) { intercepted_requests[0] }
|
10
10
|
|
11
11
|
it 'exchanges request and response' do
|
12
|
-
expect(response.status).to
|
12
|
+
expect(response.status).to be(200)
|
13
13
|
expect(response.headers).to include('Content-Length' => '1')
|
14
14
|
expect(response.body.to_s).to eq('/')
|
15
15
|
|
16
16
|
expect(request.http_method).to be(:get)
|
17
|
-
expect(request.uri).to
|
18
|
-
expect(request.headers).
|
19
|
-
expect(request.body.to_s).to
|
17
|
+
expect(request.uri).to eq('/')
|
18
|
+
expect(request.headers).not_to have_key('Content-Length')
|
19
|
+
expect(request.body.to_s).to eq('')
|
20
20
|
end
|
21
21
|
end
|
@@ -8,8 +8,7 @@ describe 'Request body streaming', reactor: true do
|
|
8
8
|
let(:chunks) { [] }
|
9
9
|
|
10
10
|
before do
|
11
|
-
client.perform(request)
|
12
|
-
tick(2)
|
11
|
+
client.perform(request).sync
|
13
12
|
Fiber.new do
|
14
13
|
intercepted_requests[0].body.each do |chunk|
|
15
14
|
chunks << chunk
|
@@ -21,7 +20,7 @@ describe 'Request body streaming', reactor: true do
|
|
21
20
|
|
22
21
|
subject! do
|
23
22
|
%w[foo bar baz].each do |chunk|
|
24
|
-
request.body.
|
23
|
+
request.body.write(chunk)
|
25
24
|
tick
|
26
25
|
end
|
27
26
|
request.close
|
@@ -7,7 +7,7 @@ describe HTTPkit::Client, 'in connecting state', reactor: true do
|
|
7
7
|
let(:address) { '1.2.3.4' }
|
8
8
|
let(:client_config) do
|
9
9
|
{ address: address, port: random_port,
|
10
|
-
handlers: [HTTPkit::Client::
|
10
|
+
handlers: [HTTPkit::Client::TimeoutsHandler.new] }
|
11
11
|
end
|
12
12
|
let!(:client) { HTTPkit::Client.start(client_config) }
|
13
13
|
|
@@ -21,7 +21,7 @@ describe HTTPkit::Client, 'in connecting state', reactor: true do
|
|
21
21
|
describe 'with :connect_timeout option' do
|
22
22
|
let(:client_config) do
|
23
23
|
{ address: address, port: random_port, connect_timeout: 0.01,
|
24
|
-
handlers: [HTTPkit::Client::
|
24
|
+
handlers: [HTTPkit::Client::TimeoutsHandler.new] }
|
25
25
|
end
|
26
26
|
|
27
27
|
it 'times out after $n seconds' do
|
@@ -38,7 +38,7 @@ end
|
|
38
38
|
describe HTTPkit::Client, 'in idle state', reactor: true do
|
39
39
|
include_context :server_client_pair
|
40
40
|
|
41
|
-
let(:client_config) { { handlers: [HTTPkit::Client::
|
41
|
+
let(:client_config) { { handlers: [HTTPkit::Client::TimeoutsHandler.new] } }
|
42
42
|
|
43
43
|
it 'times out after 2 seconds' do
|
44
44
|
# only test the config, and don't actually wait for 2 seconds
|
@@ -47,7 +47,7 @@ describe HTTPkit::Client, 'in idle state', reactor: true do
|
|
47
47
|
|
48
48
|
describe 'with :timeout option' do
|
49
49
|
let(:client_config) do
|
50
|
-
{ timeout: 0.01, handlers: [HTTPkit::Client::
|
50
|
+
{ timeout: 0.01, handlers: [HTTPkit::Client::TimeoutsHandler.new] }
|
51
51
|
end
|
52
52
|
|
53
53
|
it 'times out after $n seconds' do
|
@@ -61,7 +61,7 @@ end
|
|
61
61
|
describe HTTPkit::Server, 'in idle state', reactor: true do
|
62
62
|
include_context :server_client_pair
|
63
63
|
|
64
|
-
let(:server_config) { { handlers: [HTTPkit::Server::
|
64
|
+
let(:server_config) { { handlers: [HTTPkit::Server::TimeoutsHandler.new] } }
|
65
65
|
|
66
66
|
it 'times out after 2 seconds' do
|
67
67
|
# only test the config, and don't actually wait for 2 seconds
|
@@ -70,7 +70,7 @@ describe HTTPkit::Server, 'in idle state', reactor: true do
|
|
70
70
|
|
71
71
|
describe 'with :timeout option' do
|
72
72
|
let(:server_config) do
|
73
|
-
{ timeout: 0.01, handlers: [HTTPkit::Server::
|
73
|
+
{ timeout: 0.01, handlers: [HTTPkit::Server::TimeoutsHandler.new] }
|
74
74
|
end
|
75
75
|
|
76
76
|
it 'times out after $n seconds' do
|