http 0.7.4 → 0.8.0.pre

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +0 -1
  3. data/.rubocop.yml +5 -2
  4. data/CHANGES.md +24 -7
  5. data/CONTRIBUTING.md +25 -0
  6. data/Gemfile +24 -22
  7. data/Guardfile +2 -2
  8. data/README.md +34 -4
  9. data/Rakefile +7 -7
  10. data/examples/parallel_requests_with_celluloid.rb +2 -2
  11. data/http.gemspec +12 -12
  12. data/lib/http.rb +11 -10
  13. data/lib/http/cache.rb +146 -0
  14. data/lib/http/cache/headers.rb +100 -0
  15. data/lib/http/cache/null_cache.rb +13 -0
  16. data/lib/http/chainable.rb +14 -3
  17. data/lib/http/client.rb +64 -80
  18. data/lib/http/connection.rb +139 -0
  19. data/lib/http/content_type.rb +2 -2
  20. data/lib/http/errors.rb +7 -1
  21. data/lib/http/headers.rb +21 -8
  22. data/lib/http/headers/mixin.rb +1 -1
  23. data/lib/http/mime_type.rb +2 -2
  24. data/lib/http/mime_type/adapter.rb +2 -2
  25. data/lib/http/mime_type/json.rb +4 -4
  26. data/lib/http/options.rb +65 -74
  27. data/lib/http/redirector.rb +3 -3
  28. data/lib/http/request.rb +20 -13
  29. data/lib/http/request/caching.rb +95 -0
  30. data/lib/http/request/writer.rb +5 -5
  31. data/lib/http/response.rb +15 -9
  32. data/lib/http/response/body.rb +21 -8
  33. data/lib/http/response/caching.rb +142 -0
  34. data/lib/http/response/io_body.rb +63 -0
  35. data/lib/http/response/parser.rb +1 -1
  36. data/lib/http/response/status.rb +4 -12
  37. data/lib/http/response/status/reasons.rb +53 -53
  38. data/lib/http/response/string_body.rb +53 -0
  39. data/lib/http/version.rb +1 -1
  40. data/spec/lib/http/cache/headers_spec.rb +77 -0
  41. data/spec/lib/http/cache_spec.rb +182 -0
  42. data/spec/lib/http/client_spec.rb +123 -95
  43. data/spec/lib/http/content_type_spec.rb +25 -25
  44. data/spec/lib/http/headers/mixin_spec.rb +8 -8
  45. data/spec/lib/http/headers_spec.rb +213 -173
  46. data/spec/lib/http/options/body_spec.rb +5 -5
  47. data/spec/lib/http/options/form_spec.rb +3 -3
  48. data/spec/lib/http/options/headers_spec.rb +7 -7
  49. data/spec/lib/http/options/json_spec.rb +3 -3
  50. data/spec/lib/http/options/merge_spec.rb +26 -22
  51. data/spec/lib/http/options/new_spec.rb +10 -10
  52. data/spec/lib/http/options/proxy_spec.rb +8 -8
  53. data/spec/lib/http/options_spec.rb +2 -2
  54. data/spec/lib/http/redirector_spec.rb +32 -32
  55. data/spec/lib/http/request/caching_spec.rb +133 -0
  56. data/spec/lib/http/request/writer_spec.rb +26 -26
  57. data/spec/lib/http/request_spec.rb +63 -58
  58. data/spec/lib/http/response/body_spec.rb +13 -13
  59. data/spec/lib/http/response/caching_spec.rb +201 -0
  60. data/spec/lib/http/response/io_body_spec.rb +35 -0
  61. data/spec/lib/http/response/status_spec.rb +25 -25
  62. data/spec/lib/http/response/string_body_spec.rb +35 -0
  63. data/spec/lib/http/response_spec.rb +64 -45
  64. data/spec/lib/http_spec.rb +103 -76
  65. data/spec/spec_helper.rb +10 -12
  66. data/spec/support/connection_reuse_shared.rb +100 -0
  67. data/spec/support/create_certs.rb +12 -12
  68. data/spec/support/dummy_server.rb +11 -11
  69. data/spec/support/dummy_server/servlet.rb +43 -31
  70. data/spec/support/proxy_server.rb +31 -25
  71. metadata +57 -8
  72. data/spec/support/example_server.rb +0 -30
  73. data/spec/support/example_server/servlet.rb +0 -102
