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