Skip to content

Commit a3a00ea

Browse files
author
Brian D. Burns
committed
persistent option
1 parent 95a8213 commit a3a00ea

11 files changed

+227
-102
lines changed

README.md

+39-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,41 @@ post_response = connection.post(:path => '/foo')
4545
delete_response = connection.delete(:path => '/bar')
4646
```
4747

48+
By default, each connection is non-persistent. This means that each request made against a connection behaves like a
49+
one-off request. Each request will establish a socket connection to the server, then close the socket once the request
50+
is complete.
51+
52+
To use a persistent connection, use the `:persistent` option:
53+
54+
```ruby
55+
connection = Excon.new('http://geemus.com', :persistent => true)
56+
```
57+
58+
The initial request will establish a socket connection to the server and leave the socket open. Subsequent requests
59+
will reuse that socket. You may call `Connection#reset` at any time to close the underlying socket, and the next request
60+
will establish a new socket connection.
61+
62+
You may also control persistence on a per-request basis by setting the `:persistent` option for each request.
63+
64+
```ruby
65+
connection = Excon.new('http://geemus.com') # non-persistent by default
66+
connection.get # socket established, then closed
67+
connection.get(:persistent => true) # socket established, left open
68+
connection.get(:persistent => true) # socket reused
69+
connection.get # socket reused, then closed
70+
71+
connection = Excon.new('http://geemus.com', :persistent => true)
72+
connection.get # socket established, left open
73+
connection.get(:persistent => false) # socket reused, then closed
74+
connection.get(:persistent => false) # socket established, then closed
75+
connection.get # socket established, left open
76+
connection.get # socket reused
77+
```
78+
79+
Note that sending a request with `:persistent => false` to close the socket will also send `Connection: close` to inform
80+
the server the connection is no longer needed. `Connection#reset` will simply close our end of the socket.
81+
82+
4883
Options
4984
-------
5085

@@ -131,13 +166,16 @@ Iterating in this way allows you to have more granular control over writes and t
131166
Pipelining Requests
132167
------------------
133168

134-
You can make use of HTTP pipelining to improve performance. Instead of the normal request/response cyle, pipelining sends a series of requests and then receives a series of responses. You can take advantage of this using the `requests` method, which takes an array of params where each is a hash like request would receive and returns an array of responses.
169+
You can make use of HTTP pipelining to improve performance. Instead of the normal request/response cycle, pipelining sends a series of requests and then receives a series of responses. You can take advantage of this using the `requests` method, which takes an array of params where each is a hash like request would receive and returns an array of responses.
135170

136171
```ruby
137172
connection = Excon.new('http://geemus.com/')
138173
connection.requests([{:method => :get}, {:method => :get}])
139174
```
140175

176+
By default, each call to `requests` will use a separate persistent socket connection. To make multiple `requests` calls
177+
using a single persistent connection, set `:persistent => true` when establishing the connection.
178+
141179
Streaming Responses
142180
-------------------
143181

lib/excon.rb

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def defaults
4141
:mock => false,
4242
:nonblock => true,
4343
:omit_default_port => false,
44+
:persistent => false,
4445
:read_timeout => 60,
4546
:retry_limit => DEFAULT_RETRY_LIMIT,
4647
:ssl_verify_peer => true,

lib/excon/connection.rb

+19-8
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ def request_call(datum)
152152
else
153153
datum[:headers]['TE'] = 'trailers, deflate, gzip'
154154
end
155-
datum[:headers]['Connection'] = 'TE'
155+
datum[:headers]['Connection'] = datum[:persistent] ? 'TE' : 'TE, close'
156156

157157
# add headers to request
158158
datum[:headers].each do |key, values|
@@ -267,10 +267,14 @@ def request(params={}, &block)
267267
unless datum[:pipeline]
268268
datum = response(datum)
269269

270-
if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Connection') == 0 }
271-
if split_header_value(datum[:response][:headers][key]).any? {|t| t.casecmp('close') }
272-
reset
270+
if datum[:persistent]
271+
if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Connection') == 0 }
272+
if split_header_value(datum[:response][:headers][key]).any? {|t| t.casecmp('close') }
273+
reset
274+
end
273275
end
276+
else
277+
reset
274278
end
275279