@@ -0,0 +1,53 @@
1
+ module HTTP
2
+ class Response
3
+ # A Body class that wraps a String, rather than a the client
4
+ # object.
5
+ class StringBody
6
+ include Enumerable
7
+ extend Forwardable
8
+
9
+ # @return [String,nil] the next `size` octets part of the
10
+ # body, or nil if whole body has already been read.
11
+ def readpartial(size = @contents.length)
12
+ stream!
13
+ return nil if @streaming_offset >= @contents.length
14
+
15
+ @contents[@streaming_offset, size].tap do |part|
16
+ @streaming_offset += (part.length + 1)
17
+ end
18
+ end
19
+
20
+ # Iterate over the body, allowing it to be enumerable
21
+ def each
22
+ yield @contents
23
+ end
24
+
25
+ # @return [String] eagerly consume the entire body as a string
26
+ def to_s
27
+ @contents
28
+ end
29
+ alias_method :to_str, :to_s
30
+
31
+ def_delegator :@contents, :empty?
32
+
33
+ # Assert that the body is actively being streamed
34
+ def stream!
35
+ fail StateError, "body has already been consumed" if @streaming == false
36
+ @streaming = true
37
+ end
38
+
39
+ # Easier to interpret string inspect
40
+ def inspect
41
+ "#<#{self.class}:#{object_id.to_s(16)}>"
42
+ end
43
+
44
+ protected
45
+
46
+ def initialize(contents)
47
+ @contents = contents
48
+ @streaming = nil
49
+ @streaming_offset = 0
50
+ end
51
+ end
52
+ end
53
+ end
data/lib/http/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module HTTP
2
- VERSION = '0.7.4'
2
+ VERSION = "0.8.0.pre"
3
3
  end
