httpotato 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. data/.gitignore +7 -0
  2. data/History +216 -0
  3. data/MIT-LICENSE +20 -0
  4. data/Manifest +47 -0
  5. data/README.rdoc +52 -0
  6. data/Rakefile +90 -0
  7. data/VERSION +1 -0
  8. data/bin/httpotato +108 -0
  9. data/cucumber.yml +1 -0
  10. data/examples/aaws.rb +32 -0
  11. data/examples/basic.rb +11 -0
  12. data/examples/custom_parsers.rb +67 -0
  13. data/examples/delicious.rb +37 -0
  14. data/examples/google.rb +16 -0
  15. data/examples/rubyurl.rb +14 -0
  16. data/examples/twitter.rb +31 -0
  17. data/examples/whoismyrep.rb +10 -0
  18. data/features/basic_authentication.feature +20 -0
  19. data/features/command_line.feature +7 -0
  20. data/features/deals_with_http_error_codes.feature +26 -0
  21. data/features/digest_authentication.feature +20 -0
  22. data/features/handles_compressed_responses.feature +19 -0
  23. data/features/handles_multiple_formats.feature +34 -0
  24. data/features/steps/env.rb +23 -0
  25. data/features/steps/httpotato_response_steps.rb +26 -0
  26. data/features/steps/httpotato_steps.rb +27 -0
  27. data/features/steps/mongrel_helper.rb +94 -0
  28. data/features/steps/remote_service_steps.rb +69 -0
  29. data/features/supports_redirection.feature +22 -0
  30. data/features/supports_timeout_option.feature +13 -0
  31. data/httpotato.gemspec +150 -0
  32. data/lib/httpotato.rb +371 -0
  33. data/lib/httpotato/cookie_hash.rb +22 -0
  34. data/lib/httpotato/core_extensions.rb +31 -0
  35. data/lib/httpotato/exceptions.rb +26 -0
  36. data/lib/httpotato/module_inheritable_attributes.rb +34 -0
  37. data/lib/httpotato/net_digest_auth.rb +35 -0
  38. data/lib/httpotato/parser.rb +146 -0
  39. data/lib/httpotato/request.rb +231 -0
  40. data/lib/httpotato/response.rb +79 -0
  41. data/spec/fixtures/delicious.xml +23 -0
  42. data/spec/fixtures/empty.xml +0 -0
  43. data/spec/fixtures/google.html +3 -0
  44. data/spec/fixtures/ssl/generate.sh +29 -0
  45. data/spec/fixtures/ssl/generated/1fe462c2.0 +15 -0
  46. data/spec/fixtures/ssl/generated/bogushost.crt +13 -0
  47. data/spec/fixtures/ssl/generated/ca.crt +15 -0
  48. data/spec/fixtures/ssl/generated/ca.key +15 -0
  49. data/spec/fixtures/ssl/generated/selfsigned.crt +14 -0
  50. data/spec/fixtures/ssl/generated/server.crt +13 -0
  51. data/spec/fixtures/ssl/generated/server.key +15 -0
  52. data/spec/fixtures/ssl/openssl-exts.cnf +9 -0
  53. data/spec/fixtures/twitter.json +1 -0
  54. data/spec/fixtures/twitter.xml +403 -0
  55. data/spec/fixtures/undefined_method_add_node_for_nil.xml +2 -0
  56. data/spec/httpotato/cookie_hash_spec.rb +71 -0
  57. data/spec/httpotato/parser_spec.rb +155 -0
  58. data/spec/httpotato/request_spec.rb +430 -0
  59. data/spec/httpotato/response_spec.rb +188 -0
  60. data/spec/httpotato/ssl_spec.rb +54 -0
  61. data/spec/httpotato_spec.rb +570 -0
  62. data/spec/spec.opts +3 -0
  63. data/spec/spec_helper.rb +20 -0
  64. data/spec/support/ssl_test_helper.rb +25 -0
  65. data/spec/support/ssl_test_server.rb +69 -0
  66. data/spec/support/stub_response.rb +30 -0
  67. data/test.rb +39 -0
  68. data/website/css/common.css +47 -0
  69. data/website/index.html +66 -0
  70. metadata +260 -0
