http 0.8.0.pre3 → 0.8.0.pre4

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 5758483fce79ce2698462f1ffedc148d01d75c25
4
- data.tar.gz: ac44a7864edf119f4287c9304537982887cdcdcd
3
+ metadata.gz: b7920fa65897a4adb435be3f24272554a59bb8f0
4
+ data.tar.gz: 9602b19d0e5f70eea417e69343a81159c356c3be
5
5
  SHA512:
6
- metadata.gz: 17cf0d470610ce06db0ff695674ae80a59b1fb1b3a6403f1851aaf15284ecdba86e1584bc99ae9bcdb3fe431909111fe02e45f586afdaed0866a7a867cf481f3
7
- data.tar.gz: 76101412bb754229a66b1612c3ccb656265abde996c643a1b7021f47ba05760ac85d98ff20f3c1f517a99da7f445101928e3fe168c8cce8b64a7d76ef418cdea
6
+ metadata.gz: e295f5c7d2b7c34ff6c4e0da1746361b777903d354910f0003eaded8c14cc180cd7e6e1edb11ce6980c63b851d0290c6e2dc0ba44c7bdee3cce036ee2ce17f3f
7
+ data.tar.gz: 7956cd926d6060cdc7186e1546bf8b33192f82cb71ce288c98b19f467b9e8192a4d3cfa92fd2a789e9c2dcf5631a89c7bda9136f359b8f1bb0e15a16fc14a9f0
data/CHANGES.md CHANGED
@@ -1,4 +1,4 @@
1
- ## 0.8.0.pre3 (2015-03-27)
1
+ ## 0.8.0.pre4 (2015-03-27)
2
2
 
3
3
  * Support for persistent HTTP connections (@zanker)
4
4
  * Add caching support. See #77 and #177. (@Asmod4n, @pezra)
@@ -1,6 +1,9 @@
1
1
  require "http/parser"
2
2
 
3
3
  require "http/errors"
4
+ require "http/timeout/null"
5
+ require "http/timeout/per_operation"
6
+ require "http/timeout/global"
4
7
  require "http/chainable"
5
8
  require "http/client"
6
9
  require "http/connection"
@@ -17,6 +17,7 @@ module HTTP
17
17
 
18
18
  def initialize(default_options = {})
19
19
  @default_options = HTTP::Options.new(default_options)
20
+ @connection = nil
20
21
  end
21
22
 
22
23
  # Make an HTTP request
@@ -16,14 +16,22 @@ module HTTP
16
16
 
17
17
  @parser = Response::Parser.new
18
18
 
19
- @socket = options[:socket_class].open(req.socket_host, req.socket_port)
19
+ @socket = options[:timeout_class].new(options[:timeout_options])
20
+ @socket.connect(options[:socket_class], req.socket_host, req.socket_port)
20
21
 
21
- start_tls(req.uri.host, options[:ssl_socket_class], options[:ssl_context]) if req.uri.is_a?(URI::HTTPS) && !req.using_proxy?
22
+ @socket.start_tls(
23
+ req.uri.host,
24
+ options[:ssl_socket_class],
25
+ options[:ssl_context]
26
+ ) if req.uri.is_a?(URI::HTTPS) && !req.using_proxy?
22
27
 
23
28
  reset_timer
24
29
  end
25
30
 
26
31
  # Send a request to the server
32
+ #
33
+ # @param [Request] Request to send to the server
34
+ # @return [Nil]
27
35
  def send_request(req)
28
36
  if pending_response
29
37
  fail StateError, "Tried to send a request while one is pending already. Make sure you read off the body."
@@ -60,7 +68,7 @@ module HTTP
60
68
  chunk.to_s
61
69
  end
62
70
 
63
- # Reads data from socket up until headers
71
+ # Reads data from socket up until headers are loaded
64
72
  def read_headers!
65
73
  read_more BUFFER_SIZE until parser.headers
66
74
  set_keep_alive
@@ -131,23 +139,5 @@ module HTTP
131
139
  end
132
140
 
133
141
  private :read_more
