hatetepe 0.5.2 → 0.6.0.pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/.gitignore +7 -0
  2. data/.rspec +3 -0
  3. data/.travis.yml +4 -0
  4. data/.yardopts +1 -0
  5. data/Gemfile +9 -4
  6. data/Gemfile.devtools +55 -0
  7. data/LICENSE.txt +22 -0
  8. data/README.md +39 -192
  9. data/Rakefile +3 -2
  10. data/bin/hatetepe +35 -2
  11. data/config/devtools.yml +2 -0
  12. data/config/flay.yml +3 -0
  13. data/config/flog.yml +2 -0
  14. data/config/mutant.yml +3 -0
  15. data/config/reek.yml +103 -0
  16. data/config/rubocop.yml +58 -0
  17. data/config/yardstick.yml +2 -0
  18. data/hatetepe.gemspec +23 -27
  19. data/lib/hatetepe/client/keep_alive.rb +59 -0
  20. data/lib/hatetepe/client/timeouts.rb +19 -0
  21. data/lib/hatetepe/client.rb +54 -302
  22. data/lib/hatetepe/connection/eventmachine.rb +61 -0
  23. data/lib/hatetepe/connection/status.rb +28 -0
  24. data/lib/hatetepe/errors.rb +7 -0
  25. data/lib/hatetepe/promise.rb +86 -0
  26. data/lib/hatetepe/request.rb +15 -39
  27. data/lib/hatetepe/response.rb +82 -22
  28. data/lib/hatetepe/serializer/encoding.rb +58 -0
  29. data/lib/hatetepe/serializer.rb +61 -0
  30. data/lib/hatetepe/server/keep_alive.rb +53 -13
  31. data/lib/hatetepe/server/timeouts.rb +17 -0
  32. data/lib/hatetepe/server.rb +37 -85
  33. data/lib/hatetepe/support/handlers.rb +19 -0
  34. data/lib/hatetepe/support/keep_alive.rb +14 -0
  35. data/lib/hatetepe/support/message.rb +40 -0
  36. data/lib/hatetepe/version.rb +3 -1
  37. data/lib/hatetepe.rb +29 -7
  38. data/spec/integration/error_handling_spec.rb +7 -0
  39. data/spec/integration/keep_alive_spec.rb +106 -0
  40. data/spec/integration/smoke_spec.rb +21 -0
  41. data/spec/integration/streaming_spec.rb +61 -0
  42. data/spec/integration/timeouts_spec.rb +82 -0
  43. data/spec/shared/integration/server_client_pair.rb +26 -0
  44. data/spec/spec_helper.rb +41 -10
  45. data/spec/support/handler.rb +55 -0
  46. data/spec/support/helper.rb +74 -0
  47. data/spec/unit/client_spec.rb +115 -156
  48. data/spec/unit/connection/eventmachine_spec.rb +146 -0
  49. data/spec/unit/request_spec.rb +35 -0
  50. data/spec/unit/response_spec.rb +42 -0
  51. data/spec/unit/server_spec.rb +65 -100
  52. data/spec/unit/support/keep_alive_spec.rb +52 -0
  53. data/spec/unit/support/message_spec.rb +41 -0
  54. metadata +68 -103
  55. data/Gemfile.lock +0 -46
  56. data/LICENSE +0 -19
  57. data/Procfile +0 -1
  58. data/config.ru +0 -7
  59. data/examples/parallel_requests.rb +0 -32
  60. data/lib/hatetepe/body.rb +0 -182
  61. data/lib/hatetepe/builder.rb +0 -171
  62. data/lib/hatetepe/cli.rb +0 -61
  63. data/lib/hatetepe/connection.rb +0 -73
  64. data/lib/hatetepe/events.rb +0 -35
  65. data/lib/hatetepe/message.rb +0 -13
  66. data/lib/hatetepe/parser.rb +0 -83
  67. data/lib/hatetepe/server/pipeline.rb +0 -20
  68. data/lib/hatetepe/server/rack_app.rb +0 -39
  69. data/lib/rack/handler/hatetepe.rb +0 -33
  70. data/spec/integration/cli/start_spec.rb +0 -113
  71. data/spec/integration/client/keep_alive_spec.rb +0 -23
  72. data/spec/integration/client/timeout_spec.rb +0 -97
  73. data/spec/integration/server/keep_alive_spec.rb +0 -27
  74. data/spec/integration/server/timeout_spec.rb +0 -51
  75. data/spec/unit/body_spec.rb +0 -205
  76. data/spec/unit/builder_spec.rb +0 -372
  77. data/spec/unit/connection_spec.rb +0 -62
  78. data/spec/unit/events_spec.rb +0 -96
  79. data/spec/unit/parser_spec.rb +0 -209
  80. data/spec/unit/rack_handler_spec.rb +0 -60