@@ -0,0 +1,94 @@
1
+ require 'base64'
2
+ class BasicMongrelHandler < Mongrel::HttpHandler
3
+ attr_accessor :content_type, :custom_headers, :response_body, :response_code, :preprocessor, :username, :password
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
+ instance_eval &preprocessor if preprocessor
14
+ reply_with(response, response_code, response_body)
15
+ end
16
+
17
+ def reply_with(response, code, response_body)
18
+ response.start(code) do |head, body|
19
+ head["Content-Type"] = content_type
20
+ custom_headers.each { |k,v| head[k] = v }
21
+ body.write(response_body)
22
+ end
23
+ end
24
+ end
25
+
26
+ class DeflateHandler < BasicMongrelHandler
27
+ def process(request, response)
28
+ response.start do |head, body|
29
+ head['Content-Encoding'] = 'deflate'
30
+ body.write Zlib::Deflate.deflate(response_body)
31
+ end
32
+ end
33
+ end
34
+
35
+ class GzipHandler < BasicMongrelHandler
36
+ def process(request, response)
37
+ response.start do |head, body|
38
+ head['Content-Encoding'] = 'gzip'
39
+ body.write gzip(response_body)
40
+ end
41
+ end
42
+
43
+ protected
44
+
45
+ def gzip(string)
46
+ sio = StringIO.new('', 'r+')
47
+ gz = Zlib::GzipWriter.new sio
48
+ gz.write string
49
+ gz.finish
50
+ sio.rewind
51
+ sio.read
52
+ end
53
+ end
54
+
55
+ module BasicAuthentication
56
+ def self.extended(base)
57
+ base.custom_headers["WWW-Authenticate"] = 'Basic Realm="Super Secret Page"'
58
+ end
59
+
60
+ def process(request, response)
61
+ if authorized?(request)
62
+ super
63
+ else
64
+ reply_with(response, 401, "Incorrect. You have 20 seconds to comply.")
65
+ end
66
+ end
67
+
68
+ def authorized?(request)
69
+ request.params["HTTP_AUTHORIZATION"] == "Basic " + Base64.encode64("#{@username}:#{@password}").strip
70
+ end
71
+ end
72
+
73
+ module DigestAuthentication
74
+ def self.extended(base)
75
+ base.custom_headers["WWW-Authenticate"] = 'Digest realm="testrealm@host.com",qop="auth,auth-int",nonce="nonce",opaque="opaque"'
76
+ end
77
+
78
+ def process(request, response)
79
+ if authorized?(request)
80
+ super
81
+ else
82
+ reply_with(response, 401, "Incorrect. You have 20 seconds to comply.")
83
+ end
84
+ end
85
+
86
+ def authorized?(request)
87
+ request.params["HTTP_AUTHORIZATION"] =~ /Digest.*uri=/
88
+ end
89
+ end
90
+
91
+ def new_mongrel_redirector(target_url, relative_path = false)
92
+ target_url = "http://#{@host_and_port}#{target_url}" unless relative_path
93
+ Mongrel::RedirectHandler.new(target_url)
94
+ end
@@ -0,0 +1,69 @@
1
+ Given /a remote service that returns '(.*)'/ do |response_body|
2
+ @handler = BasicMongrelHandler.new
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 = BasicMongrelHandler.new
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 /^that service takes (\d+) seconds to generate a response$/ do |time|
16
+ @server_response_time = time.to_i
17
+ @handler.preprocessor = lambda { sleep time.to_i }
18
+ end
19
+
20
+ Given /^a remote deflate service$/ do
21
+ @handler = DeflateHandler.new
22
+ end
23
+
24
+ Given /^a remote gzip service$/ do
25
+ @handler = GzipHandler.new
26
+ end
27
+
28
+ Given /the response from the service has a Content-Type of '(.*)'/ do |content_type|
29
+ @handler.content_type = content_type
30
+ end
31
+
32
+ Given /the response from the service has a body of '(.*)'/ do |response_body|
33
+ @handler.response_body = response_body
34
+ end
35
+
36
+ Given /the url '(.*)' redirects to '(.*)'/ do |redirection_url, target_url|
37
+ @server.register redirection_url, new_mongrel_redirector(target_url)
38
+ end
39
+
40
+ Given /that service is protected by Basic Authentication/ do
41
+ @handler.extend BasicAuthentication
42
+ end
43
+
44
+ Given /that service is protected by Digest Authentication/ do
45
+ @handler.extend DigestAuthentication
46
+ end
47
+
48
+ Given /that service requires the username '(.*)' with the password '(.*)'/ do |username, password|
49
+ @handler.username = username
50
+ @handler.password = password
51
+ end
52
+
53
+ Given /a restricted page at '(.*)'/ do |url|
54
+ Given "a remote service that returns 'A response I will never see'"
55
+ And "that service is accessed at the path '#{url}'"
56
+ And "that service is protected by Basic Authentication"
57
+ And "that service requires the username 'something' with the password 'secret'"
58
+ end
59
+
60
+ # This joins the server thread, and halts cucumber, so you can actually hit the
61
+ # server with a browser. Runs until you kill it with Ctrl-c
62
+ Given /I want to hit this in a browser/ do
63
+ @server.acceptor.join
64
+ end
65
+
66
+ Then /I wait for the server to recover/ do
67
+ timeout = @request_options[:timeout] || 0
68
+ sleep @server_response_time - timeout
69
+ 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 '/landing_service.html'
11
+ And the url '/redirector.html' redirects to '/landing_service.html'
12
+ When I call HTTPotato#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 HTTPotato#get with '/first.html'
22
+ Then it should raise an HTTPotato::RedirectionTooDeep exception
@@ -0,0 +1,13 @@
1
+ Feature: Supports the timeout option
2
+ In order to handle inappropriately slow response times
3
+ As a developer
4
+ I want my request to raise an exception after my specified timeout as elapsed
5
+
6
+ Scenario: A long running response
7
+ Given a remote service that returns '<h1>Some HTML</h1>'
8
+ And that service is accessed at the path '/long_running_service.html'
9
+ And that service takes 2 seconds to generate a response
10
+ When I set my HTTPotato timeout option to 1
11
+ And I call HTTPotato#get with '/long_running_service.html'
12
+ Then it should raise a Timeout::Error exception
13
+ And I wait for the server to recover
@@ -0,0 +1,150 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{httpotato}
8
+ s.version = "1.0.2"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Wes Morgan", "Adrian Cushman", "Chris Gill"]
12
+ s.date = %q{2010-11-01}
13
+ s.default_executable = %q{httpotato}
14
+ s.description = %q{Makes http fun & JSON parsing fast! Also, makes consuming restful web services dead easy. Forked from HTTParty.}
15
+ s.email = %q{innovationlab@dnc.org}
16
+ s.executables = ["httpotato"]
17
+ s.extra_rdoc_files = [
18
+ "README.rdoc"
19
+ ]
20
+ s.files = [
21
+ ".gitignore",
22
+ "History",
23
+ "MIT-LICENSE",
24
+ "Manifest",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "bin/httpotato",
29
+ "cucumber.yml",
30
+ "examples/aaws.rb",
31
+ "examples/basic.rb",
32
+ "examples/custom_parsers.rb",
33
+ "examples/delicious.rb",
34
+ "examples/google.rb",
35
+ "examples/rubyurl.rb",
36
+ "examples/twitter.rb",
37
+ "examples/whoismyrep.rb",
38
+ "features/basic_authentication.feature",
39
+ "features/command_line.feature",
40
+ "features/deals_with_http_error_codes.feature",
41
+ "features/digest_authentication.feature",
42
+ "features/handles_compressed_responses.feature",
43
+ "features/handles_multiple_formats.feature",
44
+ "features/steps/env.rb",
45
+ "features/steps/httpotato_response_steps.rb",
46
+ "features/steps/httpotato_steps.rb",
47
+ "features/steps/mongrel_helper.rb",
48
+ "features/steps/remote_service_steps.rb",
49
+ "features/supports_redirection.feature",
50
+ "features/supports_timeout_option.feature",
51
+ "httpotato.gemspec",
52
+ "lib/httpotato.rb",
53
+ "lib/httpotato/cookie_hash.rb",
54
+ "lib/httpotato/core_extensions.rb",
55
+ "lib/httpotato/exceptions.rb",
56
+ "lib/httpotato/module_inheritable_attributes.rb",
57
+ "lib/httpotato/net_digest_auth.rb",
58
+ "lib/httpotato/parser.rb",
59
+ "lib/httpotato/request.rb",
60
+ "lib/httpotato/response.rb",
61
+ "spec/fixtures/delicious.xml",
62
+ "spec/fixtures/empty.xml",
63
+ "spec/fixtures/google.html",
64
+ "spec/fixtures/ssl/generate.sh",
65
+ "spec/fixtures/ssl/generated/1fe462c2.0",
66
+ "spec/fixtures/ssl/generated/bogushost.crt",
67
+ "spec/fixtures/ssl/generated/ca.crt",
68
+ "spec/fixtures/ssl/generated/ca.key",
69
+ "spec/fixtures/ssl/generated/selfsigned.crt",
70
+ "spec/fixtures/ssl/generated/server.crt",
71
+ "spec/fixtures/ssl/generated/server.key",
72
+ "spec/fixtures/ssl/openssl-exts.cnf",
73
+ "spec/fixtures/twitter.json",
74
+ "spec/fixtures/twitter.xml",
75
+ "spec/fixtures/undefined_method_add_node_for_nil.xml",
76
+ "spec/httpotato/cookie_hash_spec.rb",
77
+ "spec/httpotato/parser_spec.rb",
78
+ "spec/httpotato/request_spec.rb",
79
+ "spec/httpotato/response_spec.rb",
80
+ "spec/httpotato/ssl_spec.rb",
81
+ "spec/httpotato_spec.rb",
82
+ "spec/spec.opts",
83
+ "spec/spec_helper.rb",
84
+ "spec/support/ssl_test_helper.rb",
85
+ "spec/support/ssl_test_server.rb",
86
+ "spec/support/stub_response.rb",
87
+ "test.rb",
88
+ "website/css/common.css",
89
+ "website/index.html"
90
+ ]
91
+ s.homepage = %q{http://github.com/dnclabs/httpotato/}
92
+ s.post_install_message = %q{Winners don't use crack.}
93
+ s.rdoc_options = ["--charset=UTF-8"]
94
+ s.require_paths = ["lib"]
95
+ s.rubyforge_project = %q{httpotato}
96
+ s.rubygems_version = %q{1.3.7}
97
+ s.summary = %q{Makes http fun & JSON parsing fast! Also, makes consuming restful web services dead easy.}
98
+ s.test_files = [
99
+ "spec/httpotato/cookie_hash_spec.rb",
100
+ "spec/httpotato/parser_spec.rb",
101
+ "spec/httpotato/request_spec.rb",
102
+ "spec/httpotato/response_spec.rb",
103
+ "spec/httpotato/ssl_spec.rb",
104
+ "spec/httpotato_spec.rb",
105
+ "spec/spec_helper.rb",
106
+ "spec/support/ssl_test_helper.rb",
107
+ "spec/support/ssl_test_server.rb",
108
+ "spec/support/stub_response.rb",
109
+ "examples/aaws.rb",
110
+ "examples/basic.rb",
111
+ "examples/custom_parsers.rb",
112
+ "examples/delicious.rb",
113
+ "examples/google.rb",
114
+ "examples/rubyurl.rb",
115
+ "examples/twitter.rb",
116
+ "examples/whoismyrep.rb"
117
+ ]
118
+
119
+ if s.respond_to? :specification_version then
120
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
121
+ s.specification_version = 3
122
+
123
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
124
+ s.add_runtime_dependency(%q<crack>, ["= 0.1.8"])
125
+ s.add_runtime_dependency(%q<json>, ["= 1.4.6"])
126
+ s.add_development_dependency(%q<activesupport>, ["~> 2.3"])
127
+ s.add_development_dependency(%q<cucumber>, ["~> 0.7"])
128
+ s.add_development_dependency(%q<fakeweb>, ["~> 1.2"])
129
+ s.add_development_dependency(%q<mongrel>, ["~> 1.1"])
130
+ s.add_development_dependency(%q<rspec>, ["~> 1.3"])
131
+ else
132
+ s.add_dependency(%q<crack>, ["= 0.1.8"])
133
+ s.add_dependency(%q<json>, ["= 1.4.6"])
134
+ s.add_dependency(%q<activesupport>, ["~> 2.3"])
135
+ s.add_dependency(%q<cucumber>, ["~> 0.7"])
136
+ s.add_dependency(%q<fakeweb>, ["~> 1.2"])
137
+ s.add_dependency(%q<mongrel>, ["~> 1.1"])
138
+ s.add_dependency(%q<rspec>, ["~> 1.3"])
139
+ end
140
+ else
141
+ s.add_dependency(%q<crack>, ["= 0.1.8"])
142
+ s.add_dependency(%q<json>, ["= 1.4.6"])
143
+ s.add_dependency(%q<activesupport>, ["~> 2.3"])
144
+ s.add_dependency(%q<cucumber>, ["~> 0.7"])
145
+ s.add_dependency(%q<fakeweb>, ["~> 1.2"])
146
+ s.add_dependency(%q<mongrel>, ["~> 1.1"])
147
+ s.add_dependency(%q<rspec>, ["~> 1.3"])
148
+ end
149
+ end
150
+
@@ -0,0 +1,371 @@
1
+ require 'pathname'
2
+ require 'net/http'
3
+ require 'net/https'
4
+ require 'uri'
5
+ require 'zlib'
6
+ require 'crack'
7
+ require 'json/ext'
8
+
9
+ dir = Pathname(__FILE__).dirname.expand_path
10
+
11
+ require dir + 'httpotato/module_inheritable_attributes'
12
+ require dir + 'httpotato/cookie_hash'
13
+ require dir + 'httpotato/net_digest_auth'
14
+
15
+ module HTTPotato
16
+ VERSION = "1.0.2".freeze
17
+ CRACK_DEPENDENCY = "0.1.8".freeze
18
+ JSON_DEPENDENCY = "1.4.6".freeze
19
+
20
+ module AllowedFormatsDeprecation
21
+ def const_missing(const)
22
+ if const.to_s =~ /AllowedFormats$/
23
+ Kernel.warn("Deprecated: Use HTTPotato::Parser::SupportedFormats")
24
+ HTTPotato::Parser::SupportedFormats
25
+ else
26
+ super
27
+ end
28
+ end
29
+ end
30
+
31
+ extend AllowedFormatsDeprecation
32
+
33
+ def self.included(base)
34
+ base.extend ClassMethods
35
+ base.send :include, HTTPotato::ModuleInheritableAttributes
36
+ base.send(:mattr_inheritable, :default_options)
37
+ base.send(:mattr_inheritable, :default_cookies)
38
+ base.instance_variable_set("@default_options", {})
39
+ base.instance_variable_set("@default_cookies", CookieHash.new)
40
+ end
41
+
42
+ module ClassMethods
43
+ extend AllowedFormatsDeprecation
44
+
45
+ # Allows setting http proxy information to be used
46
+ #
47
+ # class Foo
48
+ # include HTTPotato
49
+ # http_proxy 'http://foo.com', 80
50
+ # end
51
+ def http_proxy(addr=nil, port = nil)
52
+ default_options[:http_proxyaddr] = addr
53
+ default_options[:http_proxyport] = port
54
+ end
55
+
56
+ # Allows setting a base uri to be used for each request.
57
+ # Will normalize uri to include http, etc.
58
+ #
59
+ # class Foo
60
+ # include HTTPotato
61
+ # base_uri 'twitter.com'
62
+ # end
63
+ def base_uri(uri=nil)
64
+ return default_options[:base_uri] unless uri
65
+ default_options[:base_uri] = HTTPotato.normalize_base_uri(uri)
66
+ end
67
+
68
+ # Allows setting basic authentication username and password.
69
+ #
70
+ # class Foo
71
+ # include HTTPotato
72
+ # basic_auth 'username', 'password'
73
+ # end
74
+ def basic_auth(u, p)
75
+ default_options[:basic_auth] = {:username => u, :password => p}
76
+ end
77
+
78
+ # Allows setting digest authentication username and password.
79
+ #
80
+ # class Foo
81
+ # include HTTPotato
82
+ # digest_auth 'username', 'password'
83
+ # end
84
+ def digest_auth(u, p)
85
+ default_options[:digest_auth] = {:username => u, :password => p}
86
+ end
87
+
88
+ # Allows setting default parameters to be appended to each request.
89
+ # Great for api keys and such.
90
+ #
91
+ # class Foo
92
+ # include HTTPotato
93
+ # default_params :api_key => 'secret', :another => 'foo'
94
+ # end
95
+ def default_params(h={})
96
+ raise ArgumentError, 'Default params must be a hash' unless h.is_a?(Hash)
97
+ default_options[:default_params] ||= {}
98
+ default_options[:default_params].merge!(h)
99
+ end
100
+
101
+ # Allows setting a default timeout for all HTTP calls
102
+ # Timeout is specified in seconds.
103
+ #
104
+ # class Foo
105
+ # include HTTPotato
106
+ # default_timeout 10
107
+ # end
108
+ def default_timeout(t)
109
+ raise ArgumentError, 'Timeout must be an integer' unless t && t.is_a?(Integer)
110
+ default_options[:timeout] = t
111
+ end
112
+
113
+ # Set an output stream for debugging, defaults to $stderr.
114
+ # The output stream is passed on to Net::HTTP#set_debug_output.
115
+ #
116
+ # class Foo
117
+ # include HTTPotato
118
+ # debug_output $stderr
119
+ # end
120
+ def debug_output(stream = $stderr)
121
+ default_options[:debug_output] = stream
122
+ end
123
+
124
+ # Allows setting HTTP headers to be used for each request.
125
+ #
126
+ # class Foo
127
+ # include HTTPotato
128
+ # headers 'Accept' => 'text/html'
129
+ # end
130
+ def headers(h={})
131
+ raise ArgumentError, 'Headers must be a hash' unless h.is_a?(Hash)
132
+ default_options[:headers] ||= {}
133
+ default_options[:headers].merge!(h)
134
+ end
135
+
136
+ def cookies(h={})
137
+ raise ArgumentError, 'Cookies must be a hash' unless h.is_a?(Hash)
138
+ default_cookies.add_cookies(h)
139
+ end
140
+
141
+ # Allows setting the format with which to parse.
142
+ # Must be one of the allowed formats ie: json, xml
143
+ #
144
+ # class Foo
145
+ # include HTTPotato
146
+ # format :json
147
+ # end
148
+ def format(f = nil)
149
+ if f.nil?
150
+ default_options[:format]
151
+ else
152
+ parser(Parser) if parser.nil?
153
+ default_options[:format] = f
154
+ validate_format
155
+ end
156
+ end
157
+
158
+ # Declare whether or not to follow redirects. When true, an
159
+ # {HTTPotato::RedirectionTooDeep} error will raise upon encountering a
160
+ # redirect. You can then gain access to the response object via
161
+ # HTTPotato::RedirectionTooDeep#response.
162
+ #
163
+ # @see HTTPotato::ResponseError#response
164
+ #
165
+ # @example
166
+ # class Foo
167
+ # include HTTPotato
168
+ # base_uri 'http://google.com'
169
+ # no_follow true
170
+ # end
171
+ #
172
+ # begin
173
+ # Foo.get('/')
174
+ # rescue HTTPotato::RedirectionTooDeep => e
175
+ # puts e.response.body
176
+ # end
177
+ def no_follow(value = false)
178
+ default_options[:no_follow] = value
179
+ end
180
+
181
+ # Declare that you wish to maintain the chosen HTTP method across redirects.
182
+ # The default behavior is to follow redirects via the GET method.
183
+ # If you wish to maintain the original method, you can set this option to true.
184
+ #
185
+ # @example
186
+ # class Foo
187
+ # include HTTPotato
188
+ # base_uri 'http://google.com'
189
+ # maintain_method_across_redirects true
190
+ # end
191
+
192
+ def maintain_method_across_redirects(value = true)
193
+ default_options[:maintain_method_across_redirects] = value
194
+ end
195
+
196
+ # Allows setting a PEM file to be used
197
+ #
198
+ # class Foo
199
+ # include HTTPotato
200
+ # pem File.read('/home/user/my.pem')
201
+ # end
202
+ def pem(pem_contents)
203
+ default_options[:pem] = pem_contents
204
+ end
205
+
206
+ # Allows setting an OpenSSL certificate authority file
207
+ #
208
+ # class Foo
209
+ # include HTTPotato
210
+ # ssl_ca_file '/etc/ssl/certs/ca-certificates.crt'
211
+ # end
212
+ def ssl_ca_file(path)
213
+ default_options[:ssl_ca_file] = path
214
+ end
215
+
216
+ # Allows setting an OpenSSL certificate authority path (directory)
217
+ #
218
+ # class Foo
219
+ # include HTTPotato
220
+ # ssl_ca_path '/etc/ssl/certs/'
221
+ # end
222
+ def ssl_ca_path(path)
223
+ default_options[:ssl_ca_path] = path
224
+ end
225
+
226
+ # Allows setting a custom parser for the response.
227
+ #
228
+ # class Foo
229
+ # include HTTPotato
230
+ # parser Proc.new {|data| ...}
231
+ # end
232
+ def parser(custom_parser = nil)
233
+ if custom_parser.nil?
234
+ default_options[:parser]
235
+ else
236
+ default_options[:parser] = custom_parser
237
+ validate_format
238
+ end
239
+ end
240
+
241
+ # Allows making a get request to a url.
242
+ #
243
+ # class Foo
244
+ # include HTTPotato
245
+ # end
246
+ #
247
+ # # Simple get with full url
248
+ # Foo.get('http://foo.com/resource.json')
249
+ #
250
+ # # Simple get with full url and query parameters
251
+ # # ie: http://foo.com/resource.json?limit=10
252
+ # Foo.get('http://foo.com/resource.json', :query => {:limit => 10})
253
+ def get(path, options={})
254
+ perform_request Net::HTTP::Get, path, options
255
+ end
256
+
257
+ # Allows making a post request to a url.
258
+ #
259
+ # class Foo
260
+ # include HTTPotato
261
+ # end
262
+ #
263
+ # # Simple post with full url and setting the body
264
+ # Foo.post('http://foo.com/resources', :body => {:bar => 'baz'})
265
+ #
266
+ # # Simple post with full url using :query option,
267
+ # # which gets set as form data on the request.
268
+ # Foo.post('http://foo.com/resources', :query => {:bar => 'baz'})
269
+ def post(path, options={})
270
+ perform_request Net::HTTP::Post, path, options
271
+ end
272
+
273
+ # Perform a PUT request to a path
274
+ def put(path, options={})
275
+ perform_request Net::HTTP::Put, path, options
276
+ end
277
+
278
+ # Perform a DELETE request to a path
279
+ def delete(path, options={})
280
+ perform_request Net::HTTP::Delete, path, options
281
+ end
282
+
283
+ # Perform a HEAD request to a path
284
+ def head(path, options={})
285
+ perform_request Net::HTTP::Head, path, options
286
+ end
287
+
288
+ # Perform an OPTIONS request to a path
289
+ def options(path, options={})
290
+ perform_request Net::HTTP::Options, path, options
291
+ end
292
+
293
+ def default_options #:nodoc:
294
+ @default_options
295
+ end
296
+
297
+ private
298
+
299
+ def perform_request(http_method, path, options) #:nodoc:
300
+ options = default_options.dup.merge(options)
301
+ process_cookies(options)
302
+ Request.new(http_method, path, options).perform
303
+ end
304
+
305
+ def process_cookies(options) #:nodoc:
306
+ return unless options[:cookies] || default_cookies.any?
307
+ options[:headers] ||= headers.dup
308
+ options[:headers]["cookie"] = cookies.merge(options.delete(:cookies) || {}).to_cookie_string
309
+ end
310
+
311
+ def validate_format
312
+ if format && parser.respond_to?(:supports_format?) && !parser.supports_format?(format)
313
+ raise UnsupportedFormat, "'#{format.inspect}' Must be one of: #{parser.supported_formats.map{|f| f.to_s}.sort.join(', ')}"
314
+ end
315
+ end
316
+ end
317
+
318
+ def self.normalize_base_uri(url) #:nodoc:
319
+ normalized_url = url.dup
320
+ use_ssl = (normalized_url =~ /^https/) || normalized_url.include?(':443')
321
+ ends_with_slash = normalized_url =~ /\/$/
322
+
323
+ normalized_url.chop! if ends_with_slash
324
+ normalized_url.gsub!(/^https?:\/\//i, '')
325
+
326
+ "http#{'s' if use_ssl}://#{normalized_url}"
327
+ end
328
+
329
+ class Basement #:nodoc:
330
+ include HTTPotato
331
+ end
332
+
333
+ def self.get(*args)
334
+ Basement.get(*args)
335
+ end
336
+
337
+ def self.post(*args)
338
+ Basement.post(*args)
339
+ end
340
+
341
+ def self.put(*args)
342
+ Basement.put(*args)
343
+ end
344
+
345
+ def self.delete(*args)
346
+ Basement.delete(*args)
347
+ end
348
+
349
+ def self.head(*args)
350
+ Basement.head(*args)
351
+ end
352
+
353
+ def self.options(*args)
354
+ Basement.options(*args)
355
+ end
356
+
357
+ end
358
+
359
+ require dir + 'httpotato/core_extensions'
360
+ require dir + 'httpotato/exceptions'
361
+ require dir + 'httpotato/parser'
362
+ require dir + 'httpotato/request'
363
+ require dir + 'httpotato/response'
364
+
365
+ if JSON::VERSION != HTTPotato::JSON_DEPENDENCY
366
+ warn "warning: HTTPotato depends on version #{HTTPotato::JSON_DEPENDENCY} of json, not #{JSON::VERSION}."
367
+ end
368
+
369
+ if Crack::VERSION != HTTPotato::CRACK_DEPENDENCY
370
+ warn "warning: HTTPotato depends on version #{HTTPotato::CRACK_DEPENDENCY} of crack, not #{Crack::VERSION}."
371
+ end