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.
@@ -51,12 +51,17 @@ module Hatetepe
51
51
  klass.extend(ClassMethods)
52
52
  end
53
53
 
54
- def initialize
55
- self.class.send(:promises).map { |name| send(name) }
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)
@@ -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
- include Promise::Attribute
6
- promise :finished
7
- promise :served
7
+ private :http_method=, :uri=, :headers=, :body=
8
8
 
9
- attr_reader :http_method, :uri, :headers, :body
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
- # @http_method, @uri = http_method, URI('http://' + uri)
15
- @http_method, @uri = http_method, URI.join('http:///', uri).request_uri
16
- @headers, @body = headers, body
17
- @http_version = 1.1
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
@@ -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
- include Promise::Attribute
6
- promise :finished
7
+ private :status=, :headers=, :body=
7
8
 
8
- attr_reader :status, :headers, :body
9
- attr_accessor :http_version
9
+ include Support::Message
10
10
 
11
11
  def initialize(status, headers = {}, body = '')
12
- @status, @headers, @body = status, headers, body
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
@@ -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(data)
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
- CHUNK = "%d\r\n%s\r\n".freeze
8
+ CRLF = "\r\n".freeze
9
9
  CONTENT_LENGTH = 'Content-Length'.freeze
10
10
 
11
11
  def setup_body
12
- if finished.pending?
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
- finished.on_progress do |chunk|
27
- write_chunk(chunk)
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 finished.pending?
39
- write_chunk(body)
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(body_chunk(chunk))
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
@@ -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
- setup_connection
22
-
23
- notify_handlers(:post_init)
21
+ notify_handlers(:setup)
24
22
  end
25
23
 
26
24
  def serve(request)
27
- request.served.then { |response| respond(request, response) }
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(:serve, request)
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 respond(request, response)
36
- notify_handlers(:respond, request, response)
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
- include Support::KeepAlive
10
-
11
- def initialize(config, server, connection)
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 << request
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, response)
23
-
24
- version = request.http_version
25
- header = connection_header(request, response)
20
+ synchronize_responses(request)
26
21
 
27
- if close_connection?(version, header)
22
+ if close_connection?(request)
28
23
  response.headers[CONNECTION] = CLOSE
29
- close_connection(response)
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, response)
38
- wait_for_previous(request)
37
+ def synchronize_responses(request)
38
+ _, previous = @requests[request]
39
+ served, _ = @requests[previous]
39
40
 
40
- callback = proc { @requests.delete(request) }
41
- response.finished.then(callback, callback)
41
+ served.sync.closed! if served
42
42
  end
43
43
 
44
- def wait_for_previous(request)
45
- previous = @requests[@requests.index(request) - 1]
46
- if previous
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 connection_header(request, response)
53
- header = request.headers[CONNECTION] || response.headers[CONNECTION]
54
- header.to_s.downcase
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 close_connection(response)
58
- callback = proc { |arg| @server.close }
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, server, connection)
7
- @config, @server = config, server
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 post_init
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
- class Message
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
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Hatetepe
4
- VERSION = '0.6.0.pre'
4
+ VERSION = '0.6.0.pre.1'
5
5
  end
@@ -6,36 +6,29 @@ describe do
6
6
  include_context :server_client_pair
7
7
 
8
8
  let(:server_config) do
9
- { handlers: [SpecHandler, Hatetepe::Server::KeepAlive] }
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(:responses) do
16
- requests.each do |request|
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
- [finished_request(:get, '/sleep'), finished_request]
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) { [finished_request] }
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
- [finished_request.tap { |r| r.http_version = 1.0 }]
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
- [finished_request(:get, '/', { 'Connection' => 'keep-alive' })
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
- [finished_request(:get, '/', 'Connection' => 'close')]
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
- [finished_request(:get, '/close'), finished_request]
88
+ [open_request(:get, '/?1'), closed_request(:get, '/?2')]
96
89
  end
97
90
 
98
- it 'negotiates keep-alive before sending further requests' do
99
- expect_any_instance_of(SpecHandler)
100
- .to receive(:serve).once.and_call_original
91
+ before do
92
+ requests[0].close
93
+ end
101
94
 
102
- expect(responses[0].body).to eq('/close')
103
- expect(responses[1]).to be_nil
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