134
-
135
- # Starts the SSL connection
136
- def start_tls(host, ssl_socket_class, ssl_context)
137
- # TODO: abstract away SSLContexts so we can use other TLS libraries
138
- ssl_context ||= OpenSSL::SSL::SSLContext.new
139
- @socket = ssl_socket_class.new(socket, ssl_context)
140
- socket.sync_close = true
141
-
142
- socket.connect
143
-
144
- if ssl_context.verify_mode == OpenSSL::SSL::VERIFY_PEER
145
- socket.post_connection_check(host)
146
- end
147
-
148
- socket
149
- end
150
-
151
- private :start_tls
152
142
  end
153
143
  end
@@ -11,6 +11,9 @@ module HTTP
11
11
  # Requested to do something when we're in the wrong state
12
12
  class StateError < ResponseError; end
13
13
 
14
+ # Generic Timeout error
15
+ class TimeoutError < Error; end
16
+
14
17
  # Generic Cache error
15
18
  class CacheError < Error; end
16
19
 
@@ -8,11 +8,13 @@ module HTTP
8
8
  @default_socket_class = TCPSocket
9
9
  @default_ssl_socket_class = OpenSSL::SSL::SSLSocket
10
10
 
11
+ @default_timeout_class = HTTP::Timeout::Null
12
+
11
13
  @default_cache = Http::Cache::NullCache.new
12
14
 
13
15
  class << self
14
16
  attr_accessor :default_socket_class, :default_ssl_socket_class
15
- attr_accessor :default_cache
17
+ attr_accessor :default_cache, :default_timeout_class
16
18
 
17
19
  def new(options = {})
18
20
  return options if options.is_a?(self)
@@ -41,6 +43,8 @@ module HTTP
41
43
  def initialize(options = {})