@@ -0,0 +1,77 @@
1
+ RSpec.describe HTTP::Cache::Headers do
2
+ subject(:cache_headers) { described_class.new headers }
3
+
4
+ describe ".new" do
5
+ it "accepts instance of HTTP::Headers" do
6
+ expect { described_class.new HTTP::Headers.new }.not_to raise_error
7
+ end
8
+
9
+ it "it rejects any object that does not respond to #headers" do
10
+ expect { described_class.new double }.to raise_error HTTP::Error
11
+ end
12
+ end
13
+
14
+ context "with <Cache-Control: private>" do
15
+ let(:headers) { {"Cache-Control" => "private"} }
16
+ it { is_expected.to be_private }
17
+ end
18
+
19
+ context "with <Cache-Control: public>" do
20
+ let(:headers) { {"Cache-Control" => "public"} }
21
+ it { is_expected.to be_public }
22
+ end
23
+
24
+ context "with <Cache-Control: no-cache>" do
25
+ let(:headers) { {"Cache-Control" => "no-cache"} }
26
+ it { is_expected.to be_no_cache }
27
+ end
28
+
29
+ context "with <Cache-Control: no-store>" do
30
+ let(:headers) { {"Cache-Control" => "no-store"} }
31
+ it { is_expected.to be_no_store }
32
+ end
33
+
34
+ describe "#max_age" do
35
+ subject { cache_headers.max_age }
36
+
37
+ context "with <Cache-Control: max-age=100>" do
38
+ let(:headers) { {"Cache-Control" => "max-age=100"} }
39
+ it { is_expected.to eq 100 }
40
+ end
41
+
42
+ context "with <Expires: {100 seconds from now}>" do
43
+ let(:headers) { {"Expires" => (Time.now + 100).httpdate} }
44
+ it { is_expected.to be_within(1).of(100) }
45
+ end
46
+
47
+ context "with <Expires: {100 seconds before now}>" do
48
+ let(:headers) { {"Expires" => (Time.now - 100).httpdate} }
49
+ it { is_expected.to eq 0 }
50
+ end
51
+
52
+ context "with <Expires: -1>" do
53
+ let(:headers) { {"Expires" => "-1"} }
54
+ it { is_expected.to eq 0 }
55
+ end
56
+ end
57
+
58
+ context "with <Vary: *>" do
59
+ let(:headers) { {"Vary" => "*"} }
60
+ it { is_expected.to be_vary_star }
61
+ end
62
+
63
+ context "with no cache related headers" do
64
+ let(:headers) { {} }
65
+
66
+ it { is_expected.not_to be_private }
67
+ it { is_expected.not_to be_public }
68
+ it { is_expected.not_to be_no_cache }
69
+ it { is_expected.not_to be_no_store }
70
+ it { is_expected.not_to be_vary_star }
71
+
72
+ describe "#max_age" do
73
+ subject { cache_headers.max_age }
74
+ it { is_expected.to eq Float::INFINITY }
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,182 @@
1
+ require "support/dummy_server"
2
+ require "http/cache"
3
+
4
+ RSpec.describe HTTP::Cache do
5
+ describe "creation" do
6
+ subject { described_class }
7
+
8
+ it "allows metastore and entitystore" do
9
+ expect(subject.new(:metastore => "heap:/", :entitystore => "heap:/"))
10
+ .to be_kind_of HTTP::Cache
11
+ end
12
+ end
13
+
14
+ let(:opts) { options }
15
+ let(:sn) { SecureRandom.urlsafe_base64(3) }
16
+ let(:request) { HTTP::Request.new(:get, "http://example.com/#{sn}") }
17
+
18
+ let(:origin_response) do
19
+ HTTP::Response.new(200,
20
+ "http/1.1",
21
+ {"Cache-Control" => "private"},
22
+ "origin")
23
+ end
24
+
25
+ subject { described_class.new(:metastore => "heap:/", :entitystore => "heap:/") }
26
+
27
+ describe "#perform" do
28
+ it "calls request_performer blocck when cache miss" do
29
+ expect do |b|
30
+ subject.perform(request, opts) do |*args|
31
+ b.to_proc.call(*args)
32
+ origin_response
33
+ end
34
+ end.to yield_with_args(request, opts)
35
+ end
36
+
37
+ context "cache hit" do
38
+ it "does not call request_performer block" do
39
+ subject.perform(request, opts) do |*_t|
40
+ origin_response
41
+ end
42
+
43
+ expect { |b| subject.perform(request, opts, &b) }.not_to yield_control
44
+ end
45
+ end
46
+ end
47
+
48
+ context "empty cache, cacheable request, cacheable response" do
49
+ let!(:response) { subject.perform(request, opts) { origin_response } }
50
+
51
+ it "returns origin servers response" do
52
+ expect(response).to eq origin_response
53
+ end
54
+ end
55
+
56
+ context "cache by-passing request, cacheable response" do
57
+ let(:request) do
58
+ headers = {"Cache-Control" => "no-cache"}
59
+ HTTP::Request.new(:get, "http://example.com/", headers)
60
+ end
61
+ let!(:response) { subject.perform(request, opts) { origin_response } }
62
+
63
+ it "returns origin servers response" do
64
+ expect(response).to eq origin_response
65
+ end
66
+ end
67
+
68
+ context "empty cache, cacheable request, 'no-cache' response" do
69
+ let(:origin_response) do
70
+ HTTP::Response.new(200,
71
+ "http/1.1",
72
+ {"Cache-Control" => "no-store"},
73
+ "")
74
+ end
75
+ let!(:response) { subject.perform(request, opts) { origin_response } }
76
+
77
+ it "returns origin servers response" do
78
+ expect(response).to eq origin_response
79
+ end
80
+ end
81
+
82
+ context "empty cache, cacheable request, 'no-store' response" do
83
+ let(:origin_response) do
84
+ HTTP::Response.new(200,
85
+ "http/1.1",
86
+ {"Cache-Control" => "no-store"},
87
+ "")
88
+ end
89
+ let!(:response) { subject.perform(request, opts) { origin_response } }
90
+
91
+ it "returns origin servers response" do
92
+ expect(response).to eq origin_response
93
+ end
94
+ end
95
+
96
+ context "warm cache, cacheable request, cacheable response" do
97
+ let(:cached_response) do
98
+ build_cached_response(200,
99
+ "1.1",
100
+ {"Cache-Control" => "max-age=100"},
101
+ "cached")
102
+ end
103
+ before do
104
+ subject.perform(request, opts) { cached_response }
105
+ end
106
+
107
+ let(:response) { subject.perform(request, opts) { origin_response } }
108
+
109
+ it "returns cached response" do
110
+ expect(response.body.to_s).to eq cached_response.body.to_s
111
+ end
112
+ end
113
+
114
+ context "stale cache, cacheable request, cacheable response" do
115
+ let(:cached_response) do
116
+ build_cached_response(200,
117
+ "1.1",
118
+ {"Cache-Control" => "private, max-age=1",
119
+ "Date" => (Time.now - 2).httpdate},
120
+ "cached") do |t|
121
+ t.requested_at = (Time.now - 2)
122
+ end
123
+ end
124
+ before do
125
+ subject.perform(request, opts) { cached_response }
126
+ end
127
+ let(:response) { subject.perform(request, opts) { origin_response } }
128
+
129
+ it "returns origin servers response" do
130
+ expect(response.body.to_s).to eq origin_response.body.to_s
131
+ end
132
+ end
133
+
134
+ context "stale cache, cacheable request, not modified response" do
135
+ let(:cached_response) do
136
+ build_cached_response(200,
137
+ "http/1.1",
138
+ {"Cache-Control" => "private, max-age=1",
139
+ "Etag" => "foo",
140
+ "Date" => (Time.now - 2).httpdate},
141
+ "") do |x|
142
+ x.requested_at = (Time.now - 2)
143
+ end
144
+ end
145
+ before do
146
+ subject.perform(request, opts) { cached_response }
147
+ end
148
+
149
+ let(:origin_response) { HTTP::Response.new(304, "http/1.1", {}, "") }
150
+ let(:response) { subject.perform(request, opts) { origin_response } }
151
+
152
+ it "makes request with conditional request headers" do
153
+ subject.perform(request, opts) do |actual_request, _|
154
+ expect(actual_request.headers["If-None-Match"])
155
+ .to eq cached_response.headers["Etag"]
156
+ expect(actual_request.headers["If-Modified-Since"])
157
+ .to eq cached_response.headers["Last-Modified"]
158
+
159
+ origin_response
160
+ end
161
+ end
162
+
163
+ it "returns cached servers response" do
164
+ expect(response.body.to_s).to eq cached_response.body.to_s
165
+ end
166
+ end
167
+
168
+ let(:cached_response) { nil } # cold cache by default
169
+
170
+ def build_cached_response(*args)
171
+ r = HTTP::Response.new(*args).caching
172
+ r.requested_at = r.received_at = Time.now
173
+
174
+ yield r if block_given?
175
+
176
+ r
177
+ end
178
+
179
+ def options
180
+ HTTP::Options.new
181
+ end
182
+ end
@@ -1,12 +1,13 @@
1
- require 'support/dummy_server'
1
+ require "support/connection_reuse_shared"
2
+ require "support/dummy_server"
3
+ require "http/cache"
2
4
 