@@ -0,0 +1,106 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe do
6
+ include_context :server_client_pair
7
+
8
+ let(:server_config) do
9
+ { handlers: [SpecHandler, Hatetepe::Server::KeepAlive] }
10
+ end
11
+ let(:client_config) do
12
+ { handlers: [Hatetepe::Client::KeepAlive] }
13
+ end
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
25
+ end
26
+
27
+ describe Hatetepe::Server, 'with KeepAlive' do
28
+ let(:requests) do
29
+ [finished_request(:get, '/sleep'), finished_request]
30
+ end
31
+
32
+ it 'sends responses in the correct order' do
33
+ expect(responses[0].body).to eq('/sleep')
34
+ expect(responses[1].body).to eq('/')
35
+ end
36
+
37
+ describe 'and HTTP/1.1 request' do
38
+ let(:requests) { [finished_request] }
39
+
40
+ it 'assumes Connection: keep-alive' do
41
+ expect(client).not_to be_closed
42
+ expect(server).not_to be_closed
43
+
44
+ expect(responses[0].headers).to include('Connection' => 'keep-alive')
45
+ end
46
+ end
47
+
48
+ describe 'and HTTP/1.0 request' do
49
+ let(:requests) do
50
+ [finished_request.tap { |r| r.http_version = 1.0 }]
51
+ end
52
+
53
+ it 'assumes Connection: close' do
54
+ responses
55
+ tick
56
+ expect(client).to be_closed_by_remote
57
+ expect(server).to be_closed_by_self
58
+
59
+ expect(responses[0].headers).to include('Connection' => 'close')
60
+ end
61
+ end
62
+
63
+ describe 'and Connection: keep-alive' do
64
+ let(:requests) do
65
+ [finished_request(:get, '/', { 'Connection' => 'keep-alive' })
66
+ .tap { |r| r.http_version = 1.0 }]
67
+ end
68
+
69
+ it 'keeps the connection open' do
70
+ expect(client).not_to be_closed
71
+ expect(server).not_to be_closed
72
+
73
+ expect(responses[0].headers).to include('Connection' => 'keep-alive')
74
+ end
75
+ end
76
+
77
+ describe 'and Connection: close' do
78
+ let(:requests) do
79
+ [finished_request(:get, '/', 'Connection' => 'close')]
80
+ end
81
+
82
+ it 'closes the connection after sending the response' do
83
+ responses
84
+ tick
85
+ expect(client).to be_closed_by_remote
86
+ expect(server).to be_closed_by_self
87
+
88
+ expect(responses[0].headers).to include('Connection' => 'close')
89
+ end
90
+ end
91
+ end
92
+
93
+ describe Hatetepe::Client, 'with KeepAlive' do
94
+ let(:requests) do
95
+ [finished_request(:get, '/close'), finished_request]
96
+ end
97
+
98
+ it 'negotiates keep-alive before sending further requests' do
99
+ expect_any_instance_of(SpecHandler)
100
+ .to receive(:serve).once.and_call_original
101
+
102
+ expect(responses[0].body).to eq('/close')
103
+ expect(responses[1]).to be_nil
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,21 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'Smoke test' do
6
+ include_context :server_client_pair
7
+
8
+ let(:response) { client.request(:get, '/') }
9
+ let(:request) { intercepted_requests[0] }
10
+
11
+ it 'exchanges request and response' do
12
+ expect(response.status).to be(200)
13
+ expect(response.headers).to include('Content-Length' => '1')
14
+ expect(response.body).to eq('/')
15
+
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 eq('')
20
+ end
21
+ end
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe 'Request body streaming' do
6
+ include_context :server_client_pair
7
+
8
+ let(:bodies) { [] }
9
+ let(:chunks) { [] }
10
+
11
+ before do
12
+ client.perform(request)
13
+ tick(2)
14
+ intercepted_requests[0].finished.on_progress do |chunk|
15
+ bodies << intercepted_requests[0].body.dup
16
+ chunks << chunk
17
+ end
18
+ end
19
+
20
+ let(:request) { default_request }
21
+
22
+ subject! do
23
+ %w[foo bar baz].each do |chunk|
24
+ request.finished.progress(chunk)
25
+ tick
26
+ end
27
+ tick
28
+ end
29
+
30
+ it 'progressively sends the body' do
31
+ expect(intercepted_requests[0].headers)
32
+ .to include('Transfer-Encoding' => 'chunked')
33
+
34
+ expect(bodies).to eq(%w[foo foobar foobarbaz])
35
+ expect(chunks).to eq(%w[foo bar baz])
36
+ end
37
+ end
38
+
39
+ describe 'Response body streaming' do
40
+ include_context :server_client_pair
41
+
42
+ let(:bodies) { [] }
43
+ let(:chunks) { [] }
44
+
45
+ let(:response) { client.request(:get, '/streaming') }
46
+
47
+ subject! { response }
48
+
49
+ before do
50
+ response.finished.on_progress do |chunk|
51
+ bodies << response.body.dup
52
+ chunks << chunk
53
+ end
54
+ response.finished.sync
55
+ end
56
+
57
+ it 'progressively receives the body' do
58
+ expect(bodies).to eq(%w[foo foobar foobarbaz])
59
+ expect(chunks).to eq(%w[foo bar baz])
60
+ end
61
+ end
@@ -0,0 +1,82 @@
1
+ # encoding: utf-8
2
+
3
+ require 'spec_helper'
4
+
5
+ describe Hatetepe::Client, 'in connecting state' do
6
+ # connect to a non-existing host to provoke connection timeout
7
+ let(:address) { '1.2.3.4' }
8
+ let(:client_config) do
9
+ { address: address, port: random_port,
10
+ handlers: [Hatetepe::Client::Timeouts] }
11
+ end
12
+ let!(:client) { Hatetepe::Client.new(client_config) }
13
+
14
+ after { client.close }
15
+
16
+ it 'times out after 2 seconds' do
17
+ # only test the config, and don't actually wait for 2 seconds
18
+ expect(client.config).to include(connect_timeout: 2.0)
19
+ end
20
+
21
+ describe 'with :connect_timeout option' do
22
+ let(:client_config) do
23
+ { address: address, port: random_port, connect_timeout: 0.01,
24
+ handlers: [Hatetepe::Client::Timeouts] }
25
+ end
26
+
27
+ it 'times out after $n seconds' do
28
+ async_sleep(0.015)
29
+ if client.closed_reason == Errno::ENOTCONN
30
+ pending 'Requires network connection'
31
+ else
32
+ expect(client).to be_closed_by_timeout
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ describe Hatetepe::Client, 'in idle state' do
39
+ include_context :server_client_pair
40
+
41
+ let(:client_config) { { handlers: [Hatetepe::Client::Timeouts] } }
42
+
43
+ it 'times out after 2 seconds' do
44
+ # only test the config, and don't actually wait for 2 seconds
45
+ expect(client.config).to include(connect_timeout: 2.0)
46
+ end
47
+
48
+ describe 'with :timeout option' do
49
+ let(:client_config) do
50
+ { timeout: 0.01, handlers: [Hatetepe::Client::Timeouts] }
51
+ end
52
+
53
+ it 'times out after $n seconds' do
54
+ async_sleep(0.015)
55
+ expect(client).to be_closed_by_timeout
56
+ expect(server).to be_closed_by_remote
57
+ end
58
+ end
59
+ end
60
+
61
+ describe Hatetepe::Server, 'in idle state' do
62
+ include_context :server_client_pair
63
+
64
+ let(:server_config) { { handlers: [Hatetepe::Server::Timeouts] } }
65
+
66
+ it 'times out after 2 seconds' do
67
+ # only test the config, and don't actually wait for 2 seconds
68
+ expect(server.config).to include(timeout: 2.0)
69
+ end
70
+
71
+ describe 'with :timeout option' do
72
+ let(:server_config) do
73
+ { timeout: 0.01, handlers: [Hatetepe::Server::Timeouts] }
74
+ end
75
+
76
+ it 'times out after $n seconds' do
77
+ async_sleep(0.015)
78
+ expect(client).to be_closed_by_remote
79
+ expect(server).to be_closed_by_timeout
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,26 @@
1
+ # encoding: utf-8
2
+
3
+ shared_context :server_client_pair do
4
+ let(:server_config) { { handlers: [SpecHandler] } }
5
+ let(:client_config) { { handlers: [] } }
6
+ let(:interceptor) { double('interceptor') }
7
+
8
+ let(:server_and_client) do
9
+ server_client_pair(server_config, client_config, interceptor)
10
+ end
11
+
12
+ let(:intercepted_requests) { [] }
13
+
14
+ before do
15
+ allow(interceptor)
16
+ .to receive(:serve) { |request| intercepted_requests << request }
17
+ end
18
+
19
+ let!(:server) { server_and_client[0] }
20
+ let!(:client) { server_and_client[1] }
21
+
22
+ after do
23
+ server.close
24
+ client.close
25
+ end
26
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,16 +1,47 @@
1
- begin
2
- require "awesome_print"
3
- rescue LoadError
1
+ # encoding: utf-8
2
+
3
+ if ENV['COVERAGE'] == 'true'
4
+ require 'simplecov'
5
+ require 'coveralls'
6
+
7
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
8
+ SimpleCov::Formatter::HTMLFormatter,
9
+ Coveralls::SimpleCov::Formatter
10
+ ]
11
+
12
+ SimpleCov.start do
13
+ command_name 'spec:unit'
14
+
15
+ add_filter 'config'
16
+ add_filter 'spec'
17
+ end
4
18
  end
