diff --git a/History b/History index b175b798..530e9307 100644 --- a/History +++ b/History @@ -1,5 +1,12 @@ -== 0.4.5 2009-08-22 - * default headers are now correctly passed (sporkd) +== 0.4.5 2009-09-12 +* bug fixes + * Fixed class-level headers overwritten by cookie management code. Closes #19 + * Fixed "superclass mismatch for class BlankSlate" error. Closes #20 + * Fixed reading files as post data from the command line (vesan) +* minor enhancements + * Timeout option added; will raise a Timeout::Error after the timeout has elapsed (attack). Closes #17 + HTTParty.get "http://github.com", :timeout => 1 + * Building gem with Jeweler == 0.4.4 2009-07-19 * 2 minor update @@ -131,4 +138,4 @@ == 0.1.0 2008-07-27 * 1 major enhancement: - * Initial release + * Initial release \ No newline at end of file diff --git a/Rakefile b/Rakefile index 71d151de..f7a662e0 100644 --- a/Rakefile +++ b/Rakefile @@ -1,69 +1,70 @@ -require 'rubygems' -require 'rake' - -begin - require 'jeweler' - Jeweler::Tasks.new do |gem| - gem.name = "httparty" - gem.summary = %Q{Makes http fun! Also, makes consuming restful web services dead easy.} - gem.description = %Q{Makes http fun! Also, makes consuming restful web services dead easy.} - gem.email = "nunemaker@gmail.com" - gem.homepage = "http://httparty.rubyforge.org" - gem.authors = ["John Nunemaker"] - gem.add_dependency 'crack', '>= 0.1.1' - gem.add_development_dependency "rspec" - gem.post_install_message = "When you HTTParty, you must party hard!" - # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings - end - Jeweler::RubyforgeTasks.new do |rubyforge| - rubyforge.doc_task = "rdoc" - end -rescue LoadError - puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler" -end - -require 'spec/rake/spectask' -Spec::Rake::SpecTask.new(:spec) do |spec| - spec.libs << 'lib' << 'spec' - spec.spec_files = FileList['spec/**/*_spec.rb'] -end - -Spec::Rake::SpecTask.new(:rcov) do |spec| - spec.libs << 'lib' << 'spec' - spec.pattern = 'spec/**/*_spec.rb' - spec.rcov = true -end - -task :spec => :check_dependencies - -begin - require 'cucumber/rake/task' - Cucumber::Rake::Task.new(:features) - - task :features => :check_dependencies -rescue LoadError - task :features do - abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber" - end -end - -task :default => [:spec, :features] - -require 'rake/rdoctask' -Rake::RDocTask.new do |rdoc| - if File.exist?('VERSION') - version = File.read('VERSION') - else - version = "" - end - - rdoc.rdoc_dir = 'rdoc' - rdoc.title = "httparty #{version}" - rdoc.rdoc_files.include('README*') - rdoc.rdoc_files.include('lib/**/*.rb') -end - -desc 'Upload website files to rubyforge' -task :website do - sh %{rsync -av website/ jnunemaker@rubyforge.org:/var/www/gforge-projects/httparty} +require 'rubygems' +require 'rake' + +begin + require 'jeweler' + Jeweler::Tasks.new do |gem| + gem.name = "httparty" + gem.summary = %Q{Makes http fun! Also, makes consuming restful web services dead easy.} + gem.description = %Q{Makes http fun! Also, makes consuming restful web services dead easy.} + gem.email = "nunemaker@gmail.com" + gem.homepage = "http://httparty.rubyforge.org" + gem.authors = ["John Nunemaker"] + gem.add_dependency 'crack', '>= 0.1.1' + gem.add_development_dependency "rspec", "1.2.8" + gem.post_install_message = "When you HTTParty, you must party hard!" + # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings + end + Jeweler::RubyforgeTasks.new do |rubyforge| + rubyforge.doc_task = "rdoc" + end +rescue LoadError + puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler" +end + +require 'spec/rake/spectask' +Spec::Rake::SpecTask.new(:spec) do |spec| +spec.libs << 'lib' << 'spec' +spec.spec_files = FileList['spec/**/*_spec.rb'] +spec.spec_opts = ['--options', 'spec/spec.opts'] +end + +Spec::Rake::SpecTask.new(:rcov) do |spec| +spec.libs << 'lib' << 'spec' +spec.pattern = 'spec/**/*_spec.rb' +spec.rcov = true +end + +task :spec => :check_dependencies + +begin + require 'cucumber/rake/task' + Cucumber::Rake::Task.new(:features) + + task :features => :check_dependencies +rescue LoadError + task :features do + abort "Cucumber is not available. In order to run features, you must: sudo gem install cucumber" + end +end + +task :default => [:spec, :features] + +require 'rake/rdoctask' +Rake::RDocTask.new do |rdoc| +if File.exist?('VERSION') + version = File.read('VERSION') +else + version = "" +end + +rdoc.rdoc_dir = 'rdoc' +rdoc.title = "httparty #{version}" +rdoc.rdoc_files.include('README*') +rdoc.rdoc_files.include('lib/**/*.rb') +end + +desc 'Upload website files to rubyforge' +task :website do +sh %{rsync -av website/ jnunemaker@rubyforge.org:/var/www/gforge-projects/httparty} end \ No newline at end of file diff --git a/VERSION b/VERSION index 6f2743d6..c8a5397f 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.4 +0.4.5 \ No newline at end of file diff --git a/bin/httparty b/bin/httparty index 729cc8a4..933a770f 100755 --- a/bin/httparty +++ b/bin/httparty @@ -37,7 +37,7 @@ OptionParser.new do |o| "--data [BODY]", "Data to put in request body (prefix with '@' for file)") do |d| if d =~ /^@/ - opts[:data] = open(d).read + opts[:data] = open(d[1..-1]).read else opts[:data] = d end diff --git a/features/steps/env.rb b/features/steps/env.rb index b85c79a9..c75dcec8 100644 --- a/features/steps/env.rb +++ b/features/steps/env.rb @@ -8,6 +8,7 @@ @host_and_port = "0.0.0.0:#{port}" @server = Mongrel::HttpServer.new("0.0.0.0", port) @server.run + @request_options = {} end After do diff --git a/features/steps/httparty_response_steps.rb b/features/steps/httparty_response_steps.rb index f210cc66..684d0f55 100644 --- a/features/steps/httparty_response_steps.rb +++ b/features/steps/httparty_response_steps.rb @@ -20,7 +20,7 @@ @response_from_httparty.code.should eql(code.to_i) end -Then /it should raise an HTTParty::RedirectionTooDeep exception/ do +Then /it should raise (?:an|a) ([\w:]+) exception/ do |exception| @exception_from_httparty.should_not be_nil - @exception_from_httparty.class.should eql(HTTParty::RedirectionTooDeep) + @exception_from_httparty.class.name.should eql(exception) end diff --git a/features/steps/httparty_steps.rb b/features/steps/httparty_steps.rb index 17c63dd0..3a82c78d 100644 --- a/features/steps/httparty_steps.rb +++ b/features/steps/httparty_steps.rb @@ -1,7 +1,11 @@ +When /^I set my HTTParty timeout option to (\d+)$/ do |timeout| + @request_options[:timeout] = timeout.to_i +end + When /I call HTTParty#get with '(.*)'$/ do |url| begin - @response_from_httparty = HTTParty.get("http://#{@host_and_port}#{url}") - rescue HTTParty::RedirectionTooDeep => e + @response_from_httparty = HTTParty.get("http://#{@host_and_port}#{url}", @request_options) + rescue HTTParty::RedirectionTooDeep, Timeout::Error => e @exception_from_httparty = e end end diff --git a/features/steps/mongrel_helper.rb b/features/steps/mongrel_helper.rb index 3b782485..80b8d8cd 100644 --- a/features/steps/mongrel_helper.rb +++ b/features/steps/mongrel_helper.rb @@ -1,6 +1,6 @@ def basic_mongrel_handler Class.new(Mongrel::HttpHandler) do - attr_writer :content_type, :response_body, :response_code + attr_writer :content_type, :response_body, :response_code, :preprocessor def initialize @content_type = "text/html" @@ -10,6 +10,7 @@ def initialize end def process(request, response) + instance_eval &@preprocessor if @preprocessor reply_with(response, @response_code, @response_body) end diff --git a/features/steps/remote_service_steps.rb b/features/steps/remote_service_steps.rb index 08e205c5..c806246d 100644 --- a/features/steps/remote_service_steps.rb +++ b/features/steps/remote_service_steps.rb @@ -12,6 +12,11 @@ @server.register(path, @handler) end +Given /^that service takes (\d+) seconds to generate a response$/ do |time| + preprocessor = lambda { sleep time.to_i } + @handler.preprocessor = preprocessor +end + Given /the response from the service has a Content-Type of '(.*)'/ do |content_type| @handler.content_type = content_type end diff --git a/features/supports_timeout_option.feature b/features/supports_timeout_option.feature new file mode 100644 index 00000000..aab5f5ba --- /dev/null +++ b/features/supports_timeout_option.feature @@ -0,0 +1,12 @@ +Feature: Supports the timeout option + In order to handle inappropriately slow response times + As a developer + I want my request to raise an exception after my specified timeout as elapsed + + Scenario: A long running response + Given a remote service that returns '