3
5
  RSpec.describe HTTP::Client do
4
- let(:test_endpoint) { "http://#{ExampleServer::ADDR}" }
5
6
  run_server(:dummy) { DummyServer.new }
6
7
  run_server(:dummy_ssl) { DummyServer.new(:ssl => true) }
7
8
 
8
9
  StubbedClient = Class.new(HTTP::Client) do
9
- def perform(request, options)
10
+ def make_request(request, options)
10
11
  stubs.fetch(request.uri.to_s) { super(request, options) }
11
12
  end
12
13
 
@@ -21,105 +22,125 @@ RSpec.describe HTTP::Client do
21
22
  end
22
23
 
23
24
  def redirect_response(location, status = 302)
24
- HTTP::Response.new(status, '1.1', {'Location' => location}, '')
25
+ HTTP::Response.new(status, "1.1", {"Location" => location}, "")
25
26
  end
26
27
 
27
28
  def simple_response(body, status = 200)
28
- HTTP::Response.new(status, '1.1', {}, body)
29
+ HTTP::Response.new(status, "1.1", {}, body)
29
30
  end
30
31
 
31
- describe 'following redirects' do
32
- it 'returns response of new location' do
32
+ describe "following redirects" do
33
+ it "returns response of new location" do
33
34
  client = StubbedClient.new(:follow => true).stub(
34
- 'http://example.com/' => redirect_response('http://example.com/blog'),
35
- 'http://example.com/blog' => simple_response('OK')
35
+ "http://example.com/" => redirect_response("http://example.com/blog"),
36
+ "http://example.com/blog" => simple_response("OK")
36
37
  )