5
19
 
6
- require "em-synchrony"
20
+ require 'hatetepe'
21
+ require 'weakref'
22
+
23
+ require 'awesome_print'
24
+
25
+ require 'devtools/spec_helper'
26
+
27
+ RSpec.configure do |config|
28
+ config.include(SpecHelper)
29
+
30
+ config.around do |example|
31
+ EM.run do
32
+ fiber = Fiber.new do
33
+ example.call
34
+ EM.stop
35
+ EM.next_tick {}
36
+ end
37
+
38
+ EM.add_timer(0.5) do
39
+ EM.stop
40
+ EM.next_tick {}
41
+ raise 'Example timed out'
42
+ end
7
43
 
8
- RSpec.configure do |c|
9
- c.around do |example|
10
- EM.synchrony do
11
- EM.heartbeat_interval = 0.01
12
- example.call
13
- EM.next_tick { EM.stop }
44
+ fiber.resume
14
45
  end
15
46
  end
16
47
  end
@@ -0,0 +1,55 @@
1
+ # encoding: utf-8
2
+
3
+ class SpecHandler
4
+ def initialize(config, server, connection)
5
+ end
6
+
7
+ def serve(request)
8
+ response = response_for(request)
9
+ request.served.fulfill(response)
10
+ end
11
+
12
+ private
13
+
14
+ def response_for(request)
15
+ case request.uri
16
+ when '/' then root_response.tap(&fulfill)
17
+ when '/sleep' then sleep_response.tap(&fulfill)
18
+ when '/close' then close_response.tap(&fulfill)
19
+ when '/streaming' then streaming_response
20
+ end
21
+ end
22
+
23
+ def fulfill
24
+ proc { |response| response.finished.fulfill }
25
+ end
26
+
27
+ def root_response
28
+ Hatetepe::Response.new(200, { 'Content-Type' => 'text/plain' }, '/')
29
+ end
30
+
31
+ def sleep_response
32
+ SpecHelper.async_sleep(0.01)
33
+ Hatetepe::Response.new(200, { 'Content-Type' => 'text/plain' }, '/sleep')
34
+ end
35
+
36
+ def close_response
37
+ Hatetepe::Response.new(200,
38
+ { 'Content-Type' => 'text/plain',
39
+ 'Connection' => 'close' },
40
+ '/close')
41
+ end
42
+
43
+ def streaming_response
44
+ response = Hatetepe::Response.new(200, 'Content-Type' => 'text/plain')
45
+ SpecHelper.defer do
46
+ %w[foo bar baz].each do |chunk|
47
+ SpecHelper.tick(2)
48
+ response.body << chunk
49
+ response.finished.progress(chunk)
50
+ end
51
+ response.finished.fulfill
52
+ end
53
+ response
54
+ end
55
+ end
@@ -0,0 +1,74 @@
1
+ # encoding: utf-8
2
+
3
+ module SpecHelper
4
+ def async_sleep(seconds)
5
+ fiber = Fiber.current
6
+ EM.add_timer(seconds) { fiber.resume }
7
+ Fiber.yield
8
+ end
9
+ module_function :async_sleep
10
+
11
+ def tick(ticks = 1)
12
+ fiber = Fiber.current
13
+ EM.next_tick { fiber.resume }
14
+ Fiber.yield
15
+
16
+ tick(ticks - 1) if ticks > 1
17
+ end
18
+ module_function :tick
19
+
20
+ def defer
21
+ EM.next_tick do
22
+ Fiber.new { yield }.resume
23
+ end
24
+ end
25
+ module_function :defer
26
+
27
+ def default_request(http_method = :get, uri = '/', headers = {})
28
+ Hatetepe::Request.new(http_method, uri, headers)
29
+ end
30
+
31
+ def finished_request(*args)
32
+ default_request(*args).tap { |request| request.finished.fulfill }
33
+ end
34
+
35
+ def default_response(status = 200, headers = {})
36
+ Hatetepe::Response.new(status, headers)
37
+ end
38
+
39
+ def finished_response(*args)
40
+ default_response(*args).tap { |response| response.finished.fulfill }
41
+ end
42
+
43
+ def localhost
44
+ '127.0.0.1'
45
+ end
46
+
47
+ def random_port
48
+ server = TCPServer.new(localhost, 0)
49
+ server.addr[1]
50
+ ensure
51
+ server.shutdown
52
+ end
53
+
54
+ def server_client_pair(server_config, client_config, interceptor)
55
+ server = nil
56
+ inspect_server(server_config) do |_, s, _|
57
+ server = s
58
+ interceptor
59
+ end
60
+
61
+ config = { address: localhost, port: random_port }
62
+ Hatetepe::Server.start(config.merge(server_config))
63
+ client = Hatetepe::Client.new(config.merge(client_config))
64
+
65
+ tick
66
+ [server, client]
67
+ end
68
+
69
+ def inspect_server(server_config, &block)
70
+ interceptor_class = double('interceptor_class')
71
+ allow(interceptor_class).to receive(:new, &block)
72
+ server_config[:handlers].unshift(interceptor_class)
73
+ end
74
+ end