42
44
  defaults = {:response => :auto,
43
45
  :proxy => {},
46
+ :timeout_class => self.class.default_timeout_class,
47
+ :timeout_options => {},
44
48
  :socket_class => self.class.default_socket_class,
45
49
  :ssl_socket_class => self.class.default_ssl_socket_class,
46
50
  :cache => self.class.default_cache,
@@ -62,7 +66,7 @@ module HTTP
62
66
  %w(
63
67
  proxy params form json body follow response
64
68
  socket_class ssl_socket_class ssl_context
65
- persistent keep_alive_timeout
69
+ persistent keep_alive_timeout timeout_class timeout_options
66
70
  ).each do |method_name|
67
71
  def_option method_name
68
72
  end
@@ -0,0 +1,99 @@
1
+ # rubocop:disable Lint/HandleExceptions
2
+ module HTTP
3
+ module Timeout
4
+ class Global < PerOperation
5
+ attr_reader :time_left, :total_timeout
6
+
7
+ def initialize(*args)
8
+ super
9
+
10
+ @time_left = connect_timeout + read_timeout + write_timeout
11
+ @total_timeout = time_left
12
+ end
13
+
14
+ # Abstracted out from the normal connect for SSL connections
15
+ def connect_with_timeout(*args)
16
+ reset_timer
17
+
18
+ begin
19
+ socket.connect_nonblock(*args)
20
+
21
+ rescue IO::WaitReadable
22
+ IO.select([socket], nil, nil, time_left)
23
+ log_time
24
+ retry
25
+
26
+ rescue Errno::EINPROGRESS
27
+ IO.select(nil, [socket], nil, time_left)
28
+ log_time
29
+ retry
30
+
31
+ rescue Errno::EISCONN
32
+ end
33
+ end
34
+
35
+ # Read from the socket
36
+ def readpartial(size)
37
+ reset_timer
38
+
39
+ begin
40
+ socket.read_nonblock(size)
41
+ rescue IO::WaitReadable
42
+ IO.select([socket], nil, nil, time_left)
43
+ log_time
44
+ retry
45
+ end
46
+ end
47
+
48
+ # Write to the socket
49
+ def write(data)
50
+ reset_timer
51
+
52
+ begin
53
+ socket << data
54
+ rescue IO::WaitWritable
55
+ IO.select(nil, [socket], nil, time_left)
56
+ log_time
57
+ retry
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ # Create a DNS resolver
64
+ def resolve_address(host)
65
+ addr = HostResolver.getaddress(host)
66
+ return addr if addr
67
+
68
+ reset_timer
69
+
70
+ addr = Resolv::DNS.open(:timeout => time_left) do |dns|
71
+ dns.getaddress
72
+ end
73
+
74
+ log_time
75
+
76
+ addr
77
+
78
+ rescue Resolv::ResolvTimeout
79
+ raise TimeoutError, "DNS timed out after #{total_timeout} seconds"
80
+ end
81
+
82
+ # Due to the run/retry nature of nonblocking I/O, it's easier to keep track of time
83
+ # via method calls instead of a block to monitor.
84
+ def reset_timer
85
+ @started = Time.now
86
+ end
87
+
88
+ def log_time
89
+ @time_left -= (Time.now - @started)
90
+ if time_left <= 0
91
+ fail TimeoutError, "Timed out after using the allocated #{total_timeout} seconds"
92
+ end
93
+
94
+ reset_timer
95
+ end
96
+ end
97
+ end
98
+ end
99
+ # rubocop:enable Lint/HandleExceptions
@@ -0,0 +1,51 @@
1
+ require "forwardable"
2
+
3
+ module HTTP
4
+ module Timeout
5
+ class Null
6
+ extend Forwardable
7
+
8
+ def_delegators :@socket, :close, :closed?
9
+
10
+ attr_reader :options, :socket
11
+
12
+ def initialize(options = {})
13
+ @options = options
14
+ end
15
+
16
+ # Connects to a socket
17
+ def connect(socket_class, host, port)
18
+ @socket = socket_class.open(host, port)
19
+ end
20
+
21
+ # Starts a SSL connection on a socket
22
+ def connect_ssl
23
+ socket.connect
24
+ end
25
+
26
+ # Configures the SSL connection and starts the connection
27
+ def start_tls(host, ssl_socket_class, ssl_context)
28
+ # TODO: abstract away SSLContexts so we can use other TLS libraries
29
+ ssl_context ||= OpenSSL::SSL::SSLContext.new
30
+ @socket = ssl_socket_class.new(socket, ssl_context)
31
+ socket.sync_close = true
32
+
33
+ connect_ssl
34
+
35
+ socket.post_connection_check(host) if ssl_context.verify_mode == OpenSSL::SSL::VERIFY_PEER
36
+ end
37
+
38
+ # Read from the socket
39
+ def readpartial(size)
40
+ socket.readpartial(size)
41
+ end
42
+
43
+ # Write to the socket
44
+ def write(data)
45
+ socket << data
46
+ end
47
+
48
+ alias_method :<<, :write
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,117 @@
1
+ # rubocop:disable Lint/HandleExceptions
2
+ require "resolv"
3
+
4
+ module HTTP
5
+ module Timeout
6
+ class PerOperation < Null
7
+ HostResolver = Resolv::Hosts.new.tap(&:lazy_initialize)
8
+
9
+ CONNECT_TIMEOUT = 0.25
10
+ WRITE_TIMEOUT = 0.25
11
+ READ_TIMEOUT = 0.25
12
+
13
+ attr_reader :read_timeout, :write_timeout, :connect_timeout
14
+
15
+ def initialize(*args)
16
+ super
17
+
18
+ @read_timeout = options.fetch(:read_timeout, READ_TIMEOUT)
19
+ @write_timeout = options.fetch(:write_timeout, WRITE_TIMEOUT)
20
+ @connect_timeout = options.fetch(:connect_timeout, CONNECT_TIMEOUT)
21
+ end
22
+
23
+ def connect(_, host, port)
24
+ # https://github.com/celluloid/celluloid-io/blob/master/lib/celluloid/io/tcp_socket.rb
25
+ begin
26
+ addr = Resolv::IPv4.create(host)
27
+ rescue ArgumentError
28
+ end
29
+
30
+ # Guess it's not IPv4! Is it IPv6?
31
+ begin
32
+ addr ||= Resolv::IPv6.create(host)
33
+ rescue ArgumentError
34
+ end
35
+
36
+ unless addr
37
+ addr = resolve_address(host)
38
+ fail Resolv::ResolvError, "DNS result has no information for #{host}" unless addr
39
+ end
40
+
41
+ case addr
42
+ when Resolv::IPv4
43
+ family = Socket::AF_INET
44
+ when Resolv::IPv6
45
+ family = Socket::AF_INET6
46
+ else fail ArgumentError, "unsupported address class: #{addr.class}"
47
+ end
48
+
49
+ @socket = Socket.new(family, Socket::SOCK_STREAM, 0)
50
+
51
+ connect_with_timeout(Socket.sockaddr_in(port, addr.to_s))
52
+ end
53
+
54
+ # No changes need to be made for the SSL connection
55
+ alias_method :connect_with_timeout, :connect_ssl
56
+
57
+ # Read data from the socket
58
+ def readpartial(size)
59
+ socket.read_nonblock(size)
60
+ rescue IO::WaitReadable
61
+ if IO.select([socket], nil, nil, read_timeout)
62
+ retry
63
+ else
64
+ raise TimeoutError, "Read timed out after #{read_timeout} seconds"
65
+ end
66
+ end
67
+
68
+ # Write data to the socket
69
+ def write(data)
70
+ socket.write_nonblock(data)
71
+ rescue IO::WaitWritable
72
+ if IO.select(nil, [socket], nil, write_timeout)
73
+ retry
74
+ else
75
+ raise TimeoutError, "Read timed out after #{write_timeout} seconds"
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ # Actually do the connect after we're setup
82
+ def connect_with_timeout(*args)
83
+ socket.connect_nonblock(*args)
84
+
85
+ rescue IO::WaitReadable
86
+ if IO.select([socket], nil, nil, connect_timeout)
87
+ retry
88
+ else
89
+ raise TimeoutError, "Connection timed out after #{connect_timeout} seconds"
90
+ end
91
+
92
+ rescue Errno::EINPROGRESS
93
+ if IO.select(nil, [socket], nil, connect_timeout)
94
+ retry
95
+ else
96
+ raise TimeoutError, "Connection timed out after #{connect_timeout} seconds"
97
+ end
98
+
99
+ rescue Errno::EISCONN
100
+ end
101
+
102
+ # Create a DNS resolver
103
+ def resolve_address(host)
104
+ addr = HostResolver.getaddress(host)
105
+ return addr if addr
106
+
107
+ Resolv::DNS.open(:timeout => connect_timeout) do |dns|
108
+ dns.getaddress
109
+ end
110
+
111
+ rescue Resolv::ResolvTimeout
112
+ raise TimeoutError, "DNS timed out after #{connect_timeout} seconds"
113
+ end
114
+ end
115
+ end
116
+ end
117
+ # rubocop:enable Lint/HandleExceptions
@@ -1,3 +1,3 @@
1
1
  module HTTP
2
- VERSION = "0.8.0.pre3"
2
+ VERSION = "0.8.0.pre4"
3
3
  end
@@ -1,4 +1,4 @@
1
- require "support/connection_reuse_shared"
1
+ require "support/http_handling_shared"
2
2
  require "support/dummy_server"
3
3
  require "http/cache"
4
4
 
@@ -167,44 +167,34 @@ RSpec.describe HTTP::Client do
167
167
  end
168
168
  end
169
169
 
170
- include_context "handles shared connections" do
171
- let(:reuse_conn) { nil }
172
- let(:keep_alive_timeout) { 5 }
173
-
170
+ include_context "HTTP handling" do
171
+ let(:options) { {} }
174
172
  let(:server) { dummy }
175
- let(:client) do
176
- described_class.new(
177
- :persistent => reuse_conn,
178
- :keep_alive_timeout => keep_alive_timeout
179
- )
180
- end
173
+ let(:client) { described_class.new(options) }
181
174
  end
182
175
 
183
176
  describe "SSL" do
184
- let(:reuse_conn) { nil }
185
- let(:keep_alive_timeout) { 5 }
186
-
187
177
  let(:client) do
188
178
  described_class.new(
189
- :persistent => reuse_conn,
190
- :keep_alive_timeout => keep_alive_timeout,
191
- :ssl_context => OpenSSL::SSL::SSLContext.new.tap do |context|
192
- context.options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
193
-
194
- context.verify_mode = OpenSSL::SSL::VERIFY_PEER
195
- context.ca_file = File.join(certs_dir, "ca.crt")
196
- context.cert = OpenSSL::X509::Certificate.new(
197
- File.read(File.join(certs_dir, "client.crt"))
198
- )
199
- context.key = OpenSSL::PKey::RSA.new(
200
- File.read(File.join(certs_dir, "client.key"))
201
- )
202
- context
203
- end
179
+ options.merge(
180
+ :ssl_context => OpenSSL::SSL::SSLContext.new.tap do |context|
181
+ context.options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
182
+
183
+ context.verify_mode = OpenSSL::SSL::VERIFY_PEER
184
+ context.ca_file = File.join(certs_dir, "ca.crt")
185
+ context.cert = OpenSSL::X509::Certificate.new(
186
+ File.read(File.join(certs_dir, "client.crt"))
187
+ )
188
+ context.key = OpenSSL::PKey::RSA.new(
189
+ File.read(File.join(certs_dir, "client.key"))
190
+ )
191
+ context
192
+ end
193
+ )
204
194
  )
205
195
  end
206
196
 
207
- include_context "handles shared connections" do
197
+ include_context "HTTP handling", true do
208
198
  let(:server) { dummy_ssl }
209
199
  end
210
200
 
@@ -34,10 +34,13 @@ RSpec.describe HTTP::Options, "merge" do
34
34
  :json => {:bar => "bar"},
35
35
  :keep_alive_timeout => 10,
36
36
  :headers => {:accept => "xml", :bar => "bar"},
37
+ :timeout_options => {:foo => :bar},
37
38
  :proxy => {:proxy_address => "127.0.0.1", :proxy_port => 8080})