276280
Excon::Response.new(datum[:response])
@@ -290,16 +294,23 @@ def request(params={}, &block)
290294
# Sends the supplied requests to the destination host using pipelining.
291295
# @pipeline_params [Array<Hash>] pipeline_params An array of one or more optional params, override defaults set in Connection.new, see #request for details
292296
def requests(pipeline_params)
297+
pipeline_params.each {|params| params.merge!(:pipeline => true, :persistent => true) }
298+
pipeline_params.last.merge!(:persistent => @data[:persistent])
299+
293300
responses = pipeline_params.map do |params|
294-
request(params.merge!(:pipeline => true))
301+
request(params)
295302
end.map do |datum|
296303
Excon::Response.new(response(datum)[:response])
297304
end
298305

299-
if key = responses.last[:headers].keys.detect {|k| k.casecmp('Connection') == 0 }
300-
if split_header_value(responses.last[:headers][key]).any? {|t| t.casecmp('close') }
301-
reset
306+
if @data[:persistent]
307+
if key = responses.last[:headers].keys.detect {|k| k.casecmp('Connection') == 0 }
308+
if split_header_value(responses.last[:headers][key]).any? {|t| t.casecmp('close') }
309+
reset
310+
end
302311
end
312+
else
313+
reset
303314
end
304315

305316
responses

lib/excon/constants.rb

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ module Excon
4646
:middlewares,
4747
:mock,
4848
:path,
49+
:persistent,
4950
:pipeline,
5051
:query,
5152
:read_timeout,

tests/basic_tests.rb

+1-2
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,7 @@
6161

6262
basic_tests('https://127.0.0.1:8443',
6363
:client_key => File.join(File.dirname(__FILE__), 'data', 'excon.cert.key'),
64-
:client_cert => File.join(File.dirname(__FILE__), 'data', 'excon.cert.crt'),
65-
:reset_connection => RUBY_VERSION == '1.9.2'
64+
:client_cert => File.join(File.dirname(__FILE__), 'data', 'excon.cert.crt')
6665
)
6766

6867
end

tests/middlewares/mock_tests.rb

-6
Original file line numberDiff line numberDiff line change
@@ -219,11 +219,5 @@
219219
Excon.stubs.clear
220220
end
221221

222-
tests('mock = false') do
223-
with_rackup('basic.ru') do
224-
basic_tests
225-
end
226-
end
227-
228222
env_restore
229223
end

tests/pipeline_tests.rb

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
Shindo.tests('Pipelined Requests') do
2+
with_server('good') do
3+
4+
tests('with default :persistent => true') do
5+
connection = Excon.new('http://127.0.0.1:9292', :persistent => true)
6+
7+
returns(%w{ 1 2 3 4 }, 'connection is persistent') do
8+
ret = []
9+
ret << connection.requests([
10+
{:method => :get, :path => '/echo/request_count'},
11+
{:method => :get, :path => '/echo/request_count'}
12+
]).map(&:body)
13+
ret << connection.requests([
14+
{:method => :get, :path => '/echo/request_count'},
15+
{:method => :get, :path => '/echo/request_count'}
16+
]).map(&:body)
17+
ret.flatten
18+
end
19+
end
20+
21+
tests('with default :persistent => false') do
22+
connection = Excon.new('http://127.0.0.1:9292', :persistent => false)
23+
24+
returns(%w{ 1 2 1 2 }, 'connection is persistent per call to #requests') do
25+
ret = []
26+
ret << connection.requests([
27+
{:method => :get, :path => '/echo/request_count'},
28+
{:method => :get, :path => '/echo/request_count'}
29+
]).map(&:body)
30+
ret << connection.requests([
31+
{:method => :get, :path => '/echo/request_count'},
32+
{:method => :get, :path => '/echo/request_count'}
33+
]).map(&:body)
34+
ret.flatten
35+
end
36+
37+
end
38+
39+
end
40+
end

