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.
Files changed (59) hide show
  1. checksums.yaml +15 -0
  2. data/.rspec +0 -1
  3. data/.travis.yml +5 -4
  4. data/Gemfile +0 -2
  5. data/Gemfile.devtools +27 -22
  6. data/README.md +11 -13
  7. data/config/flay.yml +2 -2
  8. data/config/flog.yml +1 -1
  9. data/config/reek.yml +11 -5
  10. data/config/rubocop.yml +47 -5
  11. data/examples/echo_server.rb +2 -2
  12. data/examples/getting_started.rb +16 -10
  13. data/httpkit.gemspec +5 -4
  14. data/lib/httpkit.rb +15 -5
  15. data/lib/httpkit/body.rb +44 -33
  16. data/lib/httpkit/client.rb +52 -23
  17. data/lib/httpkit/client/body_handler.rb +21 -0
  18. data/lib/httpkit/client/{keep_alive.rb → keep_alive_handler.rb} +1 -1
  19. data/lib/httpkit/client/mandatory_handler.rb +29 -0
  20. data/lib/httpkit/client/{timeouts.rb → timeouts_handler.rb} +1 -1
  21. data/lib/httpkit/connection/eventmachine.rb +4 -4
  22. data/lib/httpkit/request.rb +46 -10
  23. data/lib/httpkit/response.rb +37 -5
  24. data/lib/httpkit/serializer.rb +33 -25
  25. data/lib/httpkit/server.rb +14 -19
  26. data/lib/httpkit/server/body_handler.rb +25 -0
  27. data/lib/httpkit/server/{keep_alive.rb → keep_alive_handler.rb} +22 -13
  28. data/lib/httpkit/server/mandatory_handler.rb +23 -0
  29. data/lib/httpkit/server/{timeouts.rb → timeouts_handler.rb} +1 -1
  30. data/lib/httpkit/support/handler_manager.rb +8 -5
  31. data/lib/httpkit/support/message.rb +28 -15
  32. data/lib/httpkit/version.rb +1 -1
  33. data/spec/integration/keep_alive_spec.rb +6 -7
  34. data/spec/integration/smoke_spec.rb +4 -4
  35. data/spec/integration/streaming_spec.rb +2 -3
  36. data/spec/integration/timeouts_spec.rb +6 -6
  37. data/spec/shared/integration/server_client_pair.rb +1 -1
  38. data/spec/spec_helper.rb +3 -2
  39. data/spec/support/handler.rb +1 -1
  40. data/spec/support/helper.rb +6 -4
  41. data/spec/unit/body_spec.rb +6 -0
  42. data/spec/unit/client/keep_alive_handler_spec.rb +6 -0
  43. data/spec/unit/client/mandatory_handler_spec.rb +31 -0
  44. data/spec/unit/client/timeouts_handler_spec.rb +6 -0
  45. data/spec/unit/client_spec.rb +83 -34
  46. data/spec/unit/connection/eventmachine_spec.rb +12 -13
  47. data/spec/unit/httpkit_spec.rb +65 -24
  48. data/spec/unit/promise_spec.rb +1 -1
  49. data/spec/unit/request_spec.rb +2 -10
  50. data/spec/unit/response_spec.rb +7 -15
  51. data/spec/unit/serializer_spec.rb +83 -0
  52. data/spec/unit/server/{keep_alive_spec.rb → keep_alive_handler_spec.rb} +5 -2
  53. data/spec/unit/server/mandatory_handler_spec.rb +30 -0
  54. data/spec/unit/server/timeouts_handler_spec.rb +6 -0
  55. data/spec/unit/server_spec.rb +26 -32
  56. data/spec/unit/support/handler_manager_spec.rb +38 -7
  57. data/spec/unit/support/message_spec.rb +45 -20
  58. metadata +57 -36
  59. data/lib/httpkit/serializer/encoding.rb +0 -43
@@ -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
- Fiber.new { @handlers.notify(:serve, request, served) }.resume
27
+ serve!(request, served).resume
30
28
  end
31
29
 
32
30
  def respond(request, response)
33
31
  fiber = Fiber.new do
34
- add_extra_headers(response)
35
- respond!(request, response)
36
- response.closed { finish(request) }
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.notify(:finish, request)
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::KeepAlive
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, @previous = {}, nil
11
+ @requests = {}
12
12
  end
13
13
 
14
14
  def serve(request, served)
15
- @requests[request] = [served, @previous]
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
- response.headers[CONNECTION] = CLOSE
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
- response.headers[CONNECTION] = KEEP_ALIVE
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 synchronize_responses(request)
38
- _, previous = @requests[request]
39
- served, _ = @requests[previous]
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][0].value
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
@@ -2,7 +2,7 @@
2
2
 
3
3
  module HTTPkit
4
4
  # @see EM.heartbeat_interval
5
- class Server::Timeouts
5
+ class Server::TimeoutsHandler
6
6
  def setup(config, _, connection)
7
7
  @config = config
8
8
  @connection = connection
@@ -7,17 +7,20 @@ module HTTPkit
7
7
  @handlers = handlers
8
8
  end
9
9
 
10
- def notify(message, *args)
11
- @handlers.each do |handler|
12
- handler.public_send(message, *args) if handler.respond_to?(message)
13
- end
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.notify(:setup, @config, self, @connection)
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 add_extra_headers(extra)
27
- extra.each { |k, v| headers.key?(k) || headers[k] = v }
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
- request = Request.new(http_method_from(parser),
42
- parser.request_url,
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
- response = Response.new(parser.status_code,
51
- parser.headers,
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)
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module HTTPkit
4
- VERSION = '0.6.0.pre.3'
4
+ VERSION = '0.6.0.pre.5'
5
5
  end
@@ -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::KeepAlive.new, SpecHandler.new] }
9
+ { handlers: [HTTPkit::Server::KeepAliveHandler.new, SpecHandler.new] }
10
10
  end
11
11
  let(:client_config) do
12
- { handlers: [HTTPkit::Client::KeepAlive.new] }
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 KeepAlive' do
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.tap { |r| r.http_version = 1.0 }]
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 KeepAlive' do
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 be(200)
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 eq('/')
18
- expect(request.headers).to include('Content-Length' => '0')
19
- expect(request.body.to_s).to eq('')
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.closed.progress(chunk)
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::Timeouts.new] }
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::Timeouts.new] }
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::Timeouts.new] } }
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::Timeouts.new] }
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::Timeouts.new] } }
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::Timeouts.new] }
73
+ { timeout: 0.01, handlers: [HTTPkit::Server::TimeoutsHandler.new] }
74
74
  end
75
75
 
76
76
  it 'times out after $n seconds' do