hatetepe 0.6.0.pre → 0.6.0.pre.1
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/README.md +6 -4
- data/bin/hatetepe +4 -8
- data/config/flay.yml +2 -2
- data/config/reek.yml +15 -11
- data/lib/hatetepe.rb +1 -1
- data/lib/hatetepe/body.rb +62 -0
- data/lib/hatetepe/client.rb +34 -34
- data/lib/hatetepe/client/keep_alive.rb +4 -49
- data/lib/hatetepe/client/timeouts.rb +3 -3
- data/lib/hatetepe/connection/eventmachine.rb +7 -8
- data/lib/hatetepe/promise.rb +7 -2
- data/lib/hatetepe/request.rb +8 -10
- data/lib/hatetepe/response.rb +5 -7
- data/lib/hatetepe/serializer.rb +2 -3
- data/lib/hatetepe/serializer/encoding.rb +8 -16
- data/lib/hatetepe/server.rb +15 -10
- data/lib/hatetepe/server/keep_alive.rb +26 -31
- data/lib/hatetepe/server/timeouts.rb +3 -3
- data/lib/hatetepe/support/message.rb +25 -3
- data/lib/hatetepe/version.rb +1 -1
- data/spec/integration/keep_alive_spec.rb +18 -24
- data/spec/integration/smoke_spec.rb +2 -2
- data/spec/integration/streaming_spec.rb +9 -13
- data/spec/support/handler.rb +12 -16
- data/spec/support/helper.rb +8 -8
- data/spec/unit/client_spec.rb +25 -24
- data/spec/unit/connection/eventmachine_spec.rb +9 -8
- data/spec/unit/request_spec.rb +6 -6
- data/spec/unit/response_spec.rb +3 -3
- data/spec/unit/server/keep_alive_spec.rb +67 -0
- data/spec/unit/server_spec.rb +13 -9
- data/spec/unit/support/message_spec.rb +5 -2
- metadata +5 -5
- data/lib/hatetepe/support/keep_alive.rb +0 -14
- data/spec/unit/support/keep_alive_spec.rb +0 -52
data/lib/hatetepe/promise.rb
CHANGED
@@ -51,12 +51,17 @@ module Hatetepe
|
|
51
51
|
klass.extend(ClassMethods)
|
52
52
|
end
|
53
53
|
|
54
|
-
def initialize
|
55
|
-
|
54
|
+
def initialize(*args)
|
55
|
+
super(*args)
|
56
|
+
promises.map { |name| send(name) }
|
56
57
|
end
|
57
58
|
|
58
59
|
private
|
59
60
|
|
61
|
+
def promises
|
62
|
+
self.class.send(:promises)
|
63
|
+
end
|
64
|
+
|
60
65
|
def get_or_set_promise(ivar)
|
61
66
|
if instance_variable_defined?(ivar)
|
62
67
|
instance_variable_get(ivar)
|
data/lib/hatetepe/request.rb
CHANGED
@@ -1,21 +1,19 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module Hatetepe
|
4
|
+
Request = Struct.new(:http_method, :uri, :headers, :body, :http_version)
|
5
|
+
|
4
6
|
class Request
|
5
|
-
|
6
|
-
promise :finished
|
7
|
-
promise :served
|
7
|
+
private :http_method=, :uri=, :headers=, :body=
|
8
8
|
|
9
|
-
|
10
|
-
attr_accessor :http_version
|
9
|
+
include Support::Message
|
11
10
|
|
12
11
|
# TODO: URI.join is really slow
|
13
12
|
def initialize(http_method, uri, headers = {}, body = '')
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
super()
|
13
|
+
super(http_method,
|
14
|
+
# URI('http://' + uri)
|
15
|
+
URI.join('http:///', uri).request_uri,
|
16
|
+
headers, Body.build(body), 1.1)
|
19
17
|
end
|
20
18
|
end
|
21
19
|
end
|
data/lib/hatetepe/response.rb
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module Hatetepe
|
4
|
+
Response = Struct.new(:status, :headers, :body, :http_version)
|
5
|
+
|
4
6
|
class Response
|
5
|
-
|
6
|
-
promise :finished
|
7
|
+
private :status=, :headers=, :body=
|
7
8
|
|
8
|
-
|
9
|
-
attr_accessor :http_version
|
9
|
+
include Support::Message
|
10
10
|
|
11
11
|
def initialize(status, headers = {}, body = '')
|
12
|
-
|
13
|
-
@http_version = 1.1
|
14
|
-
super()
|
12
|
+
super(status, headers, Body.build(body), 1.1)
|
15
13
|
end
|
16
14
|
|
17
15
|
def status_name
|
data/lib/hatetepe/serializer.rb
CHANGED
@@ -14,7 +14,6 @@ module Hatetepe
|
|
14
14
|
extend Forwardable
|
15
15
|
def_delegator :@message, :headers
|
16
16
|
def_delegator :@message, :body
|
17
|
-
def_delegator :@message, :finished
|
18
17
|
|
19
18
|
def initialize(message, writer)
|
20
19
|
@message = message
|
@@ -32,8 +31,8 @@ module Hatetepe
|
|
32
31
|
|
33
32
|
private
|
34
33
|
|
35
|
-
def write(data)
|
36
|
-
@writer.call(
|
34
|
+
def write(*data)
|
35
|
+
data.each { |str| @writer.call(str) }
|
37
36
|
end
|
38
37
|
|
39
38
|
def banana_line
|
@@ -5,11 +5,11 @@ module Hatetepe
|
|
5
5
|
module Encoding
|
6
6
|
TRANSFER_ENCODING = 'Transfer-Encoding'.freeze
|
7
7
|
CHUNKED = 'chunked'.freeze
|
8
|
-
|
8
|
+
CRLF = "\r\n".freeze
|
9
9
|
CONTENT_LENGTH = 'Content-Length'.freeze
|
10
10
|
|
11
11
|
def setup_body
|
12
|
-
if
|
12
|
+
if body.closed.pending?
|
13
13
|
setup_chunked_encoding
|
14
14
|
setup_chunked_streaming
|
15
15
|
else
|
@@ -23,22 +23,18 @@ module Hatetepe
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def setup_chunked_streaming
|
26
|
-
|
27
|
-
|
28
|
-
end
|
29
|
-
finished.then { |_| write_chunk!('') }
|
26
|
+
body.closed.on_progress { |chunk| write_chunk(chunk) }
|
27
|
+
body.closed.then { |_| write_chunk!('') }
|
30
28
|
end
|
31
29
|
|
32
30
|
def setup_identity_encoding
|
33
31
|
headers.delete(TRANSFER_ENCODING)
|
34
|
-
headers[CONTENT_LENGTH] = body.bytesize
|
32
|
+
headers[CONTENT_LENGTH] = body.to_s.bytesize
|
35
33
|
end
|
36
34
|
|
37
35
|
def write_body
|
38
|
-
if
|
39
|
-
|
40
|
-
else
|
41
|
-
write(body)
|
36
|
+
if headers.key?(CONTENT_LENGTH)
|
37
|
+
write(body.to_s)
|
42
38
|
end
|
43
39
|
end
|
44
40
|
|
@@ -47,11 +43,7 @@ module Hatetepe
|
|
47
43
|
end
|
48
44
|
|
49
45
|
def write_chunk!(chunk)
|
50
|
-
write(
|
51
|
-
end
|
52
|
-
|
53
|
-
def body_chunk(chunk)
|
54
|
-
sprintf(CHUNK, chunk.bytesize, chunk)
|
46
|
+
write(chunk.bytesize, CRLF, chunk, CRLF)
|
55
47
|
end
|
56
48
|
end
|
57
49
|
end
|
data/lib/hatetepe/server.rb
CHANGED
@@ -15,26 +15,30 @@ module Hatetepe
|
|
15
15
|
|
16
16
|
def initialize(config, connection)
|
17
17
|
@config = config
|
18
|
-
@connection = connection
|
19
18
|
|
19
|
+
setup_connection(connection)
|
20
20
|
setup_handlers
|
21
|
-
|
22
|
-
|
23
|
-
notify_handlers(:post_init)
|
21
|
+
notify_handlers(:setup)
|
24
22
|
end
|
25
23
|
|
26
24
|
def serve(request)
|
27
|
-
|
25
|
+
served = Promise.new
|
26
|
+
served.then { |response| respond(request, response) }
|
27
|
+
|
28
|
+
Fiber.new { notify_handlers(:serve, request, served) }.resume
|
29
|
+
end
|
28
30
|
|
31
|
+
def respond(request, response)
|
29
32
|
fiber = Fiber.new do
|
30
|
-
notify_handlers(:
|
33
|
+
notify_handlers(:respond, request, response)
|
34
|
+
@connection.serialize(response)
|
35
|
+
response.closed { finish(request) }
|
31
36
|
end
|
32
37
|
fiber.resume
|
33
38
|
end
|
34
39
|
|
35
|
-
def
|
36
|
-
notify_handlers(:
|
37
|
-
@connection.serialize(response)
|
40
|
+
def finish(request)
|
41
|
+
notify_handlers(:finish, request)
|
38
42
|
end
|
39
43
|
|
40
44
|
def close
|
@@ -43,7 +47,8 @@ module Hatetepe
|
|
43
47
|
|
44
48
|
private
|
45
49
|
|
46
|
-
def setup_connection
|
50
|
+
def setup_connection(connection)
|
51
|
+
@connection = connection
|
47
52
|
@connection.parse(method(:serve))
|
48
53
|
@connection.closed.then(method(:shutdown))
|
49
54
|
end
|
@@ -6,58 +6,53 @@ module Hatetepe
|
|
6
6
|
CLOSE = 'close'.freeze
|
7
7
|
KEEP_ALIVE = 'keep-alive'.freeze
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
@config, @server = config, server
|
13
|
-
@connection = connection
|
14
|
-
@requests = []
|
9
|
+
def initialize(_, server, _)
|
10
|
+
@server = server
|
11
|
+
@requests, @previous = {}, nil
|
15
12
|
end
|
16
13
|
|
17
|
-
def serve(request)
|
18
|
-
@requests
|
14
|
+
def serve(request, served)
|
15
|
+
@requests[request] = [served, @previous]
|
16
|
+
@previous = request
|
19
17
|
end
|
20
18
|
|
21
19
|
def respond(request, response)
|
22
|
-
synchronize_responses(request
|
23
|
-
|
24
|
-
version = request.http_version
|
25
|
-
header = connection_header(request, response)
|
20
|
+
synchronize_responses(request)
|
26
21
|
|
27
|
-
if close_connection?(
|
22
|
+
if close_connection?(request)
|
28
23
|
response.headers[CONNECTION] = CLOSE
|
29
|
-
|
24
|
+
# XXX: possible race condition with other locations waiting for this
|
25
|
+
response.closed { @server.close }
|
30
26
|
else
|
31
27
|
response.headers[CONNECTION] = KEEP_ALIVE
|
32
28
|
end
|
33
29
|
end
|
34
30
|
|
31
|
+
def finish(request)
|
32
|
+
@requests.delete(request)
|
33
|
+
end
|
34
|
+
|
35
35
|
private
|
36
36
|
|
37
|
-
def synchronize_responses(request
|
38
|
-
|
37
|
+
def synchronize_responses(request)
|
38
|
+
_, previous = @requests[request]
|
39
|
+
served, _ = @requests[previous]
|
39
40
|
|
40
|
-
|
41
|
-
response.finished.then(callback, callback)
|
41
|
+
served.sync.closed! if served
|
42
42
|
end
|
43
43
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
47
|
-
response = previous.served.sync
|
48
|
-
response.finished.sync
|
49
|
-
end
|
44
|
+
def connection_header(request)
|
45
|
+
response = @requests[request][0].value
|
46
|
+
request.headers[CONNECTION] || response.headers[CONNECTION]
|
50
47
|
end
|
51
48
|
|
52
|
-
def
|
53
|
-
header = request.
|
54
|
-
header.
|
49
|
+
def close_connection?(request)
|
50
|
+
header = connection_header(request).to_s.downcase
|
51
|
+
header == CLOSE || http10_close?(request.http_version, header)
|
55
52
|
end
|
56
53
|
|
57
|
-
def
|
58
|
-
|
59
|
-
# XXX possible race condition with other locations waiting for this
|
60
|
-
response.finished.then(callback, callback)
|
54
|
+
def http10_close?(version, header)
|
55
|
+
version < 1.1 && header != KEEP_ALIVE
|
61
56
|
end
|
62
57
|
end
|
63
58
|
end
|
@@ -3,14 +3,14 @@
|
|
3
3
|
module Hatetepe
|
4
4
|
# @see EM.heartbeat_interval
|
5
5
|
class Server::Timeouts
|
6
|
-
def initialize(config,
|
7
|
-
@config
|
6
|
+
def initialize(config, _, connection)
|
7
|
+
@config = config
|
8
8
|
@connection = connection
|
9
9
|
|
10
10
|
@config[:timeout] ||= 2.0
|
11
11
|
end
|
12
12
|
|
13
|
-
def
|
13
|
+
def setup
|
14
14
|
@connection.comm_inactivity_timeout = @config[:timeout]
|
15
15
|
end
|
16
16
|
end
|
@@ -2,7 +2,27 @@
|
|
2
2
|
|
3
3
|
module Hatetepe
|
4
4
|
module Support
|
5
|
-
|
5
|
+
module Message
|
6
|
+
def closed?
|
7
|
+
!body.closed.pending?
|
8
|
+
end
|
9
|
+
|
10
|
+
def closed!
|
11
|
+
body.closed.sync
|
12
|
+
end
|
13
|
+
|
14
|
+
def closed(&block)
|
15
|
+
body.closed.then(&block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def close
|
19
|
+
body.closed.fulfill
|
20
|
+
end
|
21
|
+
|
22
|
+
def reject_closed(reason)
|
23
|
+
body.closed.reject(reason)
|
24
|
+
end
|
25
|
+
|
6
26
|
def self.build(parser)
|
7
27
|
if parser.http_method
|
8
28
|
build_request(parser)
|
@@ -16,14 +36,16 @@ module Hatetepe
|
|
16
36
|
def self.build_request(parser)
|
17
37
|
request = Request.new(http_method_from(parser),
|
18
38
|
parser.request_url,
|
19
|
-
parser.headers
|
39
|
+
parser.headers,
|
40
|
+
Body.new)
|
20
41
|
request.http_version = http_version_from(parser)
|
21
42
|
request
|
22
43
|
end
|
23
44
|
|
24
45
|
def self.build_response(parser)
|
25
46
|
response = Response.new(parser.status_code,
|
26
|
-
parser.headers
|
47
|
+
parser.headers,
|
48
|
+
Body.new)
|
27
49
|
response.http_version = http_version_from(parser)
|
28
50
|
response
|
29
51
|
end
|
data/lib/hatetepe/version.rb
CHANGED
@@ -6,36 +6,29 @@ describe do
|
|
6
6
|
include_context :server_client_pair
|
7
7
|
|
8
8
|
let(:server_config) do
|
9
|
-
{ handlers: [
|
9
|
+
{ handlers: [Hatetepe::Server::KeepAlive, SpecHandler] }
|
10
10
|
end
|
11
11
|
let(:client_config) do
|
12
12
|
{ handlers: [Hatetepe::Client::KeepAlive] }
|
13
13
|
end
|
14
14
|
|
15
|
-
let(:
|
16
|
-
requests.
|
17
|
-
client.perform(request)
|
18
|
-
end
|
19
|
-
|
20
|
-
requests.map do |request|
|
21
|
-
request.served.sync.tap do |response|
|
22
|
-
response.finished.sync if response
|
23
|
-
end
|
24
|
-
end
|
15
|
+
let!(:served) do
|
16
|
+
requests.map { |request| client.perform(request) }
|
25
17
|
end
|
18
|
+
let(:responses) { served.map(&:sync) }
|
26
19
|
|
27
20
|
describe Hatetepe::Server, 'with KeepAlive' do
|
28
21
|
let(:requests) do
|
29
|
-
[
|
22
|
+
[closed_request(:get, '/sleep'), closed_request]
|
30
23
|
end
|
31
24
|
|
32
25
|
it 'sends responses in the correct order' do
|
33
|
-
expect(responses[0].body).to eq('/sleep')
|
34
|
-
expect(responses[1].body).to eq('/')
|
26
|
+
expect(responses[0].body.to_s).to eq('/sleep')
|
27
|
+
expect(responses[1].body.to_s).to eq('/')
|
35
28
|
end
|
36
29
|
|
37
30
|
describe 'and HTTP/1.1 request' do
|
38
|
-
let(:requests) { [
|
31
|
+
let(:requests) { [closed_request] }
|
39
32
|
|
40
33
|
it 'assumes Connection: keep-alive' do
|
41
34
|
expect(client).not_to be_closed
|
@@ -47,7 +40,7 @@ describe do
|
|
47
40
|
|
48
41
|
describe 'and HTTP/1.0 request' do
|
49
42
|
let(:requests) do
|
50
|
-
[
|
43
|
+
[closed_request.tap { |r| r.http_version = 1.0 }]
|
51
44
|
end
|
52
45
|
|
53
46
|
it 'assumes Connection: close' do
|
@@ -62,7 +55,7 @@ describe do
|
|
62
55
|
|
63
56
|
describe 'and Connection: keep-alive' do
|
64
57
|
let(:requests) do
|
65
|
-
[
|
58
|
+
[closed_request(:get, '/', { 'Connection' => 'keep-alive' })
|
66
59
|
.tap { |r| r.http_version = 1.0 }]
|
67
60
|
end
|
68
61
|
|
@@ -76,7 +69,7 @@ describe do
|
|
76
69
|
|
77
70
|
describe 'and Connection: close' do
|
78
71
|
let(:requests) do
|
79
|
-
[
|
72
|
+
[closed_request(:get, '/', 'Connection' => 'close')]
|
80
73
|
end
|
81
74
|
|
82
75
|
it 'closes the connection after sending the response' do
|
@@ -92,15 +85,16 @@ describe do
|
|
92
85
|
|
93
86
|
describe Hatetepe::Client, 'with KeepAlive' do
|
94
87
|
let(:requests) do
|
95
|
-
[
|
88
|
+
[open_request(:get, '/?1'), closed_request(:get, '/?2')]
|
96
89
|
end
|
97
90
|
|
98
|
-
|
99
|
-
|
100
|
-
|
91
|
+
before do
|
92
|
+
requests[0].close
|
93
|
+
end
|
101
94
|
|
102
|
-
|
103
|
-
expect(responses[
|
95
|
+
it 'sends one request at a time' do
|
96
|
+
expect(responses[0].body.to_s).to eq('/?1')
|
97
|
+
expect(responses[1].body.to_s).to eq('/?2')
|
104
98
|
end
|
105
99
|
end
|
106
100
|
end
|