37
38
 
38
- expect(client.get('http://example.com/').to_s).to eq 'OK'
39
+ expect(client.get("http://example.com/").to_s).to eq "OK"
39
40
  end
40
41
 
41
- it 'prepends previous request uri scheme and host if needed' do
42
+ it "prepends previous request uri scheme and host if needed" do
42
43
  client = StubbedClient.new(:follow => true).stub(
43
- 'http://example.com/' => redirect_response('/index'),
44
- 'http://example.com/index' => redirect_response('/index.html'),
45
- 'http://example.com/index.html' => simple_response('OK')
44
+ "http://example.com/" => redirect_response("/index"),
45
+ "http://example.com/index" => redirect_response("/index.html"),
46
+ "http://example.com/index.html" => simple_response("OK")
46
47
  )
47
48
 
48
- expect(client.get('http://example.com/').to_s).to eq 'OK'
49
+ expect(client.get("http://example.com/").to_s).to eq "OK"
49
50
  end
50
51
 
51
- it 'fails upon endless redirects' do
52
+ it "fails upon endless redirects" do
52
53
  client = StubbedClient.new(:follow => true).stub(
53
- 'http://example.com/' => redirect_response('/')
54
+ "http://example.com/" => redirect_response("/")
54
55
  )
55
56
 
56
- expect { client.get('http://example.com/') }
57
+ expect { client.get("http://example.com/") }
57
58
  .to raise_error(HTTP::Redirector::EndlessRedirectError)
58
59
  end
59
60
 
60
- it 'fails if max amount of hops reached' do
61
+ it "fails if max amount of hops reached" do
61
62
  client = StubbedClient.new(:follow => 5).stub(
62
- 'http://example.com/' => redirect_response('/1'),
63
- 'http://example.com/1' => redirect_response('/2'),
64
- 'http://example.com/2' => redirect_response('/3'),
65
- 'http://example.com/3' => redirect_response('/4'),
66
- 'http://example.com/4' => redirect_response('/5'),
67
- 'http://example.com/5' => redirect_response('/6'),
68
- 'http://example.com/6' => simple_response('OK')
63
+ "http://example.com/" => redirect_response("/1"),
64
+ "http://example.com/1" => redirect_response("/2"),
65
+ "http://example.com/2" => redirect_response("/3"),
66
+ "http://example.com/3" => redirect_response("/4"),
67
+ "http://example.com/4" => redirect_response("/5"),
68
+ "http://example.com/5" => redirect_response("/6"),
69
+ "http://example.com/6" => simple_response("OK")
69
70
  )
70
71
 
71
- expect { client.get('http://example.com/') }
72
+ expect { client.get("http://example.com/") }
72
73
  .to raise_error(HTTP::Redirector::TooManyRedirectsError)
73
74
  end
74
75
  end
75
76
 
76
- describe 'parsing params' do
77
+ describe "caching" do
78
+ let(:sn) { SecureRandom.urlsafe_base64(3) }
79
+
80
+ it "returns cached responses if they exist" do
81
+ cached_response = simple_response("cached").caching
82
+ StubbedClient.new(:cache =>
83
+ HTTP::Cache.new(:metastore => "heap:/", :entitystore => "heap:/"))
84
+ .stub("http://example.com/#{sn}" => cached_response)
85
+ .get("http://example.com/#{sn}")
86
+
87
+ # cache is now warm
88
+
89
+ client = StubbedClient.new(:cache => HTTP::Cache.new(:metastore => "heap:/", :entitystore => "heap:/"))
90
+ .stub("http://example.com/#{sn}" => simple_response("OK"))
91
+
92
+ expect(client.get("http://example.com/#{sn}").body.to_s)
93
+ .to eq cached_response.body.to_s
94
+ end
95
+ end
96
+
97
+ describe "parsing params" do
77
98
  let(:client) { HTTP::Client.new }