tests/request_tests.rb

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
Shindo.tests('Request Tests') do
2+
with_server('good') do
3+
4+
tests('sets transfer-coding and connection options') do
5+
6+
tests('without a :response_block') do
7+
request = Marshal.load(
8+
Excon.get('http://127.0.0.1:9292/echo/request').body
9+
)
10+
11+
returns('trailers, deflate, gzip', 'sets encoding options') do
12+
request[:headers]['TE']
13+
end
14+
15+
returns(true, 'TE added to Connection header') do
16+
request[:headers]['Connection'].include?('TE')
17+
end
18+
end
19+
20+
tests('with a :response_block') do
21+
captures = capture_response_block do |block|
22+
Excon.get('http://127.0.0.1:9292/echo/request',
23+
:response_block => block)
24+
end
25+
data = captures.map {|capture| capture[0] }.join
26+
request = Marshal.load(data)
27+
28+
returns('trailers', 'does not set encoding options') do
29+
request[:headers]['TE']
30+
end
31+
32+
returns(true, 'TE added to Connection header') do
33+
request[:headers]['Connection'].include?('TE')
34+
end
35+
end
36+
37+
end
38+
39+
tests('persistent connections') do
40+
41+
tests('with default :persistent => true') do
42+
connection = nil
43+
44+
returns(['1', '2'], 'uses a persistent connection') do
45+
connection = Excon.new('http://127.0.0.1:9292', :persistent => true)
46+
2.times.map do
47+
connection.request(:method => :get, :path => '/echo/request_count').body
48+
end
49+
end
50+
51+
returns(['3', '1', '2'], ':persistent => false resets connection') do
52+
ret = []
53+
ret << connection.request(:method => :get,
54+
:path => '/echo/request_count',
55+
:persistent => false).body
56+
ret << connection.request(:method => :get,
57+
:path => '/echo/request_count').body
58+
ret << connection.request(:method => :get,
59+
:path => '/echo/request_count').body
60+
end
61+
end
62+
63+
tests('with default :persistent => false') do
64+
connection = nil
65+
66+
returns(['1', '1'], 'does not use a persistent connection') do
67+
connection = Excon.new('http://127.0.0.1:9292', :persistent => false)
68+
2.times.map do
69+
connection.request(:method => :get, :path => '/echo/request_count').body
70+
end
71+
end
72+
73+
returns(['1', '2', '3', '1'], ':persistent => true enables persistence') do
74+
ret = []
75+
ret << connection.request(:method => :get,
76+
:path => '/echo/request_count',
77+
:persistent => true).body
78+
ret << connection.request(:method => :get,
79+
:path => '/echo/request_count',
80+
:persistent => true).body
81+
ret << connection.request(:method => :get,
82+
:path => '/echo/request_count').body
83+
ret << connection.request(:method => :get,
84+
:path => '/echo/request_count').body
85+
end
86+
end
87+
88+
tests('sends `Connection: close`') do
89+
returns(true, 'when :persistent => false') do
90+
request = Marshal.load(
91+
Excon.get('http://127.0.0.1:9292/echo/request',
92+
:persistent => false).body
93+
)
94+
request[:headers]['Connection'].include?('close')
95+
end
96+
97+
returns(false, 'not when :persistent => true') do
98+
request = Marshal.load(
99+
Excon.get('http://127.0.0.1:9292/echo/request',
100+
:persistent => true).body
101+
)
102+
request[:headers]['Connection'].include?('close')
103+
end
104+
end
105+
106+
end
107+
108+
end
109+
end

tests/requests_tests.rb

-73
This file was deleted.

tests/servers/good.rb

+7
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@ def send_response(request)
3030
send_data "\r\n"
3131
send_data data
3232

33+
when 'request_count'
34+
(@request_count ||= '0').next!
35+
start_response
36+
send_data "Content-Length: #{ @request_count.size }\r\n"
37+
send_data "\r\n"
38+
send_data @request_count
39+
3340
when /(content|transfer)-encoded\/?(.*)/
3441
if (encoding_type = $1) == 'content'
3542
accept_header = 'Accept-Encoding'

0 commit comments

Comments
 (0)