38
39
 
39
40
  expect(foo.merge(bar).to_hash).to eq(
40
41
  :response => :parsed_body,
42
+ :timeout_class => described_class.default_timeout_class,
43
+ :timeout_options => {:foo => :bar},
41
44
  :params => {:plop => "plip"},
42
45
  :form => {:bar => "bar"},
43
46
  :body => "body-bar",
@@ -40,6 +40,20 @@ class DummyServer < WEBrick::HTTPServer
40
40
  end
41
41
  end
42
42
 
43
+ get "/sleep" do |_, res|
44
+ sleep 2
45
+
46
+ res.status = 200
47
+ res.body = "hello"
48
+ end
49
+
50
+ post "/sleep" do |_, res|
51
+ sleep 2
52
+
53
+ res.status = 200
54
+ res.body = "hello"
55
+ end
56
+
43
57
  ["", "/1", "/2"].each do |path|
44
58
  get "/socket#{path}" do |req, res|
45
59
  self.class.sockets << req.instance_variable_get(:@socket)
@@ -0,0 +1,207 @@
1
+ RSpec.shared_context "HTTP handling" do |ssl = false|
2
+ describe "timeouts" do
3
+ let(:conn_timeout) { 1 }
4
+ let(:read_timeout) { 1 }
5
+ let(:write_timeout) { 1 }
6
+
7
+ let(:options) do
8
+ {
9
+ :timeout_class => timeout_class,
10
+ :timeout_options => {
11
+ :connect_timeout => conn_timeout,
12
+ :read_timeout => read_timeout,
13
+ :write_timeout => write_timeout
14
+ }
15
+ }
16
+ end
17
+
18
+ context "without timeouts" do
19
+ let(:timeout_class) { HTTP::Timeout::Null }
20
+ let(:conn_timeout) { 0 }
21
+ let(:read_timeout) { 0 }
22
+ let(:write_timeout) { 0 }
23
+
24
+ it "works" do
25
+ expect(client.get(server.endpoint).body.to_s).to eq("<!doctype html>")
26
+ end
27
+ end
28
+
29
+ context "with a per operation timeout" do
30
+ let(:timeout_class) { HTTP::Timeout::PerOperation }
31
+
32
+ let(:response) { client.get(server.endpoint).body.to_s }
33
+
34
+ it "works" do
35
+ expect(response).to eq("<!doctype html>")
36
+ end
37
+
38
+ context "connection" do
39
+ context "of 1" do
40
+ let(:conn_timeout) { 1 }
41
+
42
+ it "does not time out" do
43
+ expect { response }.to_not raise_error
44
+ end
45
+ end
46
+ end
47
+
48
+ context "read" do
49
+ context "of 0" do
50
+ let(:read_timeout) { 0 }
51
+
52
+ it "times out" do
53
+ expect { response }.to raise_error(HTTP::TimeoutError, /Read/i)
54
+ end
55
+ end
56
+
57
+ context "of 2.5" do
58
+ let(:read_timeout) { 2.5 }
59
+
60
+ it "does not time out" do
61
+ expect { client.get("#{server.endpoint}/sleep").body.to_s }.to_not raise_error
62
+ end
63
+ end
64
+ end
65
+ end
66
+
67
+ context "with a global timeout" do
68
+ let(:timeout_class) { HTTP::Timeout::Global }
69
+
70
+ let(:conn_timeout) { 0 }
71
+ let(:read_timeout) { 1 }
72
+ let(:write_timeout) { 0 }
73
+
74
+ let(:response) { client.get(server.endpoint).body.to_s }
75
+
76
+ context "with localhost" do
77
+ let(:endpoint) { server.endpoint.sub("127.0.0.1", "localhost") }
78
+
79
+ it "errors if DNS takes too long" do
80
+ # Block the localhost lookup
81
+ expect(timeout_class::HostResolver)
82
+ .to receive(:getaddress).with("localhost").and_return(nil)
83
+
84
+ # Request
85
+ expect(Resolv::DNS).to receive(:open).with(:timeout => 1) do |_|
86
+ sleep 1.25
87
+ "127.0.0.1"
88
+ end
89
+
90
+ expect { client.get(server.endpoint.sub("127.0.0.1", "localhost")) }
91
+ .to raise_error(HTTP::TimeoutError, /Timed out/)
92
+ end
93
+ end
94
+
95
+ it "errors if connecting takes too long" do
96
+ socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
97
+
98
+ fake_socket = double(:to_io => socket)
99
+ expect(fake_socket).to receive(:connect_nonblock) do |*args|
100
+ sleep 1.25
101
+ socket.connect_nonblock(*args)
102
+ end
103
+
104
+ allow_any_instance_of(timeout_class).to receive(:socket).and_return(fake_socket)
105
+
106
+ expect { response }.to raise_error(HTTP::TimeoutError, /Timed out/)
107
+ end
108
+
109
+ it "errors if reading takes too long" do
110
+ expect { client.get("#{server.endpoint}/sleep").body.to_s }
111
+ .to raise_error(HTTP::TimeoutError, /Timed out/)
112
+ end
113
+
114
+ unless ssl
115
+ it "errors if writing takes too long" do
116
+ socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0)
117
+ allow_any_instance_of(timeout_class).to receive(:socket).and_return(socket)
118
+
119
+ expect(socket).to receive(:<<) do |*|
120
+ sleep 1.25
121
+ end
122
+
123
+ expect { response }.to raise_error(HTTP::TimeoutError, /Timed out/)
124
+ end
125
+ end
126
+ end
127
+ end
128
+
129
+ describe "connection reuse" do
130
+ let(:sockets_used) do
131
+ [
132
+ client.get("#{server.endpoint}/socket/1").body.to_s,
133
+ client.get("#{server.endpoint}/socket/2").body.to_s
134
+ ]
135
+ end
136
+
137
+ context "when enabled" do
138
+ let(:options) { {:persistent => server.endpoint} }
139
+
140
+ context "without a host" do
141
+ it "infers host from persistent config" do
142
+ expect(client.get("/").body.to_s).to eq("<!doctype html>")
143
+ end
144
+ end
145
+
146
+ it "re-uses the socket" do
147
+ expect(sockets_used).to_not include("")
148
+ expect(sockets_used.uniq.length).to eq(1)
149
+ end
150
+
151
+ context "when trying to read a stale body" do
152
+ it "errors" do
153
+ client.get("#{server.endpoint}/not-found")
154
+ expect { client.get(server.endpoint) }.to raise_error(HTTP::StateError, /Tried to send a request/)
155
+ end
156
+ end
157
+
158
+ context "when reading a cached body" do
159
+ it "succeeds" do
160
+ first_res = client.get(server.endpoint)
161
+ first_res.body.to_s
162
+
163
+ second_res = client.get(server.endpoint)
164
+
165
+ expect(first_res.body.to_s).to eq("<!doctype html>")
166
+ expect(second_res.body.to_s).to eq("<!doctype html>")
167
+ end
168
+ end
169
+
170
+ context "with a socket issue" do
171
+ it "transparently reopens" do
172
+ first_socket = client.get("#{server.endpoint}/socket").body.to_s
173
+ expect(first_socket).to_not eq("")
174
+ # Kill off the sockets we used
175
+ # rubocop:disable Style/RescueModifier
176
+ DummyServer::Servlet.sockets.each do |socket|
177
+ socket.close rescue nil
178
+ end
179
+ DummyServer::Servlet.sockets.clear
180
+ # rubocop:enable Style/RescueModifier
181
+
182
+ # Should error because we tried to use a bad socket
183
+ expect { client.get("#{server.endpoint}/socket").body.to_s }.to raise_error(IOError)
184
+
185
+ # Should succeed since we create a new socket
186
+ second_socket = client.get("#{server.endpoint}/socket").body.to_s
187
+ expect(second_socket).to_not eq(first_socket)
188
+ end
189
+ end
190
+
191
+ context "with a change in host" do
192
+ it "errors" do
193
+ expect { client.get("https://invalid.com/socket") }.to raise_error(/Persistence is enabled/i)
194
+ end
195
+ end
196
+ end
197
+
198
+ context "when disabled" do
199
+ let(:options) { {} }
200
+
201
+ it "opens new sockets" do
202
+ expect(sockets_used).to_not include("")
203
+ expect(sockets_used.uniq.length).to eq(2)
204
+ end
205
+ end
206
+ end
207
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: http
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0.pre3
4
+ version: 0.8.0.pre4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tony Arcieri
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2015-03-28 00:00:00.000000000 Z
13
+ date: 2015-03-29 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: http_parser.rb
@@ -104,6 +104,9 @@ files:
104
104
  - lib/http/response/status.rb
105
105
  - lib/http/response/status/reasons.rb
106
106
  - lib/http/response/string_body.rb
107
+ - lib/http/timeout/global.rb
108
+ - lib/http/timeout/null.rb
109
+ - lib/http/timeout/per_operation.rb
107
110
  - lib/http/version.rb
108
111
  - logo.png
109
112
  - spec/lib/http/cache/headers_spec.rb
@@ -138,6 +141,7 @@ files:
138
141
  - spec/support/create_certs.rb
139
142
  - spec/support/dummy_server.rb
140
143
  - spec/support/dummy_server/servlet.rb
144
+ - spec/support/http_handling_shared.rb
141
145
  - spec/support/proxy_server.rb
142
146
  - spec/support/servers/config.rb
143
147
  - spec/support/servers/runner.rb
@@ -198,6 +202,7 @@ test_files:
198
202
  - spec/support/create_certs.rb
199
203
  - spec/support/dummy_server.rb
200
204
  - spec/support/dummy_server/servlet.rb
205
+ - spec/support/http_handling_shared.rb
201
206
  - spec/support/proxy_server.rb
202
207
  - spec/support/servers/config.rb
203
208
  - spec/support/servers/runner.rb