diff -Nru ruby-excon-0.25.1/Gemfile ruby-excon-0.28.0/Gemfile --- ruby-excon-0.25.1/Gemfile 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/Gemfile 2013-11-12 16:08:34.000000000 +0000 @@ -3,6 +3,7 @@ gemspec gem 'jruby-openssl', :platform => :jruby +gem 'unicorn', :platform => :mri # group :benchmark do # gem 'em-http-request' diff -Nru ruby-excon-0.25.1/Gemfile.lock ruby-excon-0.28.0/Gemfile.lock --- ruby-excon-0.25.1/Gemfile.lock 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/Gemfile.lock 2013-11-12 16:08:34.000000000 +0000 @@ -1,7 +1,7 @@ PATH remote: . specs: - excon (0.25.1) + excon (0.28.0) GEM remote: http://rubygems.org/ @@ -9,7 +9,7 @@ activesupport (3.2.6) i18n (~> 0.6) multi_json (~> 1.0) - bouncy-castle-java (1.5.0146.1) + bouncy-castle-java (1.5.0147) chronic (0.6.7) delorean (2.0.0) chronic @@ -17,15 +17,17 @@ eventmachine (1.0.0-java) formatador (0.2.3) i18n (0.6.0) - jruby-openssl (0.7.7) - bouncy-castle-java (>= 1.5.0146.1) + jruby-openssl (0.8.8) + bouncy-castle-java (>= 1.5.0147) json (1.7.3) json (1.7.3-java) + kgio (2.8.0) multi_json (1.3.6) open4 (1.3.0) rack (1.4.1) rack-protection (1.2.0) rack + raindrops (0.11.0) rake (0.9.2.2) rdoc (3.12) json (~> 1.4) @@ -36,6 +38,10 @@ rack-protection (~> 1.2) tilt (~> 1.3, >= 1.3.3) tilt (1.3.3) + unicorn (4.6.3) + kgio (~> 2.6) + rack + raindrops (~> 0.7) PLATFORMS java @@ -52,3 +58,4 @@ rdoc shindo sinatra + unicorn diff -Nru ruby-excon-0.25.1/README.md ruby-excon-0.28.0/README.md --- ruby-excon-0.25.1/README.md 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/README.md 2013-11-12 16:08:34.000000000 +0000 @@ -1,7 +1,9 @@ excon ===== -Usable, fast, simple Ruby HTTP 1.0 +Usable, fast, simple Ruby HTTP 1.1 + +Excon was designed to be simple, fast and performant. It works great as a general HTTP(s) client and is particularly well suited to usage in API clients. [![Build Status](https://secure.travis-ci.org/geemus/excon.png)](http://travis-ci.org/geemus/excon) [![Dependency Status](https://gemnasium.com/geemus/excon.png)](https://gemnasium.com/geemus/excon) @@ -12,130 +14,142 @@ Install the gem. - $ sudo gem install excon +``` +$ sudo gem install excon +``` Require with rubygems. - require 'rubygems' - require 'excon' - -The simplest way to use excon is with one-off requests: - - response = Excon.get('http://geemus.com') - -Supported one-off request methods are #connect, #delete, #get, #head, #options, #post, #put, and #trace. - -The returned response object has #body, #headers and #status attributes. - -Alternately you can create a connection object which is reusable across multiple requests (more performant!). - - connection = Excon.new('http://geemus.com') - response_one = connection.get - response_two = connection.post(:path => '/foo') - response_three = connection.delete(:path => '/bar') - -Sometimes it is more convenient to specify the request type as an argument: - - response_four = connection.request(:method => :get, :path => '/more') - -Both one-off and persistent connections support many other options. Here are a few common examples: - - # Custom headers - Excon.get('http://geemus.com', :headers => {'Authorization' => 'Basic 0123456789ABCDEF'}) - connection.get(:headers => {'Authorization' => 'Basic 0123456789ABCDEF'}) - - # Changing query strings - connection = Excon.new('http://geemus.com/') - connection.get(:query => {:foo => 'bar'}) - - # POST body encoded with application/x-www-form-urlencoded - Excon.post('http://geemus.com', - :body => 'language=ruby&class=fog', - :headers => { "Content-Type" => "application/x-www-form-urlencoded" }) - - # same again, but using URI to build the body of parameters - Excon.post('http://geemus.com', - :body => URI.encode_www_form(:language => 'ruby', :class => 'fog'), - :headers => { "Content-Type" => "application/x-www-form-urlencoded" }) - - # request accepts either symbols or strings - connection.request(:method => :get) - connection.request(:method => 'GET') - - # this request can be repeated safely, so retry on errors up to 3 times - connection.request(:idempotent => true) - - # this request can be repeated safely, retry up to 6 times - connection.request(:idempotent => true, :retry_limit => 6) - - # opt-out of nonblocking operations for performance and/or as a workaround - connection.request(:nonblock => false) - - # opt-in to omitting port from http:80 and https:443 - connection.request(:omit_default_port => true) - - # set longer connect_timeout (default is 60 seconds) - connection.request(:connect_timeout => 360) - - # set longer read_timeout (default is 60 seconds) - connection.request(:read_timeout => 360) - - # set longer write_timeout (default is 60 seconds) - connection.request(:write_timeout => 360) - - # Enable the socket option TCP_NODELAY on the underlying socket. - # - # This can improve response time when sending frequent short - # requests in time-sensitive scenarios. - # - connection = Excon.new('http://geemus.com/', :tcp_nodelay => true) - -These options can be combined to make pretty much any request you might need. - -Excon can also expect one or more HTTP status code in response, raising an exception if the response does not meet the criteria. - -If you need to accept as response one or more HTTP status codes you can declare them in an array: - - connection.request(:expects => [200, 201], :method => :get, :path => path, :query => {}) +```ruby +require 'rubygems' +require 'excon' +``` + +The easiest way to get started is by using one-off requests. Supported one-off request methods are `connect`, `delete`, `get`, `head`, `options`, `post`, `put`, and `trace`. Requests return a response object which has `body`, `headers`, `remote_ip` and `status` attributes. + +```ruby +response = Excon.get('http://geemus.com') +response.body # => "..." +response.headers # => {...} +response.remote_ip # => "..." +response.status # => 200 +``` + +For API clients or other ongoing usage, reuse a connection across multiple requests to share options and improve performance. + +```ruby +connection = Excon.new('http://geemus.com') +get_response = connection.get +post_response = connection.post(:path => '/foo') +delete_response = connection.delete(:path => '/bar') +``` + +Options +------- + +Both one-off and persistent connections support many other options. The final options for a request are built up by starting with `Excon.defaults`, then merging in options from the connection and finally merging in any request options. In this way you have plenty of options on where and how to set options and can easily setup connections or defaults to match common options for a particular endpoint. + +Here are a few common examples: + +```ruby +# Custom headers +Excon.get('http://geemus.com', :headers => {'Authorization' => 'Basic 0123456789ABCDEF'}) +connection.get(:headers => {'Authorization' => 'Basic 0123456789ABCDEF'}) + +# Changing query strings +connection = Excon.new('http://geemus.com/') +connection.get(:query => {:foo => 'bar'}) + +# POST body encoded with application/x-www-form-urlencoded +Excon.post('http://geemus.com', + :body => 'language=ruby&class=fog', + :headers => { "Content-Type" => "application/x-www-form-urlencoded" }) + +# same again, but using URI to build the body of parameters +Excon.post('http://geemus.com', + :body => URI.encode_www_form(:language => 'ruby', :class => 'fog'), + :headers => { "Content-Type" => "application/x-www-form-urlencoded" }) + +# request takes a method option, accepting either a symbol or string +connection.request(:method => :get) +connection.request(:method => 'GET') + +# expect one or more status codes, or raise an error +connection.request(:expects => [200, 201], :method => :get) + +# this request can be repeated safely, so retry on errors up to 3 times +connection.request(:idempotent => true) + +# this request can be repeated safely, retry up to 6 times +connection.request(:idempotent => true, :retry_limit => 6) + +# opt-out of nonblocking operations for performance and/or as a workaround +connection.request(:nonblock => false) + +# set longer read_timeout (default is 60 seconds) +connection.request(:read_timeout => 360) + +# set longer write_timeout (default is 60 seconds) +connection.request(:write_timeout => 360) + +# Enable the socket option TCP_NODELAY on the underlying socket. +# +# This can improve response time when sending frequent short +# requests in time-sensitive scenarios. +# +connection = Excon.new('http://geemus.com/', :tcp_nodelay => true) + +# opt-in to omitting port from http:80 and https:443 +connection = Excon.new('http://geemus.com/', :omit_default_port => true) + +# set longer connect_timeout (default is 60 seconds) +connection = Excon.new('http://geemus.com/', :connect_timeout => 360) +``` Chunked Requests ---------------- You can make `Transfer-Encoding: chunked` requests by passing a block that will deliver chunks, delivering an empty chunk to signal completion. - file = File.open('data') +```ruby +file = File.open('data') - chunker = lambda do - # Excon.defaults[:chunk_size] defaults to 1048576, ie 1MB - # to_s will convert the nil receieved after everything is read to the final empty chunk - file.read(Excon.defaults[:chunk_size]).to_s - end +chunker = lambda do + # Excon.defaults[:chunk_size] defaults to 1048576, ie 1MB + # to_s will convert the nil received after everything is read to the final empty chunk + file.read(Excon.defaults[:chunk_size]).to_s +end - Excon.post('http://geemus.com', :request_block => chunker) +Excon.post('http://geemus.com', :request_block => chunker) - file.close +file.close +``` Iterating in this way allows you to have more granular control over writes and to write things where you can not calculate the overall length up front. Pipelining Requests ------------------ -You can make use of HTTP pipelining to improve performance. Insead 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. +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. - connection = Excon.new('http://geemus.com/') - connection.requests([{:method => :get}, {:method => :get}]) +```ruby +connection = Excon.new('http://geemus.com/') +connection.requests([{:method => :get}, {:method => :get}]) +``` Streaming Responses ------------------- You can stream responses by passing a block that will receive each chunk. - streamer = lambda do |chunk, remaining_bytes, total_bytes| - puts chunk - puts "Remaining: #{remaining_bytes.to_f / total_bytes}%" - end +```ruby +streamer = lambda do |chunk, remaining_bytes, total_bytes| + puts chunk + puts "Remaining: #{remaining_bytes.to_f / total_bytes}%" +end - Excon.get('http://geemus.com', :response_block => streamer) +Excon.get('http://geemus.com', :response_block => streamer) +``` Iterating over each chunk will allow you to do work on the response incrementally without buffering the entire response first. For very large responses this can lead to significant memory savings. @@ -144,108 +158,128 @@ You can specify a proxy URL that Excon will use with both HTTP and HTTPS connections: - connection = Excon.new('http://geemus.com', :proxy => 'http://my.proxy:3128') - connection.request(:method => 'GET') +```ruby +connection = Excon.new('http://geemus.com', :proxy => 'http://my.proxy:3128') +connection.request(:method => 'GET') + +Excon.get('http://geemus.com', :proxy => 'http://my.proxy:3128') +``` The proxy URL must be fully specified, including scheme (e.g. "http://") and port. -Proxy support must be set when establishing a connection object and cannot be overridden in individual requests. Because of this it is unavailable in one-off requests (Excon.get, etc.) +Proxy support must be set when establishing a connection object and cannot be overridden in individual requests. NOTE: Excon will use the environment variables `http_proxy` and `https_proxy` if they are present. If these variables are set they will take precedence over a :proxy option specified in code. If "https_proxy" is not set, the value of "http_proxy" will be used for both HTTP and HTTPS connections. -Stubs ------ - -You can stub out requests for testing purposes by enabling mock mode on a connection. - - connection = Excon.new('http://example.com', :mock => true) - -Or by enabling mock mode for a request. - - connection.request(:method => :get, :path => 'example', :mock => true) - -Then you can add stubs, for instance: - - # Excon.stub(request_attributes, response_attributes) - Excon.stub({:method => :get}, {:body => 'body', :status => 200}) - -Omitted attributes are assumed to match, so this stub will match any get request and return an Excon::Response with a body of 'body' and status of 200. You can add whatever stubs you might like this way and they will be checked against in the order they were added, if none of them match then excon will raise an error to let you know. - -Alternatively you can pass a block instead of `response_attributes` and it will be called with the request params. For example, you could create a stub that echoes the body given to it like this: - - # Excon.stub(request_attributes, &response_block) - Excon.stub({:method => :put}) do |params| - {:body => params[:body], :status => 200} - end +Unix Socket Support +------------------ -In order to clear all previously defined stubs you can use: +The Unix socket will work for one-off requests and multiuse connections. A Unix socket path must be provided separate from the resource path. - Excon.stubs.clear +```ruby +connection = Excon.new('unix:///', :socket => '/tmp/unicorn.sock') +connection.request(:method => :get, :path => '/ping') -Or to simply remove the last defined stub you can use: +Excon.get('unix:///ping', :socket => '/tmp/unicorn.sock') +``` - Excon.stubs.shift +NOTE: Proxies will be ignored when using a Unix socket, since a Unix socket has to be local. -For example, if using RSpec for your test suite you can clear stubs after running each example: +Stubs +----- - config.after(:each) do - Excon.stubs.clear - end +You can stub out requests for testing purposes by enabling mock mode on a connection. -You can also modify 'Excon.defaults` to set a default for all requests, so for a test suite you might do this: +```ruby +connection = Excon.new('http://example.com', :mock => true) +``` - before(:all) { Excon.defaults[:mock] = true } +Or by enabling mock mode for a request. -For additional information on stubbing, read the pull request notes [here](https://github.com/geemus/excon/issues/29) +```ruby +connection.request(:method => :get, :path => 'example', :mock => true) +``` -HTTPS/SSL Issues ----------------- +Add stubs by providing the request_attributes to match and response attributes to return. Response params can be specified as either a hash or block which will yield with response_params. -By default excon will try to verify peer certificates when using SSL for HTTPS. Unfortunately on some operating systems the defaults will not work. This will likely manifest itself as something like `Excon::Errors::SocketError: SSL_connect returned=1 ...` +```ruby +Excon.stub({}, {:body => 'body', :status => 200}) +Excon.stub({}, lambda {|request_params| :body => request_params[:body], :status => 200}) +``` -If you have the misfortune of running into this problem you have a couple options. If you have certificates but they aren't being auto-discovered, you can specify the path to your certificates: +Omitted attributes are assumed to match, so this stub will match *any* request and return an Excon::Response with a body of 'body' and status of 200. You can add whatever stubs you might like this way and they will be checked against in the order they were added, if none of them match then excon will raise an `Excon::Errors::StubNotFound` error to let you know. - Excon.defaults[:ssl_ca_path] = '/path/to/certs' +To remove a previously defined stub, or all stubs: -Failing that, you can turn off peer verification (less secure): +```ruby +Excon.unstub({}) # remove first/oldest stub matching {} +Excon.stubs.clear # remove all stubs +``` - Excon.defaults[:ssl_verify_peer] = false +For example, if using RSpec for your test suite you can clear stubs after running each example: -Either of these should allow you to work around the socket error and continue with your work. +```ruby +config.after(:each) do + Excon.stubs.clear +end +``` + +You can also modify 'Excon.defaults` to set a stub for all requests, so for a test suite you might do this: + +```ruby +# Mock by default and stub any request as success +config.before(:all) do + Excon.defaults[:mock] = true + Excon.stub({}, {:body => 'Fallback', :status => 200}) + # Add your own stubs here or in specific tests... +end +``` Instrumentation --------------- Excon calls can be timed using the [ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) API. - connection = Excon.new('http://geemus.com', - :instrumentor => ActiveSupport::Notifications) +```ruby +connection = Excon.new( + 'http://geemus.com', + :instrumentor => ActiveSupport::Notifications +) +``` Excon will then instrument each request, retry, and error. The corresponding events are named excon.request, excon.retry, and excon.error respectively. - ActiveSupport::Notifications.subscribe(/excon/) do |*args| - puts "Excon did stuff!" - end +```ruby +ActiveSupport::Notifications.subscribe(/excon/) do |*args| + puts "Excon did stuff!" +end +``` If you prefer to label each event with something other than "excon," you may specify an alternate name in the constructor: - connection = Excon.new('http://geemus.com', - :instrumentor => ActiveSupport::Notifications, - :instrumentor_name => 'my_app') +```ruby +connection = Excon.new( + 'http://geemus.com', + :instrumentor => ActiveSupport::Notifications, + :instrumentor_name => 'my_app' +) +``` If you don't want to add activesupport to your application, simply define a class which implements the same #instrument method like so: - class SimpleInstrumentor - class << self - attr_accessor :events - - def instrument(name, params = {}, &block) - puts "#{name} just happened." - yield if block_given? - end - end +```ruby +class SimpleInstrumentor + class << self + attr_accessor :events + + def instrument(name, params = {}, &block) + puts "#{name} just happened." + yield if block_given? end + end +end +``` The #instrument method will be called for each HTTP request, response, retry, and error. @@ -253,12 +287,31 @@ See [the documentation for ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html) for more detail on using the subscription interface. See excon's instrumentation_test.rb for more examples of instrumenting excon. +HTTPS/SSL Issues +---------------- + +By default excon will try to verify peer certificates when using SSL for HTTPS. Unfortunately on some operating systems the defaults will not work. This will likely manifest itself as something like `Excon::Errors::SocketError: SSL_connect returned=1 ...` + +If you have the misfortune of running into this problem you have a couple options. If you have certificates but they aren't being auto-discovered, you can specify the path to your certificates: + +```ruby +Excon.defaults[:ssl_ca_path] = '/path/to/certs' +``` + +Failing that, you can turn off peer verification (less secure): + +```ruby +Excon.defaults[:ssl_verify_peer] = false +``` + +Either of these should allow you to work around the socket error and continue with your work. + Copyright --------- (The MIT License) -Copyright (c) 2010-2013 {geemus (Wesley Beary)}[http://github.com/geemus] +Copyright (c) 2010-2013 [geemus (Wesley Beary)](http://github.com/geemus) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the diff -Nru ruby-excon-0.25.1/changelog.txt ruby-excon-0.28.0/changelog.txt --- ruby-excon-0.25.1/changelog.txt 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/changelog.txt 2013-11-12 16:08:34.000000000 +0000 @@ -1,3 +1,85 @@ +0.28.0 10/28/2013 +================= + +tag warning messages with [excon] +allow specific ssl_versions +fixes around param validation +create a new connection for redirect_follower middleware +add connection_uri/request_uri to utils +avoid mutating connection data +remove connection key in redirect_follower + + +0.27.6 10/15/2013 +================= + +warn, but no longer mutate params during validation + +0.27.5 10/14/2013 +================= + +extract validations/port_string to utils module + +0.27.4 10/14/2013 +================= + +fix for https/port string values + +0.27.3 10/11/2013 +================= + +better invalid key handling +avoid mutating port value + +0.27.2 10/10/2013 +================= + +avoid mutating datum in idempotent middleware + +0.27.1 10/09/2013 +================= + +improve warning messages for valid keys + +0.27.0 10/04/2013 +================= + +display warnings based on ruby and/or debug settings +add missing valid connection keys +remove 1.8.7 related nonblock warning +add support for unix sockets +cleanup constants +improve test setup to minimize server spawning +separate connection/request key validation + +0.26.0 09/24/2013 +================= + +add basic decompress middleware +update readme mocking+stubbing info +add unstub functionality +avoid modifying original options in request +jruby fixes +misc cleanup/fixes +encoding/compatibility fixes +close sockets on error +warn when both request_block and idempotent are set + +0.25.3 07/18/2013 +================= + +respect SSL_CERT_DIR/SSL_CERT_FILE +more aggressively include bundled cert as fallback + +0.25.2 07/18/2013 +================= + +add license to gemspec +add "excon/#{version}" default user agent +create/use response parser middleware +fix proxy request info to use datum rather than @data + + 0.25.1 07/01/2013 ================= diff -Nru ruby-excon-0.25.1/debian/changelog ruby-excon-0.28.0/debian/changelog --- ruby-excon-0.25.1/debian/changelog 2013-07-06 21:48:45.000000000 +0000 +++ ruby-excon-0.28.0/debian/changelog 2013-11-19 10:28:18.000000000 +0000 @@ -1,3 +1,15 @@ +ruby-excon (0.28.0-1~ubuntu14.04.1~ppa1) trusty; urgency=low + + * No-change backport to trusty + + -- Neil Wilson Tue, 19 Nov 2013 10:28:18 +0000 + +ruby-excon (0.28.0-1) UNRELEASED; urgency=low + + * New upstream release + + -- Neil Wilson Tue, 12 Nov 2013 16:11:06 +0000 + ruby-excon (0.25.1-1) unstable; urgency=low * New upstream release diff -Nru ruby-excon-0.25.1/debian/patches/01_use_ca-certificates.patch ruby-excon-0.28.0/debian/patches/01_use_ca-certificates.patch --- ruby-excon-0.25.1/debian/patches/01_use_ca-certificates.patch 2013-07-06 20:40:10.000000000 +0000 +++ ruby-excon-0.28.0/debian/patches/01_use_ca-certificates.patch 2013-11-12 16:11:35.000000000 +0000 @@ -3,9 +3,9 @@ Forwarded: not-needed Last-Updated: 2013-05-16 ---- a/lib/excon/constants.rb -+++ b/lib/excon/constants.rb -@@ -2,7 +2,7 @@ +--- ruby-excon-0.28.0.orig/lib/excon/constants.rb ++++ ruby-excon-0.28.0/lib/excon/constants.rb +@@ -4,7 +4,7 @@ CR_NL = "\r\n" diff -Nru ruby-excon-0.25.1/debian/patches/03_remove_rubygems_bundler_add_requires_from_gemspec.patch ruby-excon-0.28.0/debian/patches/03_remove_rubygems_bundler_add_requires_from_gemspec.patch --- ruby-excon-0.25.1/debian/patches/03_remove_rubygems_bundler_add_requires_from_gemspec.patch 2013-07-06 20:40:10.000000000 +0000 +++ ruby-excon-0.28.0/debian/patches/03_remove_rubygems_bundler_add_requires_from_gemspec.patch 2013-11-12 16:19:05.000000000 +0000 @@ -4,9 +4,9 @@ Author: Praveen Arimbrathodiyil Last-Update: 2013-05-20 ---- a/tests/test_helper.rb -+++ b/tests/test_helper.rb -@@ -1,10 +1,22 @@ +--- ruby-excon-0.28.0.orig/tests/test_helper.rb ++++ ruby-excon-0.28.0/tests/test_helper.rb +@@ -1,7 +1,18 @@ -require 'rubygems' if RUBY_VERSION < '1.9' -require 'bundler' +#require 'rubygems' if RUBY_VERSION < '1.9' @@ -14,11 +14,8 @@ -Bundler.require(:default, :development) +#Bundler.require(:default, :development) - - require 'stringio' - -+# from gemspec -+require 'active_support' ++ ++require 'active_support' +require 'delorean' +require 'eventmachine' +require 'open4' @@ -28,7 +25,6 @@ +require 'sinatra' + +require 'excon' -+ + def basic_tests(url = 'http://127.0.0.1:9292', options = {}) - [false, true].each do |nonblock| - options = options.merge({:ssl_verify_peer => false, :nonblock => nonblock }) + reset_connection = !!options.delete(:reset_connection) diff -Nru ruby-excon-0.25.1/excon.gemspec ruby-excon-0.28.0/excon.gemspec --- ruby-excon-0.25.1/excon.gemspec 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/excon.gemspec 2013-11-12 16:08:34.000000000 +0000 @@ -13,8 +13,8 @@ ## If your rubyforge_project name is different, then edit it and comment out ## the sub! line in the Rakefile s.name = 'excon' - s.version = '0.25.1' - s.date = '2013-07-02' + s.version = '0.28.0' + s.date = '2013-10-28' s.rubyforge_project = 'excon' ## Make sure your summary is short. The description may be as long @@ -28,6 +28,7 @@ s.authors = ["dpiddy (Dan Peterson)", "geemus (Wesley Beary)", "nextmat (Matt Sanders)"] s.email = 'geemus@gmail.com' s.homepage = 'https://github.com/geemus/excon' + s.license = 'MIT' ## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as ## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb' @@ -97,14 +98,19 @@ lib/excon/constants.rb lib/excon/errors.rb lib/excon/middlewares/base.rb + lib/excon/middlewares/decompress.rb lib/excon/middlewares/expects.rb lib/excon/middlewares/idempotent.rb lib/excon/middlewares/instrumentor.rb lib/excon/middlewares/mock.rb + lib/excon/middlewares/redirect_follower.rb + lib/excon/middlewares/response_parser.rb lib/excon/response.rb lib/excon/socket.rb lib/excon/ssl_socket.rb lib/excon/standard_instrumentor.rb + lib/excon/unix_socket.rb + lib/excon/utils.rb tests/authorization_header_tests.rb tests/bad_tests.rb tests/basic_tests.rb @@ -113,14 +119,17 @@ tests/data/xs tests/errors_tests.rb tests/header_tests.rb + tests/middlewares/decompress_tests.rb tests/middlewares/idempotent_tests.rb tests/middlewares/instrumentation_tests.rb tests/middlewares/mock_tests.rb + tests/middlewares/redirect_follower_tests.rb tests/proxy_tests.rb tests/query_string_tests.rb tests/rackups/basic.rb tests/rackups/basic.ru tests/rackups/basic_auth.ru + tests/rackups/deflater.ru tests/rackups/proxy.ru tests/rackups/query_string.ru tests/rackups/request_headers.ru @@ -139,6 +148,7 @@ tests/test_helper.rb tests/thread_safety_tests.rb tests/timeout_tests.rb + tests/utils_tests.rb ] # = MANIFEST = diff -Nru ruby-excon-0.25.1/lib/excon/connection.rb ruby-excon-0.28.0/lib/excon/connection.rb --- ruby-excon-0.25.1/lib/excon/connection.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/lib/excon/connection.rb 2013-11-12 16:08:34.000000000 +0000 @@ -1,5 +1,6 @@ module Excon class Connection + include Utils attr_reader :data @@ -8,16 +9,16 @@ @data end def connection=(new_params) - Excon.display_warning("Excon::Connection#connection= is deprecated use Excon::Connection#data= instead (#{caller.first})") + Excon.display_warning("Excon::Connection#connection= is deprecated. Use of this method may cause unexpected results. (#{caller.first})") @data = new_params end def params - display_waring("Excon::Connection#params is deprecated use Excon::Connection#data instead (#{caller.first})") + Excon.display_warning("Excon::Connection#params is deprecated use Excon::Connection#data instead (#{caller.first})") @data end def params=(new_params) - Excon.display_warning("Excon::Connection#params= is deprecated use Excon::Connection#data= instead (#{caller.first})") + Excon.display_warning("Excon::Connection#params= is deprecated. Use of this method may cause unexpected results. (#{caller.first})") @data = new_params end @@ -26,7 +27,7 @@ @data[:proxy] end def proxy=(new_proxy) - Excon.display_warning("Excon::Connection#proxy= is deprecated use Excon::Connection#data[:proxy]= instead (#{caller.first})") if !ENV['VERBOSE'].nil? + Excon.display_warning("Excon::Connection#proxy= is deprecated. Use of this method may cause unexpected results. (#{caller.first})") @data[:proxy] = new_proxy end @@ -39,12 +40,13 @@ # @option params [Fixnum] :port The port on which to connect, to the destination host # @option params [Hash] :query Default query; appended to the 'scheme://host:port/path/' in the form of '?key=value'. Will only be used if params[:query] is not supplied to Connection#request # @option params [String] :scheme The protocol; 'https' causes OpenSSL to be used + # @option params [String] :socket The path to the unix socket (required for 'unix://' connections) + # @option params [String] :ciphers Only use the specified SSL/TLS cipher suites; use OpenSSL cipher spec format e.g. 'HIGH:!aNULL:!3DES' or 'AES256-SHA:DES-CBC3-SHA' # @option params [String] :proxy Proxy server; e.g. 'http://myproxy.com:8888' # @option params [Fixnum] :retry_limit Set how many times we'll retry a failed request. (Default 4) # @option params [Class] :instrumentor Responds to #instrument as in ActiveSupport::Notifications # @option params [String] :instrumentor_name Name prefix for #instrument events. Defaults to 'excon' def initialize(params = {}) - invalid_keys_warning(params, Excon::VALID_CONNECTION_KEYS) @data = Excon.defaults.dup # merge does not deep-dup, so make sure headers is not the original @data[:headers] = @data[:headers].dup @@ -52,17 +54,20 @@ # the same goes for :middlewares @data[:middlewares] = @data[:middlewares].dup + params = validate_params(:connection, params) @data.merge!(params) - no_proxy_env = ENV["no_proxy"] || ENV["NO_PROXY"] || "" - no_proxy_list = no_proxy_env.scan(/\*?\.?([^\s,:]+)(?::(\d+))?/i).map { |s| [s[0], s[1]] } - unless no_proxy_list.index { |h| /(^|\.)#{h[0]}$/.match(@data[:host]) && (h[1].nil? || h[1].to_i == @data[:port]) } - if @data[:scheme] == HTTPS && (ENV.has_key?('https_proxy') || ENV.has_key?('HTTPS_PROXY')) - @data[:proxy] = setup_proxy(ENV['https_proxy'] || ENV['HTTPS_PROXY']) - elsif (ENV.has_key?('http_proxy') || ENV.has_key?('HTTP_PROXY')) - @data[:proxy] = setup_proxy(ENV['http_proxy'] || ENV['HTTP_PROXY']) - elsif @data.has_key?(:proxy) - @data[:proxy] = setup_proxy(@data[:proxy]) + unless @data[:scheme] == UNIX + no_proxy_env = ENV["no_proxy"] || ENV["NO_PROXY"] || "" + no_proxy_list = no_proxy_env.scan(/\*?\.?([^\s,:]+)(?::(\d+))?/i).map { |s| [s[0], s[1]] } + unless no_proxy_list.index { |h| /(^|\.)#{h[0]}$/.match(@data[:host]) && (h[1].nil? || h[1].to_i == @data[:port]) } + if @data[:scheme] == HTTPS && (ENV.has_key?('https_proxy') || ENV.has_key?('HTTPS_PROXY')) + @data[:proxy] = setup_proxy(ENV['https_proxy'] || ENV['HTTPS_PROXY']) + elsif (ENV.has_key?('http_proxy') || ENV.has_key?('HTTP_PROXY')) + @data[:proxy] = setup_proxy(ENV['http_proxy'] || ENV['HTTP_PROXY']) + elsif @data.has_key?(:proxy) + @data[:proxy] = setup_proxy(@data[:proxy]) + end end end @@ -86,7 +91,19 @@ @data[:headers]['Authorization'] ||= 'Basic ' << ['' << user.to_s << ':' << pass.to_s].pack('m').delete(Excon::CR_NL) end - @socket_key = '' << @data[:scheme] << '://' << @data[:host] << ':' << @data[:port].to_s + @socket_key = '' << @data[:scheme] + if @data[:scheme] == UNIX + if @data[:host] + raise ArgumentError, "The `:host` parameter should not be set for `unix://` connections.\n" + + "When supplying a `unix://` URI, it should start with `unix:/` or `unix:///`." + elsif !@data[:socket] + raise ArgumentError, 'You must provide a `:socket` for `unix://` connections' + else + @socket_key << '://' << @data[:socket] + end + else + @socket_key << '://' << @data[:host] << port_string(@data) + end reset end @@ -105,38 +122,29 @@ socket.data = datum # start with "METHOD /path" request = datum[:method].to_s.upcase << ' ' - if @data[:proxy] - request << datum[:scheme] << '://' << @data[:host] << port_string(@data) + if datum[:proxy] + request << datum[:scheme] << '://' << datum[:host] << port_string(datum) end request << datum[:path] # add query to path, if there is one - case datum[:query] - when String - request << '?' << datum[:query] - when Hash - request << '?' - datum[:query].each do |key, values| - if values.nil? - request << key.to_s << '&' - else - [values].flatten.each do |value| - request << key.to_s << '=' << CGI.escape(value.to_s) << '&' - end - end - end - request.chop! # remove trailing '&' - end + request << query_string(datum) # finish first line with "HTTP/1.1\r\n" request << HTTP_1_1 if datum.has_key?(:request_block) datum[:headers]['Transfer-Encoding'] = 'chunked' - elsif ! (datum[:method].to_s.casecmp('GET') == 0 && datum[:body].nil?) + else + body = datum[:body].is_a?(String) ? StringIO.new(datum[:body]) : datum[:body] + # The HTTP spec isn't clear on it, but specifically, GET requests don't usually send bodies; # if they don't, sending Content-Length:0 can cause issues. - datum[:headers]['Content-Length'] = detect_content_length(datum[:body]) + unless datum[:method].to_s.casecmp('GET') == 0 && body.nil? + unless datum[:headers].has_key?('Content-Length') + datum[:headers]['Content-Length'] = detect_content_length(body) + end + end end # add headers to request @@ -148,9 +156,9 @@ # add additional "\r\n" to indicate end of headers request << CR_NL + socket.write(request) # write out request + headers if datum.has_key?(:request_block) - socket.write(request) # write out request + headers while true # write out body with chunked encoding chunk = datum[:request_block].call if FORCE_ENC @@ -163,23 +171,16 @@ break end end - elsif !datum[:body].nil? - if datum[:body].is_a?(String) # write out string body - socket.write(request << datum[:body]) # write out request + headers + body - else # write out file body - socket.write(request) # write out request + headers - if datum[:body].respond_to?(:binmode) - datum[:body].binmode - end - if datum[:body].respond_to?(:pos=) - datum[:body].pos = 0 - end - while chunk = datum[:body].read(datum[:chunk_size]) - socket.write(chunk) - end + elsif !body.nil? # write out body + if body.respond_to?(:binmode) + body.binmode + end + if body.respond_to?(:pos=) + body.pos = 0 + end + while chunk = body.read(datum[:chunk_size]) + socket.write(chunk) end - else # write out nil body - socket.write(request) # write out request + headers end end rescue => error @@ -210,23 +211,24 @@ # @param [Hash] params One or more optional params, override defaults set in Connection.new # @option params [String] :body text to be sent over a socket # @option params [Hash] :headers The default headers to supply in a request - # @option params [String] :host The destination host's reachable DNS name or IP, in the form of a String # @option params [String] :path appears after 'scheme://host:port/' - # @option params [Fixnum] :port The port on which to connect, to the destination host # @option params [Hash] :query appended to the 'scheme://host:port/path/' in the form of '?key=value' - # @option params [String] :scheme The protocol; 'https' causes OpenSSL to be used - def request(params, &block) + def request(params={}, &block) + params = validate_params(:request, params) # @data has defaults, merge in new params to override datum = @data.merge(params) - invalid_keys_warning(params, VALID_CONNECTION_KEYS) datum[:headers] = @data[:headers].merge(datum[:headers] || {}) - datum[:headers]['Host'] ||= '' << datum[:host] << port_string(datum) + if datum[:scheme] == UNIX + datum[:headers]['Host'] ||= '' << datum[:socket] + else + datum[:headers]['Host'] ||= '' << datum[:host] << port_string(datum) + end datum[:retries_remaining] ||= datum[:retry_limit] # if path is empty or doesn't start with '/', insert one unless datum[:path][0, 1] == '/' - datum[:path].insert(0, '/') + datum[:path] = datum[:path].dup.insert(0, '/') end if block_given? @@ -234,6 +236,11 @@ datum[:response_block] = Proc.new end + if datum[:request_block] && datum[:idempotent] + Excon.display_warning("Excon requests with a :request_block can not be :idempotent (#{caller.first})") + datum[:idempotent] = false + end + datum[:connection] = self datum[:stack] = datum[:middlewares].map do |middleware| @@ -255,6 +262,7 @@ datum end rescue => error + reset datum[:error] = error if datum[:stack] datum[:stack].error_call(datum) @@ -292,7 +300,7 @@ end def retry_limit - Excon.display_warning("Excon::Connection#retry_limit is deprecated, pass :retry_limit to the initializer (#{caller.first})") + Excon.display_warning("Excon::Connection#retry_limit is deprecated, use Excon::Connection#data[:retry_limit]. (#{caller.first})") @data[:retry_limit] ||= DEFAULT_RETRY_LIMIT end @@ -321,35 +329,35 @@ private def detect_content_length(body) - if body.is_a?(String) - if FORCE_ENC - body.force_encoding('BINARY') - end - body.length - elsif body.respond_to?(:size) - # IO object: File, Tempfile, etc. + if body.respond_to?(:size) + # IO object: File, Tempfile, StringIO, etc. body.size + elsif body.respond_to?(:stat) + # for 1.8.7 where file does not have size + body.stat.size else - begin - File.size(body) # for 1.8.7 where file does not have size - rescue - 0 - end + 0 end end - def invalid_keys_warning(argument, valid_keys) - invalid_keys = argument.keys - valid_keys + def validate_params(validation, params) + valid_keys = case validation + when :connection + valid_connection_keys(params) + when :request + valid_request_keys(params) + end + invalid_keys = params.keys - valid_keys unless invalid_keys.empty? - Excon.display_warning("The following keys are invalid: #{invalid_keys.map(&:inspect).join(', ')}") + Excon.display_warning("Invalid Excon #{validation} keys: #{invalid_keys.map(&:inspect).join(', ')}\n#{ caller.join("\n") }") + # FIXME: for now, just warn, don't mutate, give things (ie fog) a chance to catch up + #params = params.dup + #invalid_keys.each {|key| params.delete(key) } end + params end def response(datum={}) - unless datum.has_key?(:response) - datum = Excon::Response.parse(socket, datum) - end - datum[:stack].response_call(datum) rescue => error case error @@ -363,6 +371,8 @@ def socket sockets[@socket_key] ||= if @data[:scheme] == HTTPS Excon::SSLSocket.new(@data) + elsif @data[:scheme] == UNIX + Excon::UnixSocket.new(@data) else Excon::Socket.new(@data) end @@ -382,7 +392,7 @@ { :host => uri.host, :password => uri.password, - :port => uri.port.to_s, + :port => uri.port, :scheme => uri.scheme, :user => uri.user } @@ -390,13 +400,5 @@ proxy end end - - def port_string(datum) - if datum[:omit_default_port] && ((datum[:scheme].casecmp('http') == 0 && datum[:port].to_i == 80) || (datum[:scheme].casecmp('https') == 0 && datum[:port].to_i == 443)) - '' - else - ':' << datum[:port].to_s - end - end end end diff -Nru ruby-excon-0.25.1/lib/excon/constants.rb ruby-excon-0.28.0/lib/excon/constants.rb --- ruby-excon-0.25.1/lib/excon/constants.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/lib/excon/constants.rb 2013-11-12 16:08:34.000000000 +0000 @@ -1,5 +1,7 @@ module Excon + VERSION = '0.28.0' + CR_NL = "\r\n" DEFAULT_CA_FILE = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "data", "cacert.pem")) @@ -29,24 +31,18 @@ REDACTED = 'REDACTED' - VALID_CONNECTION_KEYS = [ + UNIX = 'unix' + + USER_AGENT = 'excon/' << VERSION + + VALID_REQUEST_KEYS = [ :body, :captures, :chunk_size, - :client_key, - :client_cert, - :certificate, - :private_key, - :connect_timeout, - :connection, :debug_request, :debug_response, - :error, - :exception, :expects, - :family, :headers, - :host, :idempotent, :instrumentor, :instrumentor_name, @@ -54,31 +50,42 @@ :middlewares, :mock, :nonblock, - :omit_default_port, - :password, :path, :pipeline, - :port, - :proxy, :query, :read_timeout, :request_block, - :response, :response_block, - :retries_remaining, + :retries_remaining, # used internally :retry_limit, + :write_timeout + ] + + VALID_CONNECTION_KEYS = VALID_REQUEST_KEYS + [ + :ciphers, + :client_key, + :client_cert, + :certificate, + :certificate_path, + :private_key, + :private_key_path, + :connect_timeout, + :family, + :host, + :omit_default_port, + :password, + :port, + :proxy, :scheme, - :tcp_nodelay, - :uri_parser, - :user, + :socket, :ssl_ca_file, :ssl_verify_peer, - :stack, - :write_timeout + :ssl_version, + :tcp_nodelay, + :uri_parser, + :user ] - VERSION = '0.25.1' - unless ::IO.const_defined?(:WaitReadable) class ::IO module WaitReadable; end diff -Nru ruby-excon-0.25.1/lib/excon/errors.rb ruby-excon-0.28.0/lib/excon/errors.rb --- ruby-excon-0.25.1/lib/excon/errors.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/lib/excon/errors.rb 2013-11-12 16:08:34.000000000 +0000 @@ -9,7 +9,7 @@ def initialize(socket_error=nil) if socket_error.message =~ /certificate verify failed/ - super('Unable to verify certificate, please set `Excon.defaults[:ssl_ca_path] = path_to_certs`, `Excon.defaults[:ssl_ca_file] = path_to_file`, or `Excon.defaults[:ssl_verify_peer] = false` (less secure).') + super("Unable to verify certificate, please set `Excon.defaults[:ssl_ca_path] = path_to_certs`, `ENV['SSL_CERT_DIR'] = path_to_certs`, `Excon.defaults[:ssl_ca_file] = path_to_file`, `ENV['SSL_CERT_FILE'] = path_to_file` or `Excon.defaults[:ssl_verify_peer] = false` (less secure).") else super("#{socket_error.message} (#{socket_error.class})") end diff -Nru ruby-excon-0.25.1/lib/excon/middlewares/decompress.rb ruby-excon-0.28.0/lib/excon/middlewares/decompress.rb --- ruby-excon-0.25.1/lib/excon/middlewares/decompress.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby-excon-0.28.0/lib/excon/middlewares/decompress.rb 2013-11-12 16:08:34.000000000 +0000 @@ -0,0 +1,18 @@ +module Excon + module Middleware + class Decompress < Excon::Middleware::Base + def response_call(datum) + unless datum.has_key?(:response_block) + case datum[:response][:headers]['Content-Encoding'] + when 'deflate' + # assume inflate omits header + datum[:response][:body] = Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(datum[:response][:body]) + when 'gzip' + datum[:response][:body] = Zlib::GzipReader.new(StringIO.new(datum[:response][:body])).read + end + end + @stack.response_call(datum) + end + end + end +end diff -Nru ruby-excon-0.25.1/lib/excon/middlewares/idempotent.rb ruby-excon-0.28.0/lib/excon/middlewares/idempotent.rb --- ruby-excon-0.25.1/lib/excon/middlewares/idempotent.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/lib/excon/middlewares/idempotent.rb 2013-11-12 16:08:34.000000000 +0000 @@ -6,10 +6,10 @@ Excon::Errors::HTTPStatusError].any? {|ex| datum[:error].kind_of?(ex) } && datum[:retries_remaining] > 1 # reduces remaining retries, reset connection, and restart request_call datum[:retries_remaining] -= 1 - datum[:connection].reset - datum.delete(:response) - datum.delete(:error) - datum[:connection].request(datum) + connection = datum.delete(:connection) + request_keys = Utils.valid_request_keys(datum) + datum.reject! {|key, _| !request_keys.include?(key) } + connection.request(datum) else @stack.error_call(datum) end diff -Nru ruby-excon-0.25.1/lib/excon/middlewares/mock.rb ruby-excon-0.28.0/lib/excon/middlewares/mock.rb --- ruby-excon-0.25.1/lib/excon/middlewares/mock.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/lib/excon/middlewares/mock.rb 2013-11-12 16:08:34.000000000 +0000 @@ -14,7 +14,7 @@ datum[:body] = datum[:body].read end - if response = Excon.stub_for(datum) + if stub = Excon.stub_for(datum) datum[:response] = { :body => '', :headers => {}, @@ -22,11 +22,11 @@ :remote_ip => '127.0.0.1' } - stub_datum = case response + stub_datum = case stub.last when Proc - response.call(datum) + stub.last.call(datum) else - response + stub.last end datum[:response].merge!(stub_datum.reject {|key,value| key == :headers}) diff -Nru ruby-excon-0.25.1/lib/excon/middlewares/redirect_follower.rb ruby-excon-0.28.0/lib/excon/middlewares/redirect_follower.rb --- ruby-excon-0.25.1/lib/excon/middlewares/redirect_follower.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby-excon-0.28.0/lib/excon/middlewares/redirect_follower.rb 2013-11-12 16:08:34.000000000 +0000 @@ -0,0 +1,45 @@ +module Excon + module Middleware + class RedirectFollower < Excon::Middleware::Base + def response_call(datum) + if datum.has_key?(:response) && [:get, :head].include?(datum[:method].to_s.downcase.to_sym) + case datum[:response][:status] + when 301, 302, 303, 307 + uri_parser = datum[:uri_parser] || Excon.defaults[:uri_parser] + _, location = datum[:response][:headers].detect do |key, value| + key.casecmp('Location') == 0 + end + uri = uri_parser.parse(location) + + # delete old/redirect response + datum.delete(:response) + + params = datum.dup + params.delete(:stack) + params.delete(:connection) + params[:headers] = datum[:headers].dup + params[:headers].delete('Authorization') + params[:headers].delete('Proxy-Connection') + params[:headers].delete('Proxy-Authorization') + params.merge!( + :scheme => uri.scheme, + :host => uri.host, + :port => uri.port, + :path => uri.path, + :query => uri.query, + :user => (URI.decode(uri.user) if uri.user), + :password => (URI.decode(uri.password) if uri.password) + ) + + response = Excon::Connection.new(params).request + datum.merge!({:response => response.data}) + else + @stack.response_call(datum) + end + else + @stack.response_call(datum) + end + end + end + end +end diff -Nru ruby-excon-0.25.1/lib/excon/middlewares/response_parser.rb ruby-excon-0.28.0/lib/excon/middlewares/response_parser.rb --- ruby-excon-0.25.1/lib/excon/middlewares/response_parser.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby-excon-0.28.0/lib/excon/middlewares/response_parser.rb 2013-11-12 16:08:34.000000000 +0000 @@ -0,0 +1,12 @@ +module Excon + module Middleware + class ResponseParser < Excon::Middleware::Base + def response_call(datum) + unless datum.has_key?(:response) + datum = Excon::Response.parse(datum[:connection].send(:socket), datum) + end + @stack.response_call(datum) + end + end + end +end diff -Nru ruby-excon-0.25.1/lib/excon/socket.rb ruby-excon-0.28.0/lib/excon/socket.rb --- ruby-excon-0.25.1/lib/excon/socket.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/lib/excon/socket.rb 2013-11-12 16:08:34.000000000 +0000 @@ -1,5 +1,6 @@ module Excon class Socket + include Utils extend Forwardable @@ -24,11 +25,6 @@ @read_buffer = '' @eof = false - @data[:family] ||= ::Socket::Constants::AF_UNSPEC - if @data[:proxy] - @data[:proxy][:family] ||= ::Socket::Constants::AF_UNSPEC - end - connect end @@ -84,47 +80,35 @@ def write(data) if @data[:nonblock] - # We normally return from the return in the else block below, but - # we guard that data is still something in case we get weird - # values and String#[] returns nil. (This behavior has been observed - # in the wild, so this is a simple defensive mechanism) - while data + if FORCE_ENC + data.force_encoding('BINARY') + end + while true + written = nil begin # I wish that this API accepted a start position, then we wouldn't # have to slice data when there is a short write. written = @socket.write_nonblock(data) - rescue OpenSSL::SSL::SSLError => error - if error.message == 'write would block' + rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => error + if error.is_a?(OpenSSL::SSL::SSLError) && error.message != 'write would block' + raise error + else if IO.select(nil, [@socket], nil, @data[:write_timeout]) retry else - raise(Excon::Errors::Timeout.new("write timeout reached")) + raise Excon::Errors::Timeout.new('write timeout reached') end - else - raise(error) end - rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable - if IO.select(nil, [@socket], nil, @data[:write_timeout]) - retry - else - raise(Excon::Errors::Timeout.new("write timeout reached")) - end - else - # Fast, common case. - # The >= seems weird, why would it have written MORE than we - # requested. But we're getting some weird behavior when @socket - # is an OpenSSL socket, where it seems like it's saying it wrote - # more (perhaps due to SSL packet overhead?). - # - # Pretty weird, but this is a simple defensive mechanism. - return if written >= data.size - - # This takes advantage of the fact that most ruby implementations - # have Copy-On-Write strings. Thusly why requesting a subrange - # of data, we actually don't copy data because the new string - # simply references a subrange of the original. - data = data[written, data.size] end + + # Fast, common case. + break if written == data.size + + # This takes advantage of the fact that most ruby implementations + # have Copy-On-Write strings. Thusly why requesting a subrange + # of data, we actually don't copy data because the new string + # simply references a subrange of the original. + data = data[written, data.size] end else begin @@ -144,9 +128,11 @@ exception = nil addrinfo = if @data[:proxy] - ::Socket.getaddrinfo(@data[:proxy][:host], @data[:proxy][:port], @data[:proxy][:family], ::Socket::Constants::SOCK_STREAM) + family = @data[:proxy][:family] || ::Socket::Constants::AF_UNSPEC + ::Socket.getaddrinfo(@data[:proxy][:host], @data[:proxy][:port], family, ::Socket::Constants::SOCK_STREAM) else - ::Socket.getaddrinfo(@data[:host], @data[:port], @data[:family], ::Socket::Constants::SOCK_STREAM) + family = @data[:family] || ::Socket::Constants::AF_UNSPEC + ::Socket.getaddrinfo(@data[:host], @data[:port], family, ::Socket::Constants::SOCK_STREAM) end addrinfo.each do |_, port, _, ip, a_family, s_type| diff -Nru ruby-excon-0.25.1/lib/excon/ssl_socket.rb ruby-excon-0.28.0/lib/excon/ssl_socket.rb --- ruby-excon-0.25.1/lib/excon/ssl_socket.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/lib/excon/ssl_socket.rb 2013-11-12 16:08:34.000000000 +0000 @@ -9,21 +9,28 @@ # create ssl context ssl_context = OpenSSL::SSL::SSLContext.new - + ssl_context.ciphers = @data[:ciphers] + ssl_context.ssl_version = @data[:ssl_version] if @data[:ssl_version] if @data[:ssl_verify_peer] # turn verification on ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER - if @data[:ssl_ca_path] - ssl_context.ca_path = @data[:ssl_ca_path] - elsif @data[:ssl_ca_file] - ssl_context.ca_file = @data[:ssl_ca_file] + if ca_path = ENV['SSL_CERT_DIR'] || @data[:ssl_ca_path] + ssl_context.ca_path = ca_path + elsif ca_file = ENV['SSL_CERT_FILE'] || @data[:ssl_ca_file] + ssl_context.ca_file = ca_file else # attempt default, fallback to bundled ssl_context.cert_store = OpenSSL::X509::Store.new - if !defined?(OpenSSL::Config::DEFAULT_CONFIG_FILE) || File.exists?(OpenSSL::Config::DEFAULT_CONFIG_FILE) - ssl_context.cert_store.set_default_paths - else - ssl_context.cert_store.add_file(DEFAULT_CA_FILE) + ssl_context.cert_store.set_default_paths + + # workaround issue #257 (JRUBY-6970) + ca_file = DEFAULT_CA_FILE + ca_file.gsub!(/^jar:/, "") if ca_file =~ /^jar:file:\// + + begin + ssl_context.cert_store.add_file(ca_file) + rescue => e + Excon.display_warning("Excon unable to add file to cert store, ignoring: #{ca_file}\n[#{e.class}] #{e.message}\n#{e.backtrace.join("\n")}") end end else @@ -44,8 +51,8 @@ end if @data[:proxy] - request = 'CONNECT ' << @data[:host] << ':' << @data[:port] << Excon::HTTP_1_1 - request << 'Host: ' << @data[:host] << ':' << @data[:port] << Excon::CR_NL + request = 'CONNECT ' << @data[:host] << port_string(@data) << Excon::HTTP_1_1 + request << 'Host: ' << @data[:host] << port_string(@data) << Excon::CR_NL if @data[:proxy][:password] || @data[:proxy][:user] auth = ['' << @data[:proxy][:user].to_s << ':' << @data[:proxy][:password].to_s].pack('m').delete(Excon::CR_NL) @@ -96,7 +103,6 @@ def check_nonblock_support # backwards compatability for things lacking nonblock if !DEFAULT_NONBLOCK && @data[:nonblock] - Excon.display_warning("Excon nonblock is not supported by your OpenSSL::SSL::SSLSocket") @data[:nonblock] = false end end diff -Nru ruby-excon-0.25.1/lib/excon/unix_socket.rb ruby-excon-0.28.0/lib/excon/unix_socket.rb --- ruby-excon-0.25.1/lib/excon/unix_socket.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby-excon-0.28.0/lib/excon/unix_socket.rb 2013-11-12 16:08:34.000000000 +0000 @@ -0,0 +1,22 @@ +module Excon + class UnixSocket < Excon::Socket + + private + + def connect + begin + @socket = ::UNIXSocket.new(@data[:socket]) + rescue Errno::ECONNREFUSED + @socket.close if @socket + raise + end + + if @data[:tcp_nodelay] + @socket.setsockopt(::Socket::IPPROTO_TCP, + ::Socket::TCP_NODELAY, + true) + end + end + + end +end diff -Nru ruby-excon-0.25.1/lib/excon/utils.rb ruby-excon-0.28.0/lib/excon/utils.rb --- ruby-excon-0.25.1/lib/excon/utils.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby-excon-0.28.0/lib/excon/utils.rb 2013-11-12 16:08:34.000000000 +0000 @@ -0,0 +1,57 @@ +module Excon + module Utils + extend self + + def valid_connection_keys(params = {}) + Excon::VALID_CONNECTION_KEYS + end + + def valid_request_keys(params = {}) + Excon::VALID_REQUEST_KEYS + end + + def connection_uri(datum = @data) + unless datum + raise ArgumentError, '`datum` must be given unless called on a Connection' + end + if datum[:scheme] == UNIX + '' << datum[:scheme] << '://' << datum[:socket] + else + '' << datum[:scheme] << '://' << datum[:host] << port_string(datum) + end + end + + def request_uri(datum) + connection_uri(datum) << datum[:path] << query_string(datum) + end + + def port_string(datum) + if datum[:port].nil? || (datum[:omit_default_port] && ((datum[:scheme].casecmp('http') == 0 && datum[:port] == 80) || (datum[:scheme].casecmp('https') == 0 && datum[:port] == 443))) + '' + else + ':' << datum[:port].to_s + end + end + + def query_string(datum) + str = '' + case datum[:query] + when String + str << '?' << datum[:query] + when Hash + str << '?' + datum[:query].sort_by {|k,_| k.to_s }.each do |key, values| + if values.nil? + str << key.to_s << '&' + else + [values].flatten.each do |value| + str << key.to_s << '=' << CGI.escape(value.to_s) << '&' + end + end + end + str.chop! # remove trailing '&' + end + str + end + end +end diff -Nru ruby-excon-0.25.1/lib/excon.rb ruby-excon-0.28.0/lib/excon.rb --- ruby-excon-0.25.1/lib/excon.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/lib/excon.rb 2013-11-12 16:08:34.000000000 +0000 @@ -8,6 +8,8 @@ require 'socket' require 'timeout' require 'uri' +require 'zlib' +require 'stringio' # Define defaults first so they will be available to other files module Excon @@ -17,13 +19,17 @@ def defaults @defaults ||= { :chunk_size => CHUNK_SIZE || DEFAULT_CHUNK_SIZE, + :ciphers => 'HIGH:!SSLv2:!aNULL:!eNULL:!3DES', :connect_timeout => 60, :debug_request => false, :debug_response => false, - :headers => {}, + :headers => { + 'User-Agent' => USER_AGENT + }, :idempotent => false, :instrumentor_name => 'excon', :middlewares => [ + Excon::Middleware::ResponseParser, Excon::Middleware::Expects, Excon::Middleware::Idempotent, Excon::Middleware::Instrumentor, @@ -50,30 +56,37 @@ end end +require 'excon/utils' require 'excon/constants' require 'excon/connection' require 'excon/errors' require 'excon/middlewares/base' +require 'excon/middlewares/decompress' require 'excon/middlewares/expects' require 'excon/middlewares/idempotent' require 'excon/middlewares/instrumentor' require 'excon/middlewares/mock' +require 'excon/middlewares/redirect_follower' +require 'excon/middlewares/response_parser' require 'excon/response' require 'excon/socket' require 'excon/ssl_socket' +require 'excon/unix_socket' require 'excon/standard_instrumentor' module Excon class << self def display_warning(warning) - # Ruby convention mandates complete silence when VERBOSE == nil - $stderr.puts(warning) if !ENV['VERBOSE'].nil? + # Respect Ruby's $VERBOSE setting, unless EXCON_DEBUG is set + if !$VERBOSE.nil? || ENV['EXCON_DEBUG'] + $stderr.puts "[excon][WARNING] #{ warning }" + end end # Status of mocking def mock - display_warning("Excon#mock is deprecated, pass Excon.defaults[:mock] instead (#{caller.first}") + display_warning("Excon#mock is deprecated, use Excon.defaults[:mock] instead (#{caller.first})") self.defaults[:mock] end @@ -81,7 +94,7 @@ # false is the default and works as expected # true returns a value from stubs or raises def mock=(new_mock) - display_warning("Excon#mock is deprecated, pass Excon.defaults[:mock]= instead (#{caller.first})") + display_warning("Excon#mock is deprecated, use Excon.defaults[:mock]= instead (#{caller.first})") self.defaults[:mock] = new_mock end @@ -100,14 +113,14 @@ # @return [true, false] Whether or not to verify the peer's SSL certificate / chain def ssl_verify_peer - display_warning("Excon#ssl_verify_peer= is deprecated, use Excon.defaults[:ssl_verify_peer]= instead (#{caller.first})") + display_warning("Excon#ssl_verify_peer is deprecated, use Excon.defaults[:ssl_verify_peer] instead (#{caller.first})") self.defaults[:ssl_verify_peer] end # Change the status of ssl peer verification # @see Excon#ssl_verify_peer (attr_reader) def ssl_verify_peer=(new_ssl_verify_peer) - display_warning("Excon#ssl_verify_peer is deprecated, use Excon.defaults[:ssl_verify_peer] instead (#{caller.first})") + display_warning("Excon#ssl_verify_peer= is deprecated, use Excon.defaults[:ssl_verify_peer]= instead (#{caller.first})") self.defaults[:ssl_verify_peer] = new_ssl_verify_peer end @@ -119,10 +132,11 @@ def new(url, params = {}) uri_parser = params[:uri_parser] || Excon.defaults[:uri_parser] uri = uri_parser.parse(url) + raise ArgumentError.new("Invalid URI: #{uri}") unless uri.scheme params = { :host => uri.host, :path => uri.path, - :port => uri.port.to_s, + :port => uri.port, :query => uri.query, :scheme => uri.scheme, :user => (URI.decode(uri.user) if uri.user), @@ -143,7 +157,7 @@ request_params.update( :host => uri.host, :path => uri.path, - :port => uri.port.to_s, + :port => uri.port, :query => uri.query, :scheme => uri.scheme ) @@ -169,11 +183,13 @@ end # get a stub matching params or nil + # @param [Hash] request params to match against, omitted params match all + # @return [Hash] response params to return from matched request or block to call with params def stub_for(request_params={}) if method = request_params.delete(:method) request_params[:method] = method.to_s.downcase.to_sym end - Excon.stubs.each do |stub, response| + Excon.stubs.each do |stub, response_params| captures = { :headers => {} } headers_match = !stub.has_key?(:headers) || stub[:headers].keys.all? do |key| case value = stub[:headers][key] @@ -199,7 +215,7 @@ end if headers_match && non_headers_match request_params[:captures] = captures - return response + return [stub, response_params] end end nil @@ -210,6 +226,14 @@ @stubs ||= [] end + # remove first/oldest stub matching request_params + # @param [Hash] request params to match against, omitted params match all + # @return [Hash] response params from deleted stub + def unstub(request_params = {}) + stub = stub_for(request_params) + Excon.stubs.delete_at(Excon.stubs.index(stub)) + end + # Generic non-persistent HTTP methods HTTP_VERBS.each do |method| module_eval <<-DEF, __FILE__, __LINE__ + 1 diff -Nru ruby-excon-0.25.1/metadata.yml ruby-excon-0.28.0/metadata.yml --- ruby-excon-0.25.1/metadata.yml 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/metadata.yml 2013-11-12 16:08:34.000000000 +0000 @@ -1,7 +1,7 @@ --- !ruby/object:Gem::Specification name: excon version: !ruby/object:Gem::Version - version: 0.25.1 + version: 0.28.0 prerelease: platform: ruby authors: @@ -11,7 +11,7 @@ autorequire: bindir: bin cert_chain: [] -date: 2013-07-02 00:00:00.000000000 Z +date: 2013-10-28 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: activesupport @@ -178,14 +178,19 @@ - lib/excon/constants.rb - lib/excon/errors.rb - lib/excon/middlewares/base.rb +- lib/excon/middlewares/decompress.rb - lib/excon/middlewares/expects.rb - lib/excon/middlewares/idempotent.rb - lib/excon/middlewares/instrumentor.rb - lib/excon/middlewares/mock.rb +- lib/excon/middlewares/redirect_follower.rb +- lib/excon/middlewares/response_parser.rb - lib/excon/response.rb - lib/excon/socket.rb - lib/excon/ssl_socket.rb - lib/excon/standard_instrumentor.rb +- lib/excon/unix_socket.rb +- lib/excon/utils.rb - tests/authorization_header_tests.rb - tests/bad_tests.rb - tests/basic_tests.rb @@ -194,14 +199,17 @@ - tests/data/xs - tests/errors_tests.rb - tests/header_tests.rb +- tests/middlewares/decompress_tests.rb - tests/middlewares/idempotent_tests.rb - tests/middlewares/instrumentation_tests.rb - tests/middlewares/mock_tests.rb +- tests/middlewares/redirect_follower_tests.rb - tests/proxy_tests.rb - tests/query_string_tests.rb - tests/rackups/basic.rb - tests/rackups/basic.ru - tests/rackups/basic_auth.ru +- tests/rackups/deflater.ru - tests/rackups/proxy.ru - tests/rackups/query_string.ru - tests/rackups/request_headers.ru @@ -220,8 +228,10 @@ - tests/test_helper.rb - tests/thread_safety_tests.rb - tests/timeout_tests.rb +- tests/utils_tests.rb homepage: https://github.com/geemus/excon -licenses: [] +licenses: +- MIT post_install_message: rdoc_options: - --charset=UTF-8 @@ -235,7 +245,7 @@ version: '0' segments: - 0 - hash: -1836858691411277981 + hash: 527099065426297719 required_rubygems_version: !ruby/object:Gem::Requirement none: false requirements: diff -Nru ruby-excon-0.25.1/tests/authorization_header_tests.rb ruby-excon-0.28.0/tests/authorization_header_tests.rb --- ruby-excon-0.25.1/tests/authorization_header_tests.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/tests/authorization_header_tests.rb 2013-11-12 16:08:34.000000000 +0000 @@ -1,5 +1,5 @@ -with_rackup('basic_auth.ru') do - Shindo.tests('Excon basics (Authorization data redacted)') do +Shindo.tests('Excon basics (Authorization data redacted)') do + with_rackup('basic_auth.ru') do cases = [ ['user & pass', 'http://user1:pass1@foo.com/', 'Basic dXNlcjE6cGFzczE='], ['email & pass', 'http://foo%40bar.com:pass1@foo.com/', 'Basic Zm9vQGJhci5jb206cGFzczE='], diff -Nru ruby-excon-0.25.1/tests/basic_tests.rb ruby-excon-0.28.0/tests/basic_tests.rb --- ruby-excon-0.25.1/tests/basic_tests.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/tests/basic_tests.rb 2013-11-12 16:08:34.000000000 +0000 @@ -1,87 +1,84 @@ -with_rackup('basic.ru') do - Shindo.tests('Excon basics') do +Shindo.tests('Excon basics') do + with_rackup('basic.ru') do basic_tests - end - - Shindo.tests('explicit uri passed to connection') do - connection = Excon::Connection.new({ - :host => '127.0.0.1', - :nonblock => false, - :port => 9292, - :scheme => 'http', - :ssl_verify_peer => false - }) - tests('GET /content-length/100') do - response = connection.request(:method => :get, :path => '/content-length/100') - - tests('response[:status]').returns(200) do + tests('explicit uri passed to connection') do + tests('GET /content-length/100').returns(200) do + connection = Excon::Connection.new({ + :host => '127.0.0.1', + :nonblock => false, + :port => 9292, + :scheme => 'http', + :ssl_verify_peer => false + }) + response = connection.request(:method => :get, :path => '/content-length/100') response[:status] end end end end -with_rackup('basic_auth.ru') do - Shindo.tests('Excon basics (Basic Auth Pass)') do +Shindo.tests('Excon basics (Basic Auth Pass)') do + with_rackup('basic_auth.ru') do basic_tests('http://test_user:test_password@127.0.0.1:9292') - end - Shindo.tests('Excon basics (Basic Auth Fail)') do - cases = [ - ['correct user, no password', 'http://test_user@127.0.0.1:9292'], - ['correct user, wrong password', 'http://test_user:fake_password@127.0.0.1:9292'], - ['wrong user, correct password', 'http://fake_user:test_password@127.0.0.1:9292'], - ] - cases.each do |desc,url| - connection = Excon.new(url) - response = connection.request(:method => :get, :path => '/content-length/100') - - tests("response.status for #{desc}").returns(401) do - response.status + tests('Excon basics (Basic Auth Fail)') do + cases = [ + ['correct user, no password', 'http://test_user@127.0.0.1:9292'], + ['correct user, wrong password', 'http://test_user:fake_password@127.0.0.1:9292'], + ['wrong user, correct password', 'http://fake_user:test_password@127.0.0.1:9292'], + ] + cases.each do |desc,url| + tests("response.status for #{desc}").returns(401) do + connection = Excon.new(url) + response = connection.request(:method => :get, :path => '/content-length/100') + response.status + end end - end end end -with_rackup('ssl.ru') do - Shindo.tests('Excon basics (ssl)') do +Shindo.tests('Excon basics (ssl)') do + with_rackup('ssl.ru') do basic_tests('https://127.0.0.1:9443') end end -with_rackup('ssl_verify_peer.ru') do - Shindo.tests('Excon basics (ssl file)',['focus']) do - connection = Excon::Connection.new({ - :host => '127.0.0.1', - :nonblock => false, - :port => 8443, - :scheme => 'https', - :ssl_verify_peer => false - }) +Shindo.tests('Excon basics (ssl file)',['focus']) do + with_rackup('ssl_verify_peer.ru') do tests('GET /content-length/100').raises(Excon::Errors::SocketError) do + connection = Excon::Connection.new({ + :host => '127.0.0.1', + :nonblock => false, + :port => 8443, + :scheme => 'https', + :ssl_verify_peer => false + }) connection.request(:method => :get, :path => '/content-length/100') end basic_tests('https://127.0.0.1:8443', :client_key => File.join(File.dirname(__FILE__), 'data', 'excon.cert.key'), - :client_cert => File.join(File.dirname(__FILE__), 'data', 'excon.cert.crt') + :client_cert => File.join(File.dirname(__FILE__), 'data', 'excon.cert.crt'), + :reset_connection => RUBY_VERSION == '1.9.2' ) end +end - Shindo.tests('Excon basics (ssl file paths)',['focus']) do - connection = Excon::Connection.new({ - :host => '127.0.0.1', - :nonblock => false, - :port => 8443, - :scheme => 'https', - :ssl_verify_peer => false - }) +Shindo.tests('Excon basics (ssl file paths)',['focus']) do + with_rackup('ssl_verify_peer.ru') do tests('GET /content-length/100').raises(Excon::Errors::SocketError) do + connection = Excon::Connection.new({ + :host => '127.0.0.1', + :nonblock => false, + :port => 8443, + :scheme => 'https', + :ssl_verify_peer => false + }) connection.request(:method => :get, :path => '/content-length/100') end @@ -91,19 +88,35 @@ ) end +end - Shindo.tests('Excon basics (ssl string)', ['focus']) do - connection = Excon::Connection.new({ - :host => '127.0.0.1', - :nonblock => false, - :port => 8443, - :scheme => 'https', - :ssl_verify_peer => false - }) - +Shindo.tests('Excon basics (ssl string)', ['focus']) do + with_rackup('ssl_verify_peer.ru') do basic_tests('https://127.0.0.1:8443', :private_key => File.read(File.join(File.dirname(__FILE__), 'data', 'excon.cert.key')), :certificate => File.read(File.join(File.dirname(__FILE__), 'data', 'excon.cert.crt')) ) end end + +Shindo.tests('Excon basics (Unix socket)') do + pending if RUBY_PLATFORM == 'java' # need to find suitable server for jruby + + file_name = '/tmp/unicorn.sock' + with_unicorn('basic.ru', file_name) do + basic_tests("unix:/", :socket => file_name) + + tests('explicit uri passed to connection') do + tests('GET /content-length/100').returns(200) do + connection = Excon::Connection.new({ + :socket => file_name, + :nonblock => false, + :scheme => 'unix', + :ssl_verify_peer => false + }) + response = connection.request(:method => :get, :path => '/content-length/100') + response[:status] + end + end + end +end diff -Nru ruby-excon-0.25.1/tests/errors_tests.rb ruby-excon-0.28.0/tests/errors_tests.rb --- ruby-excon-0.25.1/tests/errors_tests.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/tests/errors_tests.rb 2013-11-12 16:08:34.000000000 +0000 @@ -1,5 +1,14 @@ Shindo.tests('HTTPStatusError request/response debugging') do + tests('new raises errors for bad URIs').returns(true) do + begin + Excon.new('foo') + false + rescue => err + err.to_s.include? 'foo' + end + end + with_server('error') do tests('message does not include response or response info').returns(true) do diff -Nru ruby-excon-0.25.1/tests/middlewares/decompress_tests.rb ruby-excon-0.28.0/tests/middlewares/decompress_tests.rb --- ruby-excon-0.25.1/tests/middlewares/decompress_tests.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby-excon-0.28.0/tests/middlewares/decompress_tests.rb 2013-11-12 16:08:34.000000000 +0000 @@ -0,0 +1,24 @@ +Shindo.tests('Excon decompression support') do + env_init + + with_rackup('deflater.ru') do + connection = Excon.new( + 'http://127.0.0.1:9292/echo', + :body => 'x' * 100, + :method => :post, + :middlewares => Excon.defaults[:middlewares] + [Excon::Middleware::Decompress] + ) + + tests('deflate').returns('x' * 100) do + response = connection.request(:headers => { 'Accept-Encoding' => 'deflate' }) + response.body + end + + tests('gzip').returns('x' * 100) do + response = connection.request(:headers => { 'Accept-Encoding' => 'gzip' }) + response.body + end + end + + env_restore +end diff -Nru ruby-excon-0.25.1/tests/middlewares/instrumentation_tests.rb ruby-excon-0.28.0/tests/middlewares/instrumentation_tests.rb --- ruby-excon-0.25.1/tests/middlewares/instrumentation_tests.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/tests/middlewares/instrumentation_tests.rb 2013-11-12 16:08:34.000000000 +0000 @@ -84,11 +84,11 @@ [:host, :path, :port, :scheme].select {|k| @events.first.payload.has_key? k} end - tests('params in request overwrite those in constructor').returns('cheezburger') do + tests('params in request overwrite those in constructor').returns('/cheezburger') do subscribe(/excon/) stub_success - make_request(false, :host => 'cheezburger') - @events.first.payload[:host] + make_request(false, :path => '/cheezburger') + @events.first.payload[:path] end tests('notify on retry').returns(3) do diff -Nru ruby-excon-0.25.1/tests/middlewares/mock_tests.rb ruby-excon-0.28.0/tests/middlewares/mock_tests.rb --- ruby-excon-0.25.1/tests/middlewares/mock_tests.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/tests/middlewares/mock_tests.rb 2013-11-12 16:08:34.000000000 +0000 @@ -85,7 +85,7 @@ response.body end - tests('response.headers').returns({'Host' => '127.0.0.1:9292'}) do + tests('response.headers').returns({'Host' => '127.0.0.1:9292', 'User-Agent' => "excon/#{Excon::VERSION}"}) do response.headers end @@ -193,6 +193,32 @@ end + tests("stub_for({})") do + connection = Excon.new('http://127.0.0.1:9292', :mock => true) + Excon.stub({}, {}) + + tests("stub_for({})").returns([{}, {}]) do + Excon.stub_for({}) + end + + Excon.stubs.clear + end + + tests("unstub({})") do + connection = Excon.new('http://127.0.0.1:9292', :mock => true) + Excon.stub({}, {}) + + tests("unstub({})").returns([{}, {}]) do + Excon.unstub({}) + end + + tests("request(:method => :get)").raises(Excon::Errors::StubNotFound) do + connection.request(:method => :get) + end + + Excon.stubs.clear + end + tests('mock = false') do with_rackup('basic.ru') do basic_tests diff -Nru ruby-excon-0.25.1/tests/middlewares/redirect_follower_tests.rb ruby-excon-0.28.0/tests/middlewares/redirect_follower_tests.rb --- ruby-excon-0.25.1/tests/middlewares/redirect_follower_tests.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby-excon-0.28.0/tests/middlewares/redirect_follower_tests.rb 2013-11-12 16:08:34.000000000 +0000 @@ -0,0 +1,32 @@ +Shindo.tests('Excon redirector support') do + env_init + + connection = Excon.new( + 'http://127.0.0.1:9292', + :middlewares => Excon.defaults[:middlewares] + [Excon::Middleware::RedirectFollower], + :mock => true + ) + + Excon.stub( + { :path => '/old' }, + { + :headers => { 'Location' => 'http://127.0.0.1:9292/new' }, + :body => 'old', + :status => 301 + } + ) + + Excon.stub( + { :path => '/new' }, + { + :body => 'new', + :status => 200 + } + ) + + tests("request(:method => :get, :path => '/old').body").returns('new') do + connection.request(:method => :get, :path => '/old').body + end + + env_restore +end diff -Nru ruby-excon-0.25.1/tests/proxy_tests.rb ruby-excon-0.28.0/tests/proxy_tests.rb --- ruby-excon-0.25.1/tests/proxy_tests.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/tests/proxy_tests.rb 2013-11-12 16:08:34.000000000 +0000 @@ -18,7 +18,7 @@ connection.data[:proxy][:host] end - tests('connection.data[:proxy][:port]').returns('8080') do + tests('connection.data[:proxy][:port]').returns(8080) do connection.data[:proxy][:port] end @@ -37,7 +37,7 @@ connection.data[:proxy][:host] end - tests('connection.data[:proxy][:port]').returns('8080') do + tests('connection.data[:proxy][:port]').returns(8080) do connection.data[:proxy][:port] end @@ -53,7 +53,7 @@ connection.data[:proxy][:host] end - tests('connection.data[:proxy][:port]').returns('8081') do + tests('connection.data[:proxy][:port]').returns(8081) do connection.data[:proxy][:port] end @@ -69,7 +69,7 @@ connection.data[:proxy][:host] end - tests('connection.data[:proxy][:port]').returns('8080') do + tests('connection.data[:proxy][:port]').returns(8080) do connection.data[:proxy][:port] end end @@ -125,7 +125,7 @@ connection.data[:proxy][:host] end - tests('connection.data[:proxy][:port]').returns('8080') do + tests('connection.data[:proxy][:port]').returns(8080) do connection.data[:proxy][:port] end diff -Nru ruby-excon-0.25.1/tests/query_string_tests.rb ruby-excon-0.28.0/tests/query_string_tests.rb --- ruby-excon-0.25.1/tests/query_string_tests.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/tests/query_string_tests.rb 2013-11-12 16:08:34.000000000 +0000 @@ -1,5 +1,5 @@ -with_rackup('query_string.ru') do - Shindo.tests('Excon query string variants') do +Shindo.tests('Excon query string variants') do + with_rackup('query_string.ru') do connection = Excon.new('http://127.0.0.1:9292') tests(":query => {:foo => 'bar'}") do diff -Nru ruby-excon-0.25.1/tests/rackups/deflater.ru ruby-excon-0.28.0/tests/rackups/deflater.ru --- ruby-excon-0.25.1/tests/rackups/deflater.ru 1970-01-01 00:00:00.000000000 +0000 +++ ruby-excon-0.28.0/tests/rackups/deflater.ru 2013-11-12 16:08:34.000000000 +0000 @@ -0,0 +1,4 @@ +require File.join(File.dirname(__FILE__), 'basic') + +use Rack::Deflater +run Basic diff -Nru ruby-excon-0.25.1/tests/request_method_tests.rb ruby-excon-0.28.0/tests/request_method_tests.rb --- ruby-excon-0.25.1/tests/request_method_tests.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/tests/request_method_tests.rb 2013-11-12 16:08:34.000000000 +0000 @@ -34,6 +34,12 @@ connection.delete.body end + tests('not modifies path argument').returns('path') do + path = 'path' + connection.get(:path => path) + path + end + end end diff -Nru ruby-excon-0.25.1/tests/requests_tests.rb ruby-excon-0.28.0/tests/requests_tests.rb --- ruby-excon-0.25.1/tests/requests_tests.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/tests/requests_tests.rb 2013-11-12 16:08:34.000000000 +0000 @@ -1,9 +1,9 @@ -with_rackup('basic.ru') do - Shindo.tests('requests should succeed') do - - connection = Excon.new('http://127.0.0.1:9292') +Shindo.tests('requests should succeed') do + with_rackup('basic.ru') do tests('HEAD /content-length/100, GET /content-length/100') do + + connection = Excon.new('http://127.0.0.1:9292') responses = connection.requests([ {:method => :head, :path => '/content-length/100'}, {:method => :get, :path => '/content-length/100'} @@ -27,19 +27,18 @@ end - end - - Shindo.tests('requests should succeed with tcp_nodelay') do - - connection = Excon.new('http://127.0.0.1:9292', :tcp_nodelay => true) + tests('requests should succeed with tcp_nodelay') do - tests('GET /content-length/100') do - responses = connection.requests([ - {:method => :get, :path => '/content-length/100'} - ]) + connection = Excon.new('http://127.0.0.1:9292', :tcp_nodelay => true) - tests('get content length is 100').returns('100') do - responses.last.headers['Content-Length'] + tests('GET /content-length/100') do + responses = connection.requests([ + {:method => :get, :path => '/content-length/100'} + ]) + + tests('get content length is 100').returns('100') do + responses.last.headers['Content-Length'] + end end end end diff -Nru ruby-excon-0.25.1/tests/test_helper.rb ruby-excon-0.28.0/tests/test_helper.rb --- ruby-excon-0.25.1/tests/test_helper.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/tests/test_helper.rb 2013-11-12 16:08:34.000000000 +0000 @@ -3,9 +3,8 @@ Bundler.require(:default, :development) -require 'stringio' - def basic_tests(url = 'http://127.0.0.1:9292', options = {}) + reset_connection = !!options.delete(:reset_connection) [false, true].each do |nonblock| options = options.merge({:ssl_verify_peer => false, :nonblock => nonblock }) connection = Excon.new(url, options) @@ -24,6 +23,7 @@ end tests("response.headers['Connection']").returns('Keep-Alive') do + pending if connection.data[:scheme] == Excon::UNIX response.headers['Connection'] end @@ -36,10 +36,12 @@ end test("Time.parse(response.headers['Date']).is_a?(Time)") do + pending if connection.data[:scheme] == Excon::UNIX Time.parse(response.headers['Date']).is_a?(Time) end test("!!(response.headers['Server'] =~ /^WEBrick/)") do + pending if connection.data[:scheme] == Excon::UNIX !!(response.headers['Server'] =~ /^WEBrick/) end @@ -48,6 +50,7 @@ end tests("response.remote_ip").returns("127.0.0.1") do + pending if connection.data[:scheme] == Excon::UNIX response.remote_ip end @@ -57,8 +60,10 @@ tests("deprecated block usage").returns(['x' * 100, 0, 100]) do data = [] - connection.request(:method => :get, :path => '/content-length/100') do |chunk, remaining_length, total_length| - data = [chunk, remaining_length, total_length] + silence_warnings do + connection.request(:method => :get, :path => '/content-length/100') do |chunk, remaining_length, total_length| + data = [chunk, remaining_length, total_length] + end end data end @@ -77,6 +82,9 @@ tests('POST /body-sink') do tests('response.body').returns("5000000") do + if reset_connection && !nonblock + connection.reset + end response = connection.request(:method => :post, :path => '/body-sink', :headers => { 'Content-Type' => 'text/plain' }, :body => 'x' * 5_000_000) response.body end @@ -110,6 +118,20 @@ response.body end + tests('with multi-byte strings') do + body = "\xC3\xBC" * 100 + headers = { 'Custom' => body.dup } + if RUBY_VERSION >= '1.9' + body.force_encoding('BINARY') + headers['Custom'].force_encoding('UTF-8') + end + + returns(body, 'properly concatenates request+headers and body') do + response = connection.request(:method => :post, :path => '/echo', :headers => headers, :body => body) + response.body + end + end + end tests('PUT /echo') do @@ -134,6 +156,20 @@ response.body end + tests('with multi-byte strings') do + body = "\xC3\xBC" * 100 + headers = { 'Custom' => body.dup } + if RUBY_VERSION >= '1.9' + body.force_encoding('BINARY') + headers['Custom'].force_encoding('UTF-8') + end + + returns(body, 'properly concatenates request+headers and body') do + response = connection.request(:method => :put, :path => '/echo', :headers => headers, :body => body) + response.body + end + end + end end @@ -164,6 +200,14 @@ @env_stack ||= [] end +def silence_warnings + orig_verbose = $VERBOSE + $VERBOSE = nil + yield +ensure + $VERBOSE = orig_verbose +end + def rackup_path(*parts) File.expand_path(File.join(File.dirname(__FILE__), 'rackups', *parts)) end @@ -185,6 +229,26 @@ end end +def with_unicorn(name, file_name='/tmp/unicorn.sock') + unless RUBY_PLATFORM == 'java' + GC.disable + pid, w, r, e = Open4.popen4("unicorn", "-l", "unix://#{file_name}", rackup_path(name)) + until e.gets =~ /worker=0 ready/; end + else + # need to find suitable server for jruby + end + yield +ensure + unless RUBY_PLATFORM == 'java' + Process.kill(9, pid) + GC.enable + Process.wait(pid) + end + if File.exist?(file_name) + File.delete(file_name) + end +end + def server_path(*parts) File.expand_path(File.join(File.dirname(__FILE__), 'servers', *parts)) end diff -Nru ruby-excon-0.25.1/tests/thread_safety_tests.rb ruby-excon-0.28.0/tests/thread_safety_tests.rb --- ruby-excon-0.25.1/tests/thread_safety_tests.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/tests/thread_safety_tests.rb 2013-11-12 16:08:34.000000000 +0000 @@ -1,5 +1,5 @@ -with_rackup('thread_safety.ru') do - Shindo.tests('Excon thread safety') do +Shindo.tests('Excon thread safety') do + with_rackup('thread_safety.ru') do connection = Excon.new('http://127.0.0.1:9292') long_thread = Thread.new { diff -Nru ruby-excon-0.25.1/tests/timeout_tests.rb ruby-excon-0.28.0/tests/timeout_tests.rb --- ruby-excon-0.25.1/tests/timeout_tests.rb 2013-07-06 20:41:22.000000000 +0000 +++ ruby-excon-0.28.0/tests/timeout_tests.rb 2013-11-12 16:08:34.000000000 +0000 @@ -1,7 +1,7 @@ -with_rackup('timeout.ru') do - Shindo.tests('read should timeout') do - [false, true].each do |nonblock| +Shindo.tests('read should timeout') do + with_rackup('timeout.ru') do + [false, true].each do |nonblock| connection = Excon.new('http://127.0.0.1:9292', :nonblock => nonblock) tests("nonblock => #{nonblock} hits read_timeout").raises(Excon::Errors::Timeout) do diff -Nru ruby-excon-0.25.1/tests/utils_tests.rb ruby-excon-0.28.0/tests/utils_tests.rb --- ruby-excon-0.25.1/tests/utils_tests.rb 1970-01-01 00:00:00.000000000 +0000 +++ ruby-excon-0.28.0/tests/utils_tests.rb 2013-11-12 16:08:34.000000000 +0000 @@ -0,0 +1,68 @@ +Shindo.tests('Excon::Utils') do + + tests('#connection_uri') do + + expected_uri = 'unix:///tmp/some.sock' + tests('using UNIX scheme').returns(expected_uri) do + connection = Excon.new('unix:///some/path', :socket => '/tmp/some.sock') + Excon::Utils.connection_uri(connection.data) + end + + tests('using HTTP scheme') do + + expected_uri = 'http://foo.com:80' + tests('with default port').returns(expected_uri) do + connection = Excon.new('http://foo.com/some/path') + Excon::Utils.connection_uri(connection.data) + end + + expected_uri = 'http://foo.com' + tests('without default port').returns(expected_uri) do + connection = Excon.new('http://foo.com/some/path', :omit_default_port => true) + Excon::Utils.connection_uri(connection.data) + end + + end + + end + + tests('#request_uri') do + + tests('using UNIX scheme') do + + expected_uri = 'unix:///tmp/some.sock/some/path' + tests('without query').returns(expected_uri) do + connection = Excon.new('unix:/', :socket => '/tmp/some.sock') + params = { :path => '/some/path' } + Excon::Utils.request_uri(connection.data.merge(params)) + end + + expected_uri = 'unix:///tmp/some.sock/some/path?bar=that&foo=this' + tests('with query').returns(expected_uri) do + connection = Excon.new('unix:/', :socket => '/tmp/some.sock') + params = { :path => '/some/path', :query => { :foo => 'this', :bar => 'that' } } + Excon::Utils.request_uri(connection.data.merge(params)) + end + + end + + tests('using HTTP scheme') do + + expected_uri = 'http://foo.com:80/some/path' + tests('without query').returns(expected_uri) do + connection = Excon.new('http://foo.com') + params = { :path => '/some/path' } + Excon::Utils.request_uri(connection.data.merge(params)) + end + + expected_uri = 'http://foo.com:80/some/path?bar=that&foo=this' + tests('with query').returns(expected_uri) do + connection = Excon.new('http://foo.com') + params = { :path => '/some/path', :query => { :foo => 'this', :bar => 'that' } } + Excon::Utils.request_uri(connection.data.merge(params)) + end + + end + + end +end