httpkit 0.6.0.pre.3 → 0.6.0.pre.5

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