78
99
  before { allow(client).to receive :perform }
79
100
 
80
- it 'accepts params within the provided URL' do
101
+ it "accepts params within the provided URL" do
81
102
  expect(HTTP::Request).to receive(:new) do |_, uri|
82
- expect(CGI.parse uri.query).to eq('foo' => %w(bar))
103
+ expect(CGI.parse uri.query).to eq("foo" => %w(bar))
83
104
  end
84
105
 
85
- client.get('http://example.com/?foo=bar')
106
+ client.get("http://example.com/?foo=bar")
86
107
  end
87
108
 
88
- it 'combines GET params from the URI with the passed in params' do
109
+ it "combines GET params from the URI with the passed in params" do
89
110
  expect(HTTP::Request).to receive(:new) do |_, uri|
90
- expect(CGI.parse uri.query).to eq('foo' => %w(bar), 'baz' => %w(quux))
111
+ expect(CGI.parse uri.query).to eq("foo" => %w(bar), "baz" => %w(quux))
91
112
  end
92
113
 
93
- client.get('http://example.com/?foo=bar', :params => {:baz => 'quux'})
114
+ client.get("http://example.com/?foo=bar", :params => {:baz => "quux"})
94
115
  end
95
116
 
96
- it 'merges duplicate values' do
117
+ it "merges duplicate values" do
97
118
  expect(HTTP::Request).to receive(:new) do |_, uri|
98
119
  expect(uri.query).to match(/^(a=1&a=2|a=2&a=1)$/)
99
120
  end
100
121
 
101
- client.get('http://example.com/?a=1', :params => {:a => 2})
122
+ client.get("http://example.com/?a=1", :params => {:a => 2})
102
123
  end
103
124
 
104
- it 'does not modifies query part if no params were given' do
125
+ it "does not modifies query part if no params were given" do
105
126
  expect(HTTP::Request).to receive(:new) do |_, uri|
106
- expect(uri.query).to eq 'deadbeef'
127
+ expect(uri.query).to eq "deadbeef"
107
128
  end
108
129
 
109
- client.get('http://example.com/?deadbeef')
130
+ client.get("http://example.com/?deadbeef")
110
131
  end
111
132
 
112
- it 'does not corrupts index-less arrays' do
133
+ it "does not corrupts index-less arrays" do
113
134
  expect(HTTP::Request).to receive(:new) do |_, uri|
114
- expect(CGI.parse uri.query).to eq 'a[]' => %w(b c), 'd' => %w(e)
135
+ expect(CGI.parse uri.query).to eq "a[]" => %w(b c), "d" => %w(e)
115
136
  end
116
137
 
117
- client.get('http://example.com/?a[]=b&a[]=c', :params => {:d => 'e'})
138
+ client.get("http://example.com/?a[]=b&a[]=c", :params => {:d => "e"})
118
139
  end
119
140
  end
120
141
 
121
- describe 'passing json' do
122
- it 'encodes given object' do
142
+ describe "passing json" do
143
+ it "encodes given object" do
123
144
  client = HTTP::Client.new
124
145
  allow(client).to receive(:perform)
125
146
 
@@ -127,84 +148,91 @@ RSpec.describe HTTP::Client do
127
148
  expect(args.last).to eq('{"foo":"bar"}')
128
149
  end
129
150
 
130
- client.get('http://example.com/', :json => {:foo => :bar})
151
+ client.get("http://example.com/", :json => {:foo => :bar})
131
152
  end
132
153
  end
133
154
 
