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