http 0.8.0.pre3 → 0.8.0.pre4

Sign up to get free protection for your applications and to get access to all the features.
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