134
- describe '#request' do
135
- context 'with explicitly given `Host` header' do
136
- let(:headers) { {'Host' => 'another.example.com'} }
155
+ describe "#request" do
156
+ context "with explicitly given `Host` header" do
157
+ let(:headers) { {"Host" => "another.example.com"} }
137
158
  let(:client) { described_class.new :headers => headers }
138
159
 
139
- it 'keeps `Host` header as is' do
160
+ it "keeps `Host` header as is" do
140
161
  expect(client).to receive(:perform) do |req, _|
141
- expect(req['Host']).to eq 'another.example.com'
162
+ expect(req["Host"]).to eq "another.example.com"
142
163
  end
143
164
 
144
- client.request(:get, 'http://example.com/')
165
+ client.request(:get, "http://example.com/")
145
166
  end
146
167
  end
147
168
  end
148
169
 
149
- describe 'SSL' do
170
+ include_context "handles shared connections" do
171
+ let(:reuse_conn) { nil }
172
+ let(:server) { dummy }
173
+ let(:client) { described_class.new(:persistent => reuse_conn) }
174
+ end
175
+
176
+ describe "SSL" do
177
+ let(:reuse_conn) { nil }
178
+
150
179
  let(:client) do
151
180
  described_class.new(
181
+ :persistent => reuse_conn,
152
182
  :ssl_context => OpenSSL::SSL::SSLContext.new.tap do |context|
153
183
  context.options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
154
184
 
155
185
  context.verify_mode = OpenSSL::SSL::VERIFY_PEER
156
- context.ca_file = File.join(certs_dir, 'ca.crt')
186
+ context.ca_file = File.join(certs_dir, "ca.crt")
157
187
  context.cert = OpenSSL::X509::Certificate.new(
158
- File.read(File.join(certs_dir, 'client.crt'))
188
+ File.read(File.join(certs_dir, "client.crt"))
159
189
  )
160
190
  context.key = OpenSSL::PKey::RSA.new(
161
- File.read(File.join(certs_dir, 'client.key'))
191
+ File.read(File.join(certs_dir, "client.key"))
162
192
  )
163
193
  context
164
194
  end
165
195
  )
166
196
  end
167
197
 
168
- it 'works via SSL' do
198
+ include_context "handles shared connections" do
199
+ let(:server) { dummy_ssl }
200
+ end
201
+
202
+ it "works via SSL" do
169
203
  response = client.get(dummy_ssl.endpoint)
170
- expect(response.body.to_s).to eq('<!doctype html>')
204
+ expect(response.body.to_s).to eq("<!doctype html>")
171
205
  end
172
206
 
173
- context 'with a mismatch host' do
174
- it 'errors' do
175
- expect { client.get(dummy_ssl.endpoint.gsub('127.0.0.1', 'localhost')) }
207
+ context "with a mismatch host" do
208
+ it "errors" do
209
+ expect { client.get(dummy_ssl.endpoint.gsub("127.0.0.1", "localhost")) }
176
210
  .to raise_error(OpenSSL::SSL::SSLError, /does not match/)
177
211
  end
178
212
  end
179
213
  end
180
214
 
181
- describe '#perform' do
215
+ describe "#perform" do
182
216
  let(:client) { described_class.new }
183
217
 
184
- it 'calls finish_response before actual performance' do
185
- allow(TCPSocket).to receive(:open) { throw :halt }
186
- expect(client).to receive(:finish_response)
187
- catch(:halt) { client.head test_endpoint }
188
- end
189
-
190
- it 'calls finish_response once body was fully flushed' do
191
- expect(client).to receive(:finish_response).twice.and_call_original
192
- client.get(test_endpoint).to_s
218
+ it "calls finish_response once body was fully flushed" do
219
+ expect_any_instance_of(HTTP::Connection).to receive(:finish_response).and_call_original
220
+ client.get(dummy.endpoint).to_s
193
221
  end
194
222
 
195
- context 'with HEAD request' do
196
- it 'does not iterates through body' do
197
- expect(client).to_not receive(:readpartial)
198
- client.head(test_endpoint)
223
+ context "with HEAD request" do
224
+ it "does not iterates through body" do
225
+ expect_any_instance_of(HTTP::Connection).to_not receive(:readpartial)
226
+ client.head(dummy.endpoint)
199
227
  end
