jnunemaker-httparty 0.2.8 → 0.2.9
Sign up to get free protection for your applications and to get access to all the features.
- data/History +6 -0
- data/Manifest +13 -0
- data/Rakefile +6 -1
- data/cucumber.yml +1 -0
- data/examples/basic.rb +1 -1
- data/examples/delicious.rb +1 -0
- data/examples/rubyurl.rb +1 -1
- data/features/basic_authentication.feature +20 -0
- data/features/command_line.feature +7 -0
- data/features/deals_with_http_error_codes.feature +26 -0
- data/features/handles_multiple_formats.feature +34 -0
- data/features/steps/env.rb +15 -0
- data/features/steps/httparty_response_steps.rb +26 -0
- data/features/steps/httparty_steps.rb +15 -0
- data/features/steps/mongrel_helper.rb +55 -0
- data/features/steps/remote_service_steps.rb +47 -0
- data/features/supports_redirection.feature +22 -0
- data/httparty.gemspec +4 -4
- data/lib/core_extensions.rb +5 -1
- data/lib/httparty.rb +18 -1
- data/lib/httparty/cookie_hash.rb +9 -0
- data/lib/httparty/request.rb +1 -1
- data/lib/httparty/response.rb +5 -16
- data/lib/httparty/version.rb +1 -1
- data/spec/httparty/cookie_hash_spec.rb +38 -0
- data/spec/httparty/request_spec.rb +58 -75
- data/spec/httparty_spec.rb +107 -42
- metadata +16 -2
data/History
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
== 0.2.9 2009-01-29
|
2
|
+
* 3 minor enhancements
|
3
|
+
* Added a 'headers' accessor to the response with a hash of any HTTP headers. (Don Peterson)
|
4
|
+
* Add support for a ":cookies" option to be used at the class level, or as an option on any individual call. It should be passed a hash, which will be converted to the proper format and added to the request headers when the call is made. (Don Peterson)
|
5
|
+
* Refactored several specs and added a full suite of cucumber features (Don Peterson)
|
6
|
+
|
1
7
|
== 0.2.8 2009-01-28
|
2
8
|
* 1 major fix
|
3
9
|
* fixed major bug with response where it wouldn't iterate or really work at all with parsed responses
|
data/Manifest
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
bin/httparty
|
2
|
+
cucumber.yml
|
2
3
|
examples/aaws.rb
|
3
4
|
examples/basic.rb
|
4
5
|
examples/delicious.rb
|
@@ -6,9 +7,20 @@ examples/google.rb
|
|
6
7
|
examples/rubyurl.rb
|
7
8
|
examples/twitter.rb
|
8
9
|
examples/whoismyrep.rb
|
10
|
+
features/basic_authentication.feature
|
11
|
+
features/command_line.feature
|
12
|
+
features/deals_with_http_error_codes.feature
|
13
|
+
features/handles_multiple_formats.feature
|
14
|
+
features/steps/env.rb
|
15
|
+
features/steps/httparty_response_steps.rb
|
16
|
+
features/steps/httparty_steps.rb
|
17
|
+
features/steps/mongrel_helper.rb
|
18
|
+
features/steps/remote_service_steps.rb
|
19
|
+
features/supports_redirection.feature
|
9
20
|
History
|
10
21
|
httparty.gemspec
|
11
22
|
lib/core_extensions.rb
|
23
|
+
lib/httparty/cookie_hash.rb
|
12
24
|
lib/httparty/exceptions.rb
|
13
25
|
lib/httparty/request.rb
|
14
26
|
lib/httparty/response.rb
|
@@ -27,6 +39,7 @@ spec/fixtures/google.html
|
|
27
39
|
spec/fixtures/twitter.json
|
28
40
|
spec/fixtures/twitter.xml
|
29
41
|
spec/fixtures/undefined_method_add_node_for_nil.xml
|
42
|
+
spec/httparty/cookie_hash_spec.rb
|
30
43
|
spec/httparty/request_spec.rb
|
31
44
|
spec/httparty_spec.rb
|
32
45
|
spec/spec.opts
|
data/Rakefile
CHANGED
@@ -6,6 +6,7 @@ require 'rake'
|
|
6
6
|
require 'echoe'
|
7
7
|
require 'spec/rake/spectask'
|
8
8
|
require "lib/#{ProjectName}/version"
|
9
|
+
require 'cucumber/rake/task'
|
9
10
|
|
10
11
|
Echoe.new(ProjectName, HTTParty::Version) do |p|
|
11
12
|
p.description = "Makes http fun! Also, makes consuming restful web services dead easy."
|
@@ -40,4 +41,8 @@ Rake::Task[:default].prerequisites.clear
|
|
40
41
|
task :default => :spec
|
41
42
|
Spec::Rake::SpecTask.new do |t|
|
42
43
|
t.spec_files = FileList["spec/**/*_spec.rb"]
|
43
|
-
end
|
44
|
+
end
|
45
|
+
|
46
|
+
Cucumber::Rake::Task.new(:features) do |t|
|
47
|
+
t.cucumber_opts = "--format pretty"
|
48
|
+
end
|
data/cucumber.yml
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
default: features
|
data/examples/basic.rb
CHANGED
@@ -4,7 +4,7 @@ require 'pp'
|
|
4
4
|
|
5
5
|
# You can also use post, put, delete in the same fashion
|
6
6
|
response = HTTParty.get('http://twitter.com/statuses/public_timeline.json')
|
7
|
-
|
7
|
+
puts response.body, response.code, response.headers.inspect
|
8
8
|
|
9
9
|
response.each do |item|
|
10
10
|
puts item['user']['screen_name']
|
data/examples/delicious.rb
CHANGED
data/examples/rubyurl.rb
CHANGED
@@ -0,0 +1,20 @@
|
|
1
|
+
Feature: Basic Authentication
|
2
|
+
|
3
|
+
As a developer
|
4
|
+
I want to be able to use a service that requires Basic Authentication
|
5
|
+
Because that is not an uncommon requirement
|
6
|
+
|
7
|
+
Scenario: Passing no credentials to a page requiring Basic Authentication
|
8
|
+
Given a restricted page at '/protected.html'
|
9
|
+
When I call HTTParty#get with '/protected.html'
|
10
|
+
Then it should return a response with a 401 response code
|
11
|
+
|
12
|
+
Scenario: Passing proper credentials to a page requiring Basic Authentication
|
13
|
+
Given a remote service that returns 'Authenticated Page'
|
14
|
+
And that service is accessed at the path '/protected.html'
|
15
|
+
And that service is protected by Basic Authentication
|
16
|
+
And that service requires the username 'jcash' with the password 'maninblack'
|
17
|
+
When I call HTTParty#get with '/protected.html' and a basic_auth hash:
|
18
|
+
| username | password |
|
19
|
+
| jcash | maninblack |
|
20
|
+
Then the return value should match 'Authenticated Page'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
Feature: Deals with HTTP error codes
|
2
|
+
|
3
|
+
As a developer
|
4
|
+
I want to be informed of non-successful responses
|
5
|
+
Because sometimes thing explode
|
6
|
+
And I should probably know what happened
|
7
|
+
|
8
|
+
Scenario: A response of '404 - Not Found'
|
9
|
+
Given a remote service that returns a 404 status code
|
10
|
+
And that service is accessed at the path '/service.html'
|
11
|
+
When I call HTTParty#get with '/service.html'
|
12
|
+
Then it should return a response with a 404 response code
|
13
|
+
|
14
|
+
Scenario: A response of '500 - Internal Server Error'
|
15
|
+
Given a remote service that returns a 500 status code
|
16
|
+
And that service is accessed at the path '/service.html'
|
17
|
+
When I call HTTParty#get with '/service.html'
|
18
|
+
Then it should return a response with a 500 response code
|
19
|
+
|
20
|
+
Scenario: A non-successful response where I need the body
|
21
|
+
Given a remote service that returns a 400 status code
|
22
|
+
And the response from the service has a body of 'Bad response'
|
23
|
+
And that service is accessed at the path '/service.html'
|
24
|
+
When I call HTTParty#get with '/service.html'
|
25
|
+
Then it should return a response with a 400 response code
|
26
|
+
And the return value should match 'Bad response'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
Feature: Handles Multiple Formats
|
2
|
+
|
3
|
+
As a developer
|
4
|
+
I want to be able to consume remote services of many different formats
|
5
|
+
And I want those formats to be automatically detected and handled
|
6
|
+
Because web services take many forms
|
7
|
+
And I don't want to have to do any extra work
|
8
|
+
|
9
|
+
Scenario: An HTML service
|
10
|
+
Given a remote service that returns '<h1>Some HTML</h1>'
|
11
|
+
And that service is accessed at the path '/service.html'
|
12
|
+
And the response from the service has a Content-Type of 'text/html'
|
13
|
+
When I call HTTParty#get with '/service.html'
|
14
|
+
Then it should return a String
|
15
|
+
And the return value should match '<h1>Some HTML</h1>'
|
16
|
+
|
17
|
+
Scenario: A JSON service
|
18
|
+
Given a remote service that returns '{ "jennings": "waylon", "cash": "johnny" }'
|
19
|
+
And that service is accessed at the path '/service.json'
|
20
|
+
And the response from the service has a Content-Type of 'application/json'
|
21
|
+
When I call HTTParty#get with '/service.json'
|
22
|
+
Then it should return a Hash equaling:
|
23
|
+
| key | value |
|
24
|
+
| jennings | waylon |
|
25
|
+
| cash | johnny |
|
26
|
+
|
27
|
+
Scenario: An XML Service
|
28
|
+
Given a remote service that returns '<singer>waylon jennings</singer>'
|
29
|
+
And that service is accessed at the path '/service.xml'
|
30
|
+
And the response from the service has a Content-Type of 'text/xml'
|
31
|
+
When I call HTTParty#get with '/service.xml'
|
32
|
+
Then it should return a Hash equaling:
|
33
|
+
| key | value |
|
34
|
+
| singer | waylon jennings |
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'mongrel'
|
2
|
+
require 'activesupport'
|
3
|
+
require 'lib/httparty'
|
4
|
+
require 'spec/expectations'
|
5
|
+
|
6
|
+
Before do
|
7
|
+
port = ENV["HTTPARTY_PORT"] || 31981
|
8
|
+
@host_and_port = "0.0.0.0:#{port}"
|
9
|
+
@server = Mongrel::HttpServer.new("0.0.0.0", port)
|
10
|
+
@server.run
|
11
|
+
end
|
12
|
+
|
13
|
+
After do
|
14
|
+
@server.stop
|
15
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
Then /it should return an? (\w+)$/ do |class_string|
|
2
|
+
@response_from_httparty.should be_an_instance_of(class_string.constantize)
|
3
|
+
end
|
4
|
+
|
5
|
+
Then /the return value should match '(.*)'/ do |expected_text|
|
6
|
+
@response_from_httparty.should eql(expected_text)
|
7
|
+
end
|
8
|
+
|
9
|
+
Then /it should return a Hash equaling:/ do |hash_table|
|
10
|
+
@response_from_httparty.should be_an_instance_of(Hash)
|
11
|
+
@response_from_httparty.keys.length.should eql(hash_table.rows.length)
|
12
|
+
hash_table.hashes.each do |pair|
|
13
|
+
key, value = pair["key"], pair["value"]
|
14
|
+
@response_from_httparty.keys.should include(key)
|
15
|
+
@response_from_httparty[key].should eql(value)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Then /it should return a response with a (\d+) response code/ do |code|
|
20
|
+
@response_from_httparty.code.should eql(code)
|
21
|
+
end
|
22
|
+
|
23
|
+
Then /it should raise an HTTParty::RedirectionTooDeep exception/ do
|
24
|
+
@exception_from_httparty.should_not be_nil
|
25
|
+
@exception_from_httparty.class.should eql(HTTParty::RedirectionTooDeep)
|
26
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
When /I call HTTParty#get with '(.*)'$/ do |url|
|
2
|
+
begin
|
3
|
+
@response_from_httparty = HTTParty.get("http://#{@host_and_port}#{url}")
|
4
|
+
rescue HTTParty::RedirectionTooDeep => e
|
5
|
+
@exception_from_httparty = e
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
When /I call HTTParty#get with '(.*)' and a basic_auth hash:/ do |url, auth_table|
|
10
|
+
h = auth_table.hashes.first
|
11
|
+
@response_from_httparty = HTTParty.get(
|
12
|
+
"http://#{@host_and_port}#{url}",
|
13
|
+
:basic_auth => { :username => h["username"], :password => h["password"] }
|
14
|
+
)
|
15
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
def basic_mongrel_handler
|
2
|
+
Class.new(Mongrel::HttpHandler) do
|
3
|
+
attr_writer :content_type, :response_body, :response_code
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@content_type = "text/html"
|
7
|
+
@response_body = ""
|
8
|
+
@response_code = 200
|
9
|
+
@custom_headers = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def process(request, response)
|
13
|
+
reply_with(response, @response_code, @response_body)
|
14
|
+
end
|
15
|
+
|
16
|
+
def reply_with(response, code, response_body)
|
17
|
+
response.start(code) do |head, body|
|
18
|
+
head["Content-Type"] = @content_type
|
19
|
+
@custom_headers.each { |k,v| head[k] = v }
|
20
|
+
body.write(response_body)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def new_mongrel_handler
|
27
|
+
basic_mongrel_handler.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_basic_authentication_to(handler)
|
31
|
+
m = Module.new do
|
32
|
+
attr_writer :username, :password
|
33
|
+
|
34
|
+
def self.extended(base)
|
35
|
+
base.instance_eval { @custom_headers["WWW-Authenticate"] = 'Basic Realm="Super Secret Page"' }
|
36
|
+
base.class_eval { alias_method_chain :process, :basic_authentication }
|
37
|
+
end
|
38
|
+
|
39
|
+
def process_with_basic_authentication(request, response)
|
40
|
+
if authorized?(request) then process_without_basic_authentication(request, response)
|
41
|
+
else reply_with(response, 401, "Incorrect. You have 20 seconds to comply.")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def authorized?(request)
|
46
|
+
request.params["HTTP_AUTHORIZATION"] == "Basic " + Base64.encode64("#{@username}:#{@password}").strip
|
47
|
+
end
|
48
|
+
end
|
49
|
+
handler.extend(m)
|
50
|
+
end
|
51
|
+
|
52
|
+
def new_mongrel_redirector(target_url, relative_path = false)
|
53
|
+
target_url = "http://#{@host_and_port}#{target_url}" unless relative_path
|
54
|
+
Mongrel::RedirectHandler.new(target_url)
|
55
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
Given /a remote service that returns '(.*)'/ do |response_body|
|
2
|
+
@handler = new_mongrel_handler
|
3
|
+
Given "the response from the service has a body of '#{response_body}'"
|
4
|
+
end
|
5
|
+
|
6
|
+
Given /a remote service that returns a (\d+) status code/ do |code|
|
7
|
+
@handler = new_mongrel_handler
|
8
|
+
@handler.response_code = code
|
9
|
+
end
|
10
|
+
|
11
|
+
Given /that service is accessed at the path '(.*)'/ do |path|
|
12
|
+
@server.register(path, @handler)
|
13
|
+
end
|
14
|
+
|
15
|
+
Given /the response from the service has a Content-Type of '(.*)'/ do |content_type|
|
16
|
+
@handler.content_type = content_type
|
17
|
+
end
|
18
|
+
|
19
|
+
Given /the response from the service has a body of '(.*)'/ do |response_body|
|
20
|
+
@handler.response_body = response_body
|
21
|
+
end
|
22
|
+
|
23
|
+
Given /the url '(.*)' redirects to '(.*)'/ do |redirection_url, target_url|
|
24
|
+
@server.register redirection_url, new_mongrel_redirector(target_url)
|
25
|
+
end
|
26
|
+
|
27
|
+
Given /that service is protected by Basic Authentication/ do
|
28
|
+
add_basic_authentication_to @handler
|
29
|
+
end
|
30
|
+
|
31
|
+
Given /that service requires the username '(.*)' with the password '(.*)'/ do |username, password|
|
32
|
+
@handler.username = username
|
33
|
+
@handler.password = password
|
34
|
+
end
|
35
|
+
|
36
|
+
Given /a restricted page at '(.*)'/ do |url|
|
37
|
+
Given "a remote service that returns 'A response I will never see'"
|
38
|
+
And "that service is accessed at the path '#{url}'"
|
39
|
+
And "that service is protected by Basic Authentication"
|
40
|
+
And "that service requires the username 'something' with the password 'secret'"
|
41
|
+
end
|
42
|
+
|
43
|
+
# This joins the server thread, and halts cucumber, so you can actually hit the
|
44
|
+
# server with a browser. Runs until you kill it with Ctrl-c
|
45
|
+
Given /I want to hit this in a browser/ do
|
46
|
+
@server.acceptor.join
|
47
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
Feature: Supports Redirection
|
2
|
+
|
3
|
+
As a developer
|
4
|
+
I want to work with services that may redirect me
|
5
|
+
And I want it to follow a reasonable number of redirects
|
6
|
+
Because sometimes web services do that
|
7
|
+
|
8
|
+
Scenario: A service that redirects once
|
9
|
+
Given a remote service that returns 'Service Response'
|
10
|
+
And that service is accessed at the path '/service.html'
|
11
|
+
And the url '/redirector.html' redirects to '/service.html'
|
12
|
+
When I call HTTParty#get with '/redirector.html'
|
13
|
+
Then the return value should match 'Service Response'
|
14
|
+
|
15
|
+
# TODO: Look in to why this actually fails...
|
16
|
+
Scenario: A service that redirects to a relative URL
|
17
|
+
|
18
|
+
Scenario: A service that redirects infinitely
|
19
|
+
Given the url '/first.html' redirects to '/second.html'
|
20
|
+
And the url '/second.html' redirects to '/first.html'
|
21
|
+
When I call HTTParty#get with '/first.html'
|
22
|
+
Then it should raise an HTTParty::RedirectionTooDeep exception
|
data/httparty.gemspec
CHANGED
@@ -2,17 +2,17 @@
|
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
4
|
s.name = %q{httparty}
|
5
|
-
s.version = "0.2.
|
5
|
+
s.version = "0.2.9"
|
6
6
|
|
7
7
|
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
8
8
|
s.authors = ["John Nunemaker"]
|
9
|
-
s.date = %q{2009-01-
|
9
|
+
s.date = %q{2009-01-29}
|
10
10
|
s.default_executable = %q{httparty}
|
11
11
|
s.description = %q{Makes http fun! Also, makes consuming restful web services dead easy.}
|
12
12
|
s.email = %q{nunemaker@gmail.com}
|
13
13
|
s.executables = ["httparty"]
|
14
|
-
s.extra_rdoc_files = ["bin/httparty", "lib/core_extensions.rb", "lib/httparty/exceptions.rb", "lib/httparty/request.rb", "lib/httparty/response.rb", "lib/httparty/version.rb", "lib/httparty.rb", "lib/module_level_inheritable_attributes.rb", "README"]
|
15
|
-
s.files = ["bin/httparty", "examples/aaws.rb", "examples/basic.rb", "examples/delicious.rb", "examples/google.rb", "examples/rubyurl.rb", "examples/twitter.rb", "examples/whoismyrep.rb", "History", "httparty.gemspec", "lib/core_extensions.rb", "lib/httparty/exceptions.rb", "lib/httparty/request.rb", "lib/httparty/response.rb", "lib/httparty/version.rb", "lib/httparty.rb", "lib/module_level_inheritable_attributes.rb", "Manifest", "MIT-LICENSE", "Rakefile", "README", "setup.rb", "spec/as_buggery_spec.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/request_spec.rb", "spec/httparty_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "website/css/common.css", "website/index.html"]
|
14
|
+
s.extra_rdoc_files = ["bin/httparty", "lib/core_extensions.rb", "lib/httparty/cookie_hash.rb", "lib/httparty/exceptions.rb", "lib/httparty/request.rb", "lib/httparty/response.rb", "lib/httparty/version.rb", "lib/httparty.rb", "lib/module_level_inheritable_attributes.rb", "README"]
|
15
|
+
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/core_extensions.rb", "lib/httparty/cookie_hash.rb", "lib/httparty/exceptions.rb", "lib/httparty/request.rb", "lib/httparty/response.rb", "lib/httparty/version.rb", "lib/httparty.rb", "lib/module_level_inheritable_attributes.rb", "Manifest", "MIT-LICENSE", "Rakefile", "README", "setup.rb", "spec/as_buggery_spec.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_spec.rb", "spec/spec.opts", "spec/spec_helper.rb", "website/css/common.css", "website/index.html"]
|
16
16
|
s.has_rdoc = true
|
17
17
|
s.homepage = %q{http://httparty.rubyforge.org}
|
18
18
|
s.post_install_message = %q{When you HTTParty, you must party hard!}
|
data/lib/core_extensions.rb
CHANGED
data/lib/httparty.rb
CHANGED
@@ -58,6 +58,12 @@ module HTTParty
|
|
58
58
|
default_options[:headers] ||= {}
|
59
59
|
default_options[:headers].merge!(h)
|
60
60
|
end
|
61
|
+
|
62
|
+
def cookies(h={})
|
63
|
+
raise ArgumentError, 'Cookies must be a hash' unless h.is_a?(Hash)
|
64
|
+
default_options[:cookies] ||= CookieHash.new
|
65
|
+
default_options[:cookies].add_cookies(h)
|
66
|
+
end
|
61
67
|
|
62
68
|
def format(f)
|
63
69
|
raise UnsupportedFormat, "Must be one of: #{AllowedFormats.values.join(', ')}" unless AllowedFormats.value?(f)
|
@@ -82,8 +88,18 @@ module HTTParty
|
|
82
88
|
|
83
89
|
private
|
84
90
|
def perform_request(http_method, path, options) #:nodoc:
|
91
|
+
process_cookies(options)
|
85
92
|
Request.new(http_method, path, default_options.dup.merge(options)).perform
|
86
93
|
end
|
94
|
+
|
95
|
+
def process_cookies(options) #:nodoc:
|
96
|
+
return unless options[:cookies] || default_options[:cookies]
|
97
|
+
options[:headers] ||= {}
|
98
|
+
options[:headers]["cookie"] = cookies(options[:cookies] || {}).to_cookie_string
|
99
|
+
|
100
|
+
default_options.delete(:cookies)
|
101
|
+
options.delete(:cookies)
|
102
|
+
end
|
87
103
|
end
|
88
104
|
|
89
105
|
def self.normalize_base_uri(url) #:nodoc:
|
@@ -119,4 +135,5 @@ end
|
|
119
135
|
|
120
136
|
require 'httparty/exceptions'
|
121
137
|
require 'httparty/request'
|
122
|
-
require 'httparty/response'
|
138
|
+
require 'httparty/response'
|
139
|
+
require 'httparty/cookie_hash'
|
data/lib/httparty/request.rb
CHANGED
data/lib/httparty/response.rb
CHANGED
@@ -1,27 +1,16 @@
|
|
1
1
|
module HTTParty
|
2
|
-
class Response
|
3
|
-
attr_accessor :body, :code
|
2
|
+
class Response < BlankSlate
|
3
|
+
attr_accessor :body, :code, :headers
|
4
4
|
|
5
|
-
def initialize(delegate, body, code)
|
5
|
+
def initialize(delegate, body, code, headers)
|
6
6
|
@delegate = delegate
|
7
7
|
@body = body
|
8
8
|
@code = code
|
9
|
+
@headers = headers
|
9
10
|
end
|
10
11
|
|
11
12
|
def method_missing(name, *args, &block)
|
12
13
|
@delegate.send(name, *args, &block)
|
13
14
|
end
|
14
|
-
|
15
|
-
def ==(other)
|
16
|
-
@delegate == other
|
17
|
-
end
|
18
|
-
|
19
|
-
def nil?
|
20
|
-
@delegate.nil?
|
21
|
-
end
|
22
|
-
|
23
|
-
def pretty_print(q)
|
24
|
-
@delegate.pretty_print(q)
|
25
|
-
end
|
26
15
|
end
|
27
|
-
end
|
16
|
+
end
|
data/lib/httparty/version.rb
CHANGED
@@ -0,0 +1,38 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), '../spec_helper')
|
2
|
+
|
3
|
+
describe HTTParty::CookieHash do
|
4
|
+
before(:each) do
|
5
|
+
@cookie_hash = HTTParty::CookieHash.new
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "#add_cookies" do
|
9
|
+
it "should add new key/value pairs to the hash" do
|
10
|
+
@cookie_hash.add_cookies(:foo => "bar")
|
11
|
+
@cookie_hash.add_cookies(:rofl => "copter")
|
12
|
+
@cookie_hash.length.should eql(2)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should overwrite any existing key" do
|
16
|
+
@cookie_hash.add_cookies(:foo => "bar")
|
17
|
+
@cookie_hash.add_cookies(:foo => "copter")
|
18
|
+
@cookie_hash.length.should eql(1)
|
19
|
+
@cookie_hash[:foo].should eql("copter")
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# The regexen are required because Hashes aren't ordered, so a test against
|
24
|
+
# a hardcoded string was randomly failing.
|
25
|
+
describe "#to_cookie_string" do
|
26
|
+
before(:each) do
|
27
|
+
@cookie_hash.add_cookies(:foo => "bar")
|
28
|
+
@cookie_hash.add_cookies(:rofl => "copter")
|
29
|
+
@s = @cookie_hash.to_cookie_string
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should format the key/value pairs, delimited by semi-colons" do
|
33
|
+
@s.should match(/foo=bar/)
|
34
|
+
@s.should match(/rofl=copter/)
|
35
|
+
@s.should match(/^\w+=\w+; \w+=\w+$/)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -1,6 +1,20 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), '..', 'spec_helper')
|
2
2
|
|
3
3
|
describe HTTParty::Request do
|
4
|
+
def stub_response(body, code = 200)
|
5
|
+
unless @http
|
6
|
+
@http = Net::HTTP.new('localhost', 80)
|
7
|
+
@request.stub!(:http).and_return(@http)
|
8
|
+
@request.stub!(:uri).and_return(URI.parse("http://foo.com/foobar"))
|
9
|
+
end
|
10
|
+
|
11
|
+
response = Net::HTTPResponse::CODE_TO_OBJ[code.to_s].new("1.1", code, body)
|
12
|
+
response.stub!(:body).and_return(body)
|
13
|
+
|
14
|
+
@http.stub!(:request).and_return(response)
|
15
|
+
response
|
16
|
+
end
|
17
|
+
|
4
18
|
before do
|
5
19
|
@request = HTTParty::Request.new(Net::HTTP::Get, 'http://api.foo.com/v1', :format => :xml)
|
6
20
|
end
|
@@ -79,14 +93,19 @@ describe HTTParty::Request do
|
|
79
93
|
@request.options[:format] = :json
|
80
94
|
@request.send(:parse_response, json).should == {'books' => {'book' => {'id' => '1234', 'name' => 'Foo Bar!'}}}
|
81
95
|
end
|
96
|
+
|
97
|
+
it "should include any HTTP headers in the returned response" do
|
98
|
+
@request.options[:format] = :html
|
99
|
+
response = stub_response "Content"
|
100
|
+
response.initialize_http_header("key" => "value")
|
101
|
+
|
102
|
+
@request.perform.headers.should == { "key" => ["value"] }
|
103
|
+
end
|
82
104
|
|
83
105
|
describe 'with non-200 responses' do
|
84
106
|
|
85
107
|
it 'should return a valid object for 4xx response' do
|
86
|
-
|
87
|
-
http_response.stub!(:body).and_return('<foo><bar>yes</bar></foo>')
|
88
|
-
|
89
|
-
@request.should_receive(:get_response).and_return(http_response)
|
108
|
+
stub_response '<foo><bar>yes</bar></foo>', 401
|
90
109
|
resp = @request.perform
|
91
110
|
resp.code.should == 401
|
92
111
|
resp.body.should == "<foo><bar>yes</bar></foo>"
|
@@ -94,10 +113,7 @@ describe HTTParty::Request do
|
|
94
113
|
end
|
95
114
|
|
96
115
|
it 'should return a valid object for 5xx response' do
|
97
|
-
|
98
|
-
http_response.stub!(:body).and_return('<foo><bar>error</bar></foo>')
|
99
|
-
|
100
|
-
@request.should_receive(:get_response).and_return(http_response)
|
116
|
+
stub_response '<foo><bar>error</bar></foo>', 500
|
101
117
|
resp = @request.perform
|
102
118
|
resp.code.should == 500
|
103
119
|
resp.body.should == "<foo><bar>error</bar></foo>"
|
@@ -108,92 +124,59 @@ describe HTTParty::Request do
|
|
108
124
|
end
|
109
125
|
|
110
126
|
it "should not attempt to parse empty responses" do
|
111
|
-
|
112
|
-
@request.stub!(:http).and_return(http)
|
113
|
-
response = Net::HTTPNoContent.new("1.1", 204, "No content for you")
|
114
|
-
response.stub!(:body).and_return(nil)
|
115
|
-
http.stub!(:request).and_return(response)
|
116
|
-
|
117
|
-
@request.options[:format] = :xml
|
118
|
-
@request.perform.should be_nil
|
127
|
+
stub_response "", 204
|
119
128
|
|
120
|
-
|
129
|
+
@request.options[:format] = :xml
|
121
130
|
@request.perform.should be_nil
|
122
131
|
end
|
123
132
|
|
124
133
|
it "should not fail for missing mime type" do
|
125
|
-
|
126
|
-
@request.stub!(:http).and_return(http)
|
127
|
-
|
128
|
-
response = Net::HTTPOK.new("1.1", 200, "Content for you")
|
129
|
-
response.stub!(:[]).with('content-type').and_return(nil)
|
130
|
-
response.stub!(:body).and_return('Content for you')
|
131
|
-
|
132
|
-
http.stub!(:request).and_return(response)
|
133
|
-
|
134
|
+
stub_response "Content for you"
|
134
135
|
@request.options[:format] = :html
|
135
136
|
@request.perform.should == 'Content for you'
|
136
137
|
end
|
137
138
|
|
138
|
-
describe "
|
139
|
-
|
140
|
-
@
|
141
|
-
@request.stub!(:http).and_return(@http)
|
142
|
-
@request.stub!(:uri).and_return(URI.parse("http://foo.com/foobar"))
|
143
|
-
@redirect = Net::HTTPFound.new("1.1", 302, "")
|
139
|
+
describe "a request that redirects" do
|
140
|
+
before(:each) do
|
141
|
+
@redirect = stub_response("", 302)
|
144
142
|
@redirect['location'] = '/foo'
|
145
|
-
end
|
146
|
-
|
147
|
-
def setup_ok_response
|
148
|
-
@ok = Net::HTTPOK.new("1.1", 200, "Content for you")
|
149
|
-
@ok.stub!(:body).and_return('<hash><foo>bar</foo></hash>')
|
150
|
-
@http.should_receive(:request).and_return(@redirect, @ok)
|
151
|
-
@request.options[:format] = :xml
|
152
|
-
end
|
153
143
|
|
154
|
-
|
155
|
-
@http.stub!(:request).and_return(@redirect)
|
144
|
+
@ok = stub_response('<hash><foo>bar</foo></hash>', 200)
|
156
145
|
end
|
157
146
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
def setup_infinite_redirect
|
164
|
-
setup_redirect
|
165
|
-
setup_redirect_response
|
166
|
-
end
|
147
|
+
describe "once" do
|
148
|
+
before(:each) do
|
149
|
+
@http.stub!(:request).and_return(@redirect, @ok)
|
150
|
+
end
|
167
151
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
end
|
152
|
+
it "should be handled by GET transparently" do
|
153
|
+
@request.perform.should == {"hash" => {"foo" => "bar"}}
|
154
|
+
end
|
172
155
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
end
|
156
|
+
it "should be handled by POST transparently" do
|
157
|
+
@request.http_method = Net::HTTP::Post
|
158
|
+
@request.perform.should == {"hash" => {"foo" => "bar"}}
|
159
|
+
end
|
178
160
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
end
|
161
|
+
it "should be handled by DELETE transparently" do
|
162
|
+
@request.http_method = Net::HTTP::Delete
|
163
|
+
@request.perform.should == {"hash" => {"foo" => "bar"}}
|
164
|
+
end
|
184
165
|
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
166
|
+
it "should be handled by PUT transparently" do
|
167
|
+
@request.http_method = Net::HTTP::Put
|
168
|
+
@request.perform.should == {"hash" => {"foo" => "bar"}}
|
169
|
+
end
|
189
170
|
end
|
190
171
|
|
191
|
-
|
192
|
-
|
172
|
+
describe "infinitely" do
|
173
|
+
before(:each) do
|
174
|
+
@http.stub!(:request).and_return(@redirect)
|
175
|
+
end
|
193
176
|
|
194
|
-
|
195
|
-
@request.perform
|
196
|
-
end
|
177
|
+
it "should raise an exception" do
|
178
|
+
lambda { @request.perform }.should raise_error(HTTParty::RedirectionTooDeep)
|
179
|
+
end
|
197
180
|
end
|
198
181
|
end
|
199
182
|
end
|
@@ -204,4 +187,4 @@ describe HTTParty::Request, "with POST http method" do
|
|
204
187
|
HTTParty::Request.new(Net::HTTP::Post, 'http://api.foo.com/v1', :format => :xml, :query => 'astring').perform
|
205
188
|
}.should raise_error(ArgumentError)
|
206
189
|
end
|
207
|
-
end
|
190
|
+
end
|
data/spec/httparty_spec.rb
CHANGED
@@ -1,36 +1,23 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
2
|
|
3
|
-
class Foo
|
4
|
-
include HTTParty
|
5
|
-
base_uri 'api.foo.com/v1'
|
6
|
-
end
|
7
|
-
|
8
|
-
class GRest
|
9
|
-
include HTTParty
|
10
|
-
base_uri "grest.com"
|
11
|
-
default_params :one => 'two'
|
12
|
-
end
|
13
|
-
|
14
|
-
class HRest
|
15
|
-
include HTTParty
|
16
|
-
base_uri "hrest.com"
|
17
|
-
default_params :two => 'three'
|
18
|
-
end
|
19
|
-
|
20
3
|
describe HTTParty do
|
21
|
-
|
4
|
+
before(:each) do
|
5
|
+
@klass = Class.new
|
6
|
+
@klass.instance_eval { include HTTParty }
|
7
|
+
end
|
8
|
+
|
22
9
|
describe "base uri" do
|
23
|
-
before do
|
24
|
-
|
10
|
+
before(:each) do
|
11
|
+
@klass.base_uri('api.foo.com/v1')
|
25
12
|
end
|
26
13
|
|
27
14
|
it "should have reader" do
|
28
|
-
|
15
|
+
@klass.base_uri.should == 'http://api.foo.com/v1'
|
29
16
|
end
|
30
17
|
|
31
18
|
it 'should have writer' do
|
32
|
-
|
33
|
-
|
19
|
+
@klass.base_uri('http://api.foobar.com')
|
20
|
+
@klass.base_uri.should == 'http://api.foobar.com'
|
34
21
|
end
|
35
22
|
end
|
36
23
|
|
@@ -53,49 +40,113 @@ describe HTTParty do
|
|
53
40
|
|
54
41
|
describe "headers" do
|
55
42
|
it "should default to empty hash" do
|
56
|
-
|
43
|
+
@klass.headers.should == {}
|
57
44
|
end
|
58
45
|
|
59
46
|
it "should be able to be updated" do
|
60
47
|
init_headers = {:foo => 'bar', :baz => 'spax'}
|
61
|
-
|
62
|
-
|
48
|
+
@klass.headers init_headers
|
49
|
+
@klass.headers.should == init_headers
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "cookies" do
|
54
|
+
def expect_cookie_header(s)
|
55
|
+
HTTParty::Request.should_receive(:new) \
|
56
|
+
.with(anything, anything, hash_including({ :headers => { "cookie" => s } })) \
|
57
|
+
.and_return(mock("mock response", :perform => nil))
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should not be in the headers by default" do
|
61
|
+
HTTParty::Request.stub!(:new).and_return(stub(nil, :perform => nil))
|
62
|
+
@klass.get("")
|
63
|
+
@klass.headers.keys.should_not include("cookie")
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should raise an ArgumentError if passed a non-Hash" do
|
67
|
+
lambda do
|
68
|
+
@klass.cookies("nonsense")
|
69
|
+
end.should raise_error(ArgumentError)
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should allow a cookie to be specified with a one-off request" do
|
73
|
+
expect_cookie_header "type=snickerdoodle"
|
74
|
+
@klass.get("", :cookies => { :type => "snickerdoodle" })
|
75
|
+
end
|
76
|
+
|
77
|
+
describe "when a cookie is set at the class level" do
|
78
|
+
before(:each) do
|
79
|
+
@klass.cookies({ :type => "snickerdoodle" })
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should include that cookie in the request" do
|
83
|
+
expect_cookie_header "type=snickerdoodle"
|
84
|
+
@klass.get("")
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should allow the class defaults to be overridden" do
|
88
|
+
expect_cookie_header "type=chocolate_chip"
|
89
|
+
|
90
|
+
@klass.get("", :cookies => { :type => "chocolate_chip" })
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "in a class with multiple methods that use different cookies" do
|
95
|
+
before(:each) do
|
96
|
+
@klass.instance_eval do
|
97
|
+
def first_method
|
98
|
+
get("first_method", :cookies => { :first_method_cookie => "foo" })
|
99
|
+
end
|
100
|
+
|
101
|
+
def second_method
|
102
|
+
get("second_method", :cookies => { :second_method_cookie => "foo" })
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should not allow cookies used in one method to carry over into other methods" do
|
108
|
+
expect_cookie_header "first_method_cookie=foo"
|
109
|
+
@klass.first_method
|
110
|
+
|
111
|
+
expect_cookie_header "second_method_cookie=foo"
|
112
|
+
@klass.second_method
|
113
|
+
end
|
63
114
|
end
|
64
115
|
end
|
65
116
|
|
66
117
|
describe "default params" do
|
67
118
|
it "should default to empty hash" do
|
68
|
-
|
119
|
+
@klass.default_params.should == {}
|
69
120
|
end
|
70
121
|
|
71
122
|
it "should be able to be updated" do
|
72
123
|
new_defaults = {:foo => 'bar', :baz => 'spax'}
|
73
|
-
|
74
|
-
|
124
|
+
@klass.default_params new_defaults
|
125
|
+
@klass.default_params.should == new_defaults
|
75
126
|
end
|
76
127
|
end
|
77
128
|
|
78
129
|
describe "basic http authentication" do
|
79
130
|
it "should work" do
|
80
|
-
|
81
|
-
|
131
|
+
@klass.basic_auth 'foobar', 'secret'
|
132
|
+
@klass.default_options[:basic_auth].should == {:username => 'foobar', :password => 'secret'}
|
82
133
|
end
|
83
134
|
end
|
84
135
|
|
85
136
|
describe "format" do
|
86
137
|
it "should allow xml" do
|
87
|
-
|
88
|
-
|
138
|
+
@klass.format :xml
|
139
|
+
@klass.default_options[:format].should == :xml
|
89
140
|
end
|
90
141
|
|
91
142
|
it "should allow json" do
|
92
|
-
|
93
|
-
|
143
|
+
@klass.format :json
|
144
|
+
@klass.default_options[:format].should == :json
|
94
145
|
end
|
95
146
|
|
96
147
|
it 'should not allow funky format' do
|
97
148
|
lambda do
|
98
|
-
|
149
|
+
@klass.format :foobar
|
99
150
|
end.should raise_error(HTTParty::UnsupportedFormat)
|
100
151
|
end
|
101
152
|
end
|
@@ -104,33 +155,47 @@ describe HTTParty do
|
|
104
155
|
|
105
156
|
it "should fail with redirected GET" do
|
106
157
|
lambda do
|
107
|
-
|
158
|
+
@klass.get('/foo', :no_follow => true)
|
108
159
|
end.should raise_error(HTTParty::RedirectionTooDeep)
|
109
160
|
end
|
110
161
|
|
111
162
|
it "should fail with redirected POST" do
|
112
163
|
lambda do
|
113
|
-
|
164
|
+
@klass.post('/foo', :no_follow => true)
|
114
165
|
end.should raise_error(HTTParty::RedirectionTooDeep)
|
115
166
|
end
|
116
167
|
|
117
168
|
it "should fail with redirected DELETE" do
|
118
169
|
lambda do
|
119
|
-
|
170
|
+
@klass.delete('/foo', :no_follow => true)
|
120
171
|
end.should raise_error(HTTParty::RedirectionTooDeep)
|
121
172
|
end
|
122
173
|
|
123
174
|
it "should fail with redirected PUT" do
|
124
175
|
lambda do
|
125
|
-
|
176
|
+
@klass.put('/foo', :no_follow => true)
|
126
177
|
end.should raise_error(HTTParty::RedirectionTooDeep)
|
127
178
|
end
|
128
179
|
end
|
129
180
|
|
130
181
|
describe "with multiple class definitions" do
|
182
|
+
before(:each) do
|
183
|
+
@klass.instance_eval do
|
184
|
+
base_uri "http://first.com"
|
185
|
+
default_params :one => 1
|
186
|
+
end
|
187
|
+
|
188
|
+
@additional_klass = Class.new
|
189
|
+
@additional_klass.instance_eval do
|
190
|
+
include HTTParty
|
191
|
+
base_uri "http://second.com"
|
192
|
+
default_params :two => 2
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
131
196
|
it "should not run over each others options" do
|
132
|
-
|
133
|
-
|
197
|
+
@klass.default_options.should == { :base_uri => 'http://first.com', :default_params => { :one => 1 } }
|
198
|
+
@additional_klass.default_options.should == { :base_uri => 'http://second.com', :default_params => { :two => 2 } }
|
134
199
|
end
|
135
200
|
end
|
136
201
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: jnunemaker-httparty
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Nunemaker
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-01-
|
12
|
+
date: 2009-01-29 00:00:00 -08:00
|
13
13
|
default_executable: httparty
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -39,6 +39,7 @@ extensions: []
|
|
39
39
|
extra_rdoc_files:
|
40
40
|
- bin/httparty
|
41
41
|
- lib/core_extensions.rb
|
42
|
+
- lib/httparty/cookie_hash.rb
|
42
43
|
- lib/httparty/exceptions.rb
|
43
44
|
- lib/httparty/request.rb
|
44
45
|
- lib/httparty/response.rb
|
@@ -48,6 +49,7 @@ extra_rdoc_files:
|
|
48
49
|
- README
|
49
50
|
files:
|
50
51
|
- bin/httparty
|
52
|
+
- cucumber.yml
|
51
53
|
- examples/aaws.rb
|
52
54
|
- examples/basic.rb
|
53
55
|
- examples/delicious.rb
|
@@ -55,9 +57,20 @@ files:
|
|
55
57
|
- examples/rubyurl.rb
|
56
58
|
- examples/twitter.rb
|
57
59
|
- examples/whoismyrep.rb
|
60
|
+
- features/basic_authentication.feature
|
61
|
+
- features/command_line.feature
|
62
|
+
- features/deals_with_http_error_codes.feature
|
63
|
+
- features/handles_multiple_formats.feature
|
64
|
+
- features/steps/env.rb
|
65
|
+
- features/steps/httparty_response_steps.rb
|
66
|
+
- features/steps/httparty_steps.rb
|
67
|
+
- features/steps/mongrel_helper.rb
|
68
|
+
- features/steps/remote_service_steps.rb
|
69
|
+
- features/supports_redirection.feature
|
58
70
|
- History
|
59
71
|
- httparty.gemspec
|
60
72
|
- lib/core_extensions.rb
|
73
|
+
- lib/httparty/cookie_hash.rb
|
61
74
|
- lib/httparty/exceptions.rb
|
62
75
|
- lib/httparty/request.rb
|
63
76
|
- lib/httparty/response.rb
|
@@ -76,6 +89,7 @@ files:
|
|
76
89
|
- spec/fixtures/twitter.json
|
77
90
|
- spec/fixtures/twitter.xml
|
78
91
|
- spec/fixtures/undefined_method_add_node_for_nil.xml
|
92
|
+
- spec/httparty/cookie_hash_spec.rb
|
79
93
|
- spec/httparty/request_spec.rb
|
80
94
|
- spec/httparty_spec.rb
|
81
95
|
- spec/spec.opts
|