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.
@@ -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