200
228
 
201
- it 'finishes response after headers were received' do
202
- expect(client).to receive(:finish_response).twice.and_call_original
203
- client.head(test_endpoint)
229
+ it "finishes response after headers were received" do
230
+ expect_any_instance_of(HTTP::Connection).to receive(:finish_response).and_call_original
231
+ client.head(dummy.endpoint)
204
232
  end
205
233
  end
206
234
 
207
- context 'when server closes connection unexpectedly' do
235
+ context "when server closes connection unexpectedly" do
208
236
  before do
209
237
  socket_spy = double
210
238
 
@@ -216,7 +244,7 @@ RSpec.describe HTTP::Client do
216
244
  allow(TCPSocket).to receive(:open) { socket_spy }
217
245
  end
218
246
 
219
- context 'during headers reading' do
247
+ context "during headers reading" do
220
248
  let :chunks do
221
249
  [
222
250
  proc { "HTTP/1.1 200 OK\r\n" },
@@ -225,28 +253,28 @@ RSpec.describe HTTP::Client do
225
253
  ]
226
254
  end
227
255
 
228
- it 'raises IOError' do
229
- expect { client.get test_endpoint }.to raise_error IOError
256
+ it "raises IOError" do
257
+ expect { client.get dummy.endpoint }.to raise_error IOError
230
258
  end
231
259
  end
232
260
 
233
- context 'after headers were flushed' do
261
+ context "after headers were flushed" do
234
262
  let :chunks do
235
263
  [
236
264
  proc { "HTTP/1.1 200 OK\r\n" },
237
265
  proc { "Content-Type: text/html\r\n\r\n" },
238
- proc { 'unexpected end of f' },
266
+ proc { "unexpected end of f" },
239
267
  proc { fail EOFError }
240
268
  ]
241
269
  end
242
270
 
243
- it 'reads partially arrived body' do
244
- res = client.get(test_endpoint).to_s
245
- expect(res).to eq 'unexpected end of f'
271
+ it "reads partially arrived body" do
272
+ res = client.get(dummy.endpoint).to_s
273
+ expect(res).to eq "unexpected end of f"
246
274
  end
247
275
  end
248
276
 
249
- context 'when body and headers were flushed in one chunk' do
277
+ context "when body and headers were flushed in one chunk" do
250
278
  let :chunks do
251
279
  [
252
280
  proc { "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\nunexpected end of f" },
@@ -254,19 +282,19 @@ RSpec.describe HTTP::Client do
254
282
  ]
255
283
  end
256
284
 
257
- it 'reads partially arrived body' do
258
- res = client.get(test_endpoint).to_s
259
- expect(res).to eq 'unexpected end of f'
285
+ it "reads partially arrived body" do
286
+ res = client.get(dummy.endpoint).to_s
287
+ expect(res).to eq "unexpected end of f"
260
288
  end
261
289
  end
262
290
  end
263
291
 
264
- context 'when server fully flushes response in one chunk' do
292
+ context "when server fully flushes response in one chunk" do
265
293
  before do
266
294
  socket_spy = double
267
295
 
268
296
  chunks = [
269
- <<-RESPONSE.gsub(/^\s*\| */, '').gsub(/\n/, "\r\n")
297
+ <<-RESPONSE.gsub(/^\s*\| */, "").gsub(/\n/, "\r\n")
270
298
  | HTTP/1.1 200 OK
271
299
  | Content-Type: text/html
272
300
  | Server: WEBrick/1.3.1 (Ruby/1.9.3/2013-11-22)
@@ -286,9 +314,9 @@ RSpec.describe HTTP::Client do
286
314
  allow(TCPSocket).to receive(:open) { socket_spy }
287
315
  end
288
316
 
289
- it 'properly reads body' do
290
- body = client.get(test_endpoint).to_s
291
- expect(body).to eq '<!doctype html>'
317
+ it "properly reads body" do
318
+ body = client.get(dummy.endpoint).to_s
319
+ expect(body).to eq "<!doctype html>"
292
320
  end
293
321
  end
294
322
  end