Permalink
Browse files

Add keep-alive support

For ease of implementation it rely on the excellent net-http-persistent.

Persistent connections are now the default, but can be turned off globaly with
```ruby
RestClient.persistent = false
```
Or on a request basis by passing `:persistent => false`

If you are not confident with making persistent connections the default you can
easily revert it by removing the `@persistent = true` line in `restclient.rb`.
The test suite will pass whatever the default is.
  • Loading branch information...
1 parent 7ca1f63 commit 951f39aa4b9052d00d68e064f6105e9b510b0c48 @byroot byroot committed Mar 22, 2013
Showing with 225 additions and 92 deletions.
  1. +38 −0 Gemfile.lock
  2. +4 −1 lib/restclient.rb
  3. +18 −20 lib/restclient/request.rb
  4. +1 −0 rest-client.gemspec
  5. +57 −27 spec/integration/request_spec.rb
  6. +107 −44 spec/request_spec.rb
View
@@ -0,0 +1,38 @@
+PATH
+ remote: .
+ specs:
+ rest-client (1.6.7)
+ mime-types (>= 1.16)
+ net-http-persistent
+ netrc (~> 0.7.7)
+
+GEM
+ remote: https://rubygems.org/
+ specs:
+ addressable (2.3.3)
+ crack (0.3.2)
+ diff-lcs (1.2.1)
+ mime-types (1.21)
+ net-http-persistent (2.8)
+ netrc (0.7.7)
+ rake (10.0.3)
+ rspec (2.13.0)
+ rspec-core (~> 2.13.0)
+ rspec-expectations (~> 2.13.0)
+ rspec-mocks (~> 2.13.0)
+ rspec-core (2.13.1)
+ rspec-expectations (2.13.0)
+ diff-lcs (>= 1.1.3, < 2.0)
+ rspec-mocks (2.13.0)
+ webmock (1.11.0)
+ addressable (>= 2.2.7)
+ crack (>= 0.3.2)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ rake
+ rest-client!
+ rspec (>= 2.0)
+ webmock (>= 0.9.1)
View
@@ -1,6 +1,7 @@
require 'uri'
require 'zlib'
require 'stringio'
+require 'net/http/persistent'
begin
require 'net/https'
@@ -93,9 +94,11 @@ def self.options(url, headers={}, &block)
end
class << self
- attr_accessor :proxy
+ attr_accessor :proxy, :persistent
end
+ @persistent = true
+
# Setup the log for RestClient calls.
# Value should be a logger but can can be stdout, stderr, or a filename.
# You can also configure logging by the environment variable RESTCLIENT_LOG.
View
@@ -30,7 +30,7 @@ class Request
:payload, :user, :password, :timeout, :max_redirects,
:open_timeout, :raw_response, :verify_ssl, :ssl_client_cert,
:ssl_client_key, :ssl_ca_file, :processed_headers, :args,
- :ssl_version
+ :ssl_version, :cert_store, :persistent
def self.execute(args, & block)
new(args).execute(& block)
@@ -57,6 +57,8 @@ def initialize args
@ssl_client_key = args[:ssl_client_key] || nil
@ssl_ca_file = args[:ssl_ca_file] || nil
@ssl_version = args[:ssl_version] || 'SSLv3'
+ @cert_store = args[:cert_store]
+ @persistent = args.has_key?(:persistent) ? args[:persistent] : RestClient.persistent
@tf = nil # If you are a raw request, this is your tempfile
@max_redirects = args[:max_redirects] || 10
@processed_headers = make_headers headers
@@ -98,15 +100,6 @@ def make_headers user_headers
headers
end
- def net_http_class
- if RestClient.proxy
- proxy_uri = URI.parse(RestClient.proxy)
- Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
- else
- Net::HTTP
- end
- end
-
def net_http_request_class(method)
Net::HTTP.const_get(method.to_s.capitalize)
end
@@ -146,8 +139,11 @@ def process_payload(p=nil, parent_key=nil)
def transmit uri, req, payload, & block
setup_credentials req
- net = net_http_class.new(uri.host, uri.port)
- net.use_ssl = uri.is_a?(URI::HTTPS)
+ proxy = URI.parse(RestClient.proxy) if RestClient.proxy
+ app_name = 'rest-client'
+ app_name += '-persistent' if @persistent
+ net = Net::HTTP::Persistent.new(app_name, proxy)
+
net.ssl_version = @ssl_version
err_msg = nil
if (@verify_ssl == false) || (@verify_ssl == OpenSSL::SSL::VERIFY_NONE)
@@ -163,6 +159,7 @@ def transmit uri, req, payload, & block
end
end
net.cert = @ssl_client_cert if @ssl_client_cert
+ net.cert_store = @cert_store if @cert_store
net.key = @ssl_client_key if @ssl_client_key
net.ca_file = @ssl_ca_file if @ssl_ca_file
net.read_timeout = @timeout if @timeout
@@ -178,14 +175,13 @@ def transmit uri, req, payload, & block
log_request
- net.start do |http|
- if @block_response
- http.request(req, payload ? payload.to_s : nil, & @block_response)
- else
- res = http.request(req, payload ? payload.to_s : nil) { |http_response| fetch_body(http_response) }
- log_response res
- process_result res, & block
- end
+ req.body = payload.to_s if payload
+ if @block_response
+ net.request(uri, req, & @block_response)
+ else
+ res = net.request(uri, req) { |http_response| fetch_body(http_response) }
+ log_response res
+ process_result res, & block
end
rescue OpenSSL::SSL::SSLError => e
if err_msg
@@ -197,6 +193,8 @@ def transmit uri, req, payload, & block
raise RestClient::ServerBrokeConnection
rescue Timeout::Error
raise RestClient::RequestTimeout
+ ensure
+ net.shutdown unless @persistent
end
def setup_credentials(req)
View
@@ -15,6 +15,7 @@ Gem::Specification.new do |s|
s.summary = 'Simple HTTP and REST client for Ruby, inspired by microframework syntax for specifying actions.'
s.add_runtime_dependency(%q<mime-types>, [">= 1.16"])
+ s.add_runtime_dependency(%q<net-http-persistent>)
s.add_development_dependency(%q<webmock>, [">= 0.9.1"])
s.add_development_dependency(%q<rspec>, [">= 2.0"])
s.add_dependency(%q<netrc>, ["~> 0.7.7"])
@@ -1,35 +1,65 @@
require File.join( File.dirname(File.expand_path(__FILE__)), '../base')
describe RestClient::Request do
- describe "ssl verification" do
- it "is successful with the correct ca_file" do
- request = RestClient::Request.new(
- :method => :get,
- :url => 'https://www.mozilla.com',
- :verify_ssl => OpenSSL::SSL::VERIFY_PEER,
- :ssl_ca_file => File.join(File.dirname(__FILE__), "certs", "equifax.crt")
- )
- expect { request.execute }.to_not raise_error
+ shared_examples 'an http client' do
+ describe "ssl verification" do
+ it "is successful with the correct ca_file" do
+ request = RestClient::Request.new(
+ :method => :get,
+ :url => 'https://www.mozilla.com',
+ :verify_ssl => OpenSSL::SSL::VERIFY_PEER,
+ :ssl_ca_file => File.join(File.dirname(__FILE__), "certs", "equifax.crt")
+ )
+ expect { request.execute }.to_not raise_error
+ end
+
+ # I don' think this feature is useful anymore (under 1.9.3 at the very least).
+ #
+ # Exceptions in verify_callback are ignored; RestClient has to catch OpenSSL::SSL::SSLError
+ # and either re-throw it as is, or throw SSLCertificateNotVerified
+ # based on the contents of the message field of the original exception
+ #.
+ # The client has to handle OpenSSL::SSL::SSLError exceptions anyway,
+ # why make them handle both OpenSSL *AND* RestClient exceptions???
+ #
+ # also see https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl.c#L237
+ it "is unsuccessful with an incorrect ca_file" do
+ request = RestClient::Request.new(
+ :method => :get,
+ :url => 'https://www.mozilla.com',
+ :verify_ssl => OpenSSL::SSL::VERIFY_PEER,
+ :ssl_ca_file => File.join(File.dirname(__FILE__), "certs", "verisign.crt"),
+ :cert_store => OpenSSL::X509::Store.new
+ )
+ expect { request.execute }.to raise_error(RestClient::SSLCertificateNotVerified)
+ end
+ end
+ end
+
+ describe 'with keep alive' do
+ before :each do
+ @previous_persistent = RestClient.persistent
+ RestClient.persistent = true
+ end
+
+ after :each do
+ RestClient.persistent = @previous_persistent
end
- # I don' think this feature is useful anymore (under 1.9.3 at the very least).
- #
- # Exceptions in verify_callback are ignored; RestClient has to catch OpenSSL::SSL::SSLError
- # and either re-throw it as is, or throw SSLCertificateNotVerified
- # based on the contents of the message field of the original exception
- #.
- # The client has to handle OpenSSL::SSL::SSLError exceptions anyway,
- # why make them handle both OpenSSL *AND* RestClient exceptions???
- #
- # also see https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl.c#L237
- it "is unsuccessful with an incorrect ca_file" do
- request = RestClient::Request.new(
- :method => :get,
- :url => 'https://www.mozilla.com',
- :verify_ssl => OpenSSL::SSL::VERIFY_PEER,
- :ssl_ca_file => File.join(File.dirname(__FILE__), "certs", "verisign.crt")
- )
- expect { request.execute }.to raise_error(RestClient::SSLCertificateNotVerified)
+ it_should_behave_like 'an http client'
+ end
+
+ describe 'without keep alive' do
+ before :each do
+ @previous_persistent = RestClient.persistent
+ RestClient.persistent = false
+ end
+
+ after :each do
+ RestClient.persistent = @previous_persistent
end
+
+ it_should_behave_like 'an http client'
end
+
end
Oops, something went wrong.

0 comments on commit 951f39a

Please sign in to comment.