Some HTML

' + And that service is accessed at the path '/service.html' + And that service takes 2 seconds to generate a response + When I set my HTTParty timeout option to 1 + And I call HTTParty#get with '/service.html' + Then it should raise a Timeout::Error exception diff --git a/httparty.gemspec b/httparty.gemspec index d522dd7b..9a6190fc 100644 --- a/httparty.gemspec +++ b/httparty.gemspec @@ -1,26 +1,95 @@ +# Generated by jeweler +# DO NOT EDIT THIS FILE +# Instead, edit Jeweler::Tasks in Rakefile, and run `rake gemspec` # -*- encoding: utf-8 -*- Gem::Specification.new do |s| s.name = %q{httparty} - s.version = "0.4.4" + s.version = "0.4.5" - s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version= + s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["John Nunemaker"] - s.date = %q{2009-07-19} + s.date = %q{2009-09-12} s.default_executable = %q{httparty} s.description = %q{Makes http fun! Also, makes consuming restful web services dead easy.} s.email = %q{nunemaker@gmail.com} s.executables = ["httparty"] - s.extra_rdoc_files = ["bin/httparty", "lib/httparty/cookie_hash.rb", "lib/httparty/core_extensions.rb", "lib/httparty/exceptions.rb", "lib/httparty/module_inheritable_attributes.rb", "lib/httparty/request.rb", "lib/httparty/response.rb", "lib/httparty/version.rb", "lib/httparty.rb", "README.rdoc"] - s.files = ["bin/httparty", "cucumber.yml", "examples/aaws.rb", "examples/basic.rb", "examples/delicious.rb", "examples/google.rb", "examples/rubyurl.rb", "examples/twitter.rb", "examples/whoismyrep.rb", "features/basic_authentication.feature", "features/command_line.feature", "features/deals_with_http_error_codes.feature", "features/handles_multiple_formats.feature", "features/steps/env.rb", "features/steps/httparty_response_steps.rb", "features/steps/httparty_steps.rb", "features/steps/mongrel_helper.rb", "features/steps/remote_service_steps.rb", "features/supports_redirection.feature", "History", "httparty.gemspec", "lib/httparty/cookie_hash.rb", "lib/httparty/core_extensions.rb", "lib/httparty/exceptions.rb", "lib/httparty/module_inheritable_attributes.rb", "lib/httparty/request.rb", "lib/httparty/response.rb", "lib/httparty/version.rb", "lib/httparty.rb", "Manifest", "MIT-LICENSE", "Rakefile", "README.rdoc", "spec/fixtures/delicious.xml", "spec/fixtures/empty.xml", "spec/fixtures/google.html", "spec/fixtures/twitter.json", "spec/fixtures/twitter.xml", "spec/fixtures/undefined_method_add_node_for_nil.xml", "spec/httparty/cookie_hash_spec.rb", "spec/httparty/request_spec.rb", "spec/httparty/response_spec.rb", "spec/httparty_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "website/css/common.css", "website/index.html"] + s.extra_rdoc_files = [ + "README.rdoc" + ] + s.files = [ + ".gitignore", + "History", + "MIT-LICENSE", + "Manifest", + "README.rdoc", + "Rakefile", + "VERSION", + "bin/httparty", + "cucumber.yml", + "examples/aaws.rb", + "examples/basic.rb", + "examples/delicious.rb", + "examples/google.rb", + "examples/rubyurl.rb", + "examples/twitter.rb", + "examples/whoismyrep.rb", + "features/basic_authentication.feature", + "features/command_line.feature", + "features/deals_with_http_error_codes.feature", + "features/handles_multiple_formats.feature", + "features/steps/env.rb", + "features/steps/httparty_response_steps.rb", + "features/steps/httparty_steps.rb", + "features/steps/mongrel_helper.rb", + "features/steps/remote_service_steps.rb", + "features/supports_redirection.feature", + "features/supports_timeout_option.feature", + "httparty.gemspec", + "lib/httparty.rb", + "lib/httparty/cookie_hash.rb", + "lib/httparty/core_extensions.rb", + "lib/httparty/exceptions.rb", + "lib/httparty/module_inheritable_attributes.rb", + "lib/httparty/request.rb", + "lib/httparty/response.rb", + "lib/httparty/version.rb", + "spec/fixtures/delicious.xml", + "spec/fixtures/empty.xml", + "spec/fixtures/google.html", + "spec/fixtures/twitter.json", + "spec/fixtures/twitter.xml", + "spec/fixtures/undefined_method_add_node_for_nil.xml", + "spec/httparty/cookie_hash_spec.rb", + "spec/httparty/request_spec.rb", + "spec/httparty/response_spec.rb", + "spec/httparty_spec.rb", + "spec/spec.opts", + "spec/spec_helper.rb", + "website/css/common.css", + "website/index.html" + ] s.has_rdoc = true s.homepage = %q{http://httparty.rubyforge.org} s.post_install_message = %q{When you HTTParty, you must party hard!} - s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Httparty", "--main", "README.rdoc"] + s.rdoc_options = ["--charset=UTF-8"] s.require_paths = ["lib"] - s.rubyforge_project = %q{httparty} s.rubygems_version = %q{1.3.1} s.summary = %q{Makes http fun! Also, makes consuming restful web services dead easy.} + s.test_files = [ + "spec/httparty/cookie_hash_spec.rb", + "spec/httparty/request_spec.rb", + "spec/httparty/response_spec.rb", + "spec/httparty_spec.rb", + "spec/spec_helper.rb", + "examples/aaws.rb", + "examples/basic.rb", + "examples/delicious.rb", + "examples/google.rb", + "examples/rubyurl.rb", + "examples/twitter.rb", + "examples/whoismyrep.rb" + ] if s.respond_to? :specification_version then current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION @@ -28,13 +97,13 @@ Gem::Specification.new do |s| if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then s.add_runtime_dependency(%q, [">= 0.1.1"]) - s.add_development_dependency(%q, [">= 0"]) + s.add_development_dependency(%q, ["= 1.2.8"]) else s.add_dependency(%q, [">= 0.1.1"]) - s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, ["= 1.2.8"]) end else s.add_dependency(%q, [">= 0.1.1"]) - s.add_dependency(%q, [">= 0"]) + s.add_dependency(%q, ["= 1.2.8"]) end end diff --git a/lib/httparty.rb b/lib/httparty.rb index 8bb6b4b4..d6b9d625 100644 --- a/lib/httparty.rb +++ b/lib/httparty.rb @@ -172,11 +172,9 @@ def perform_request(http_method, path, options) #:nodoc: end def process_cookies(options) #:nodoc: - return unless options[:cookies] || default_cookies - options[:headers] ||= {} - options[:headers]["cookie"] = cookies.merge(options[:cookies] || {}).to_cookie_string - - options.delete(:cookies) + return unless options[:cookies] || default_cookies.any? + options[:headers] ||= headers.dup + options[:headers]["cookie"] = cookies.merge(options.delete(:cookies) || {}).to_cookie_string end end diff --git a/lib/httparty/request.rb b/lib/httparty/request.rb index 8d44d876..23523623 100644 --- a/lib/httparty/request.rb +++ b/lib/httparty/request.rb @@ -41,10 +41,15 @@ def perform end private + def http http = Net::HTTP.new(uri.host, uri.port, options[:http_proxyaddr], options[:http_proxyport]) http.use_ssl = (uri.port == 443) http.verify_mode = OpenSSL::SSL::VERIFY_NONE + if options[:timeout] && options[:timeout].is_a?(Integer) + http.open_timeout = options[:timeout] + http.read_timeout = options[:timeout] + end http end @@ -130,6 +135,15 @@ def parse_response(body) end end + def capture_cookies(response) + return unless response['Set-Cookie'] + cookies_hash = HTTParty::CookieHash.new() + cookies_hash.add_cookies(options[:headers]['Cookie']) if options[:headers] && options[:headers]['Cookie'] + cookies_hash.add_cookies(response['Set-Cookie']) + options[:headers] ||= {} + options[:headers]['Cookie'] = cookies_hash.to_cookie_string + end + def capture_cookies(response) return unless response['Set-Cookie'] cookies_hash = HTTParty::CookieHash.new() diff --git a/spec/httparty/request_spec.rb b/spec/httparty/request_spec.rb index 29610101..e8758224 100644 --- a/spec/httparty/request_spec.rb +++ b/spec/httparty/request_spec.rb @@ -46,8 +46,26 @@ def stub_response(body, code = 200) @request.send(:setup_raw_request) @request.instance_variable_get(:@raw_request)['authorization'].should_not be_nil end + + context "when setting timeout" do + it "does nothing if the timeout option is a string" do + http = mock("http", :null_object => true) + http.should_not_receive(:open_timeout=) + http.should_not_receive(:read_timeout=) + Net::HTTP.stub(:new => http) + + request = HTTParty::Request.new(Net::HTTP::Get, 'https://foobar.com', {:timeout => "five seconds"}) + request.send(:http) + end + + it "sets the timeout to 5 seconds" do + @request.options[:timeout] = 5 + @request.send(:http).open_timeout.should == 5 + @request.send(:http).read_timeout.should == 5 + end + end end - + describe '#format_from_mimetype' do it 'should handle text/xml' do ["text/xml", "text/xml; charset=iso8859-1"].each do |ct| diff --git a/spec/httparty_spec.rb b/spec/httparty_spec.rb index 362d6fe0..65d1d22c 100644 --- a/spec/httparty_spec.rb +++ b/spec/httparty_spec.rb @@ -1,11 +1,5 @@ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper')) -class CustomParser - def self.parse(body) - return {:sexy => true} - end -end - describe HTTParty do before(:each) do @klass = Class.new @@ -57,35 +51,51 @@ def self.parse(body) end describe "headers" do - def expect_header(type, value) + def expect_headers(header={}) HTTParty::Request.should_receive(:new) \ - .with(anything, anything, hash_including({ :headers => { type => value, "cookie" => "" } })) \ + .with(anything, anything, hash_including({ :headers => header })) \ .and_return(mock("mock response", :perform => nil)) end - + it "should default to empty hash" do @klass.headers.should == {} end - + it "should be able to be updated" do init_headers = {:foo => 'bar', :baz => 'spax'} @klass.headers init_headers @klass.headers.should == init_headers end - - describe "when a header is set at the class level" do - before(:each) do - @klass.headers({ 'Content-Type' => 'application/json' }) + + it "uses the class headers when sending a request" do + expect_headers(:foo => 'bar') + @klass.headers(:foo => 'bar') + @klass.get('') + end + + it "overwrites class headers when passing in headers" do + expect_headers(:baz => 'spax') + @klass.headers(:foo => 'bar') + @klass.get('', :headers => {:baz => 'spax'}) + end + + context "with cookies" do + it 'utilizes the class-level cookies' do + expect_headers(:foo => 'bar', 'cookie' => 'type=snickerdoodle') + @klass.headers(:foo => 'bar') + @klass.cookies(:type => 'snickerdoodle') + @klass.get('') end - - it "should include that header in a get request" do - expect_header "Content-Type", "application/json" - @klass.get("") + + it 'adds cookies to the headers' do + expect_headers(:foo => 'bar', 'cookie' => 'type=snickerdoodle') + @klass.headers(:foo => 'bar') + @klass.get('', :cookies => {:type => 'snickerdoodle'}) end - - it "should include that header in a post request" do - expect_header "Content-Type", "application/json" - @klass.post("") + + it 'adds optional cookies to the optional headers' do + expect_headers(:baz => 'spax', 'cookie' => 'type=snickerdoodle') + @klass.get('', :cookies => {:type => 'snickerdoodle'}, :headers => {:baz => 'spax'}) end end end @@ -180,22 +190,6 @@ def second_method end end - describe "parser" do - before(:each) do - @klass.parser Proc.new{ |data| CustomParser.parse(data) } - end - - it "should set parser options" do - @klass.default_options[:parser].class.should == Proc - end - - it "should be able parse response with custom parser" do - stub_http_response_with 'twitter.xml' - custom_parsed_response = @klass.get('http://twitter.com/statuses/public_timeline.xml') - custom_parsed_response[:sexy].should == true - end - end - describe "format" do it "should allow xml" do @klass.format :xml @@ -331,4 +325,4 @@ def second_method result.should == nil end end -end +end \ No newline at end of file diff --git a/spec/spec.opts b/spec/spec.opts index 7113a858..5779d623 100644 --- a/spec/spec.opts +++ b/spec/spec.opts @@ -1,3 +1,2 @@ ---format - progress --colour +--format specdoc diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 1321a8dc..b95aedda 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,13 +1,11 @@ -require 'rubygems' -gem 'rspec', '>= 1.2.8' +require File.join(File.dirname(__FILE__), '..', 'lib', 'httparty') +gem 'rspec', '1.2.8' gem 'fakeweb' -require 'spec' +require 'spec/autorun' require 'fakeweb' FakeWeb.allow_net_connect = false -require File.join(File.dirname(__FILE__), '..', 'lib', 'httparty') - def file_fixture(filename) open(File.join(File.dirname(__FILE__), 'fixtures', "#{filename.to_s}")).read end