public
Rubygem
Description: Makes http fun! Also, makes consuming restful web services dead easy.
Homepage:
Clone URL: git://github.com/jnunemaker/httparty.git
Click here to lend your support to: httparty and make a donation at www.pledgie.com !
Moving send_request and friends into HTTParty::Request
ReinH (author)
Sat Nov 08 10:59:57 -0800 2008
commit  7bdca06bd4d9a41765e83c30da9744521e2eb455
tree    ce6c5263dcc2b3143c6604a5339ed824bc539709
parent  a2fd09256fb5b4558838c32b19f158024d065fb0
...
8
9
10
 
 
11
12
13
 
 
14
15
16
17
18
19
20
21
22
 
 
 
 
23
24
25
...
28
29
30
31
32
 
 
33
34
35
36
37
 
 
 
38
39
 
40
41
42
43
44
 
45
46
47
48
49
50
51
52
53
 
 
54
55
56
57
58
59
60
 
 
61
62
63
64
65
 
66
67
 
68
69
70
...
84
85
86
87
 
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
 
 
153
154
155
...
159
160
161
162
163
164
165
166
167
168
169
...
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 
 
 
23
24
25
26
27
28
29
30
...
33
34
35
 
 
36
37
38
39
 
 
 
40
41
42
43
 
44
45
46
47
48
 
49
50
51
52
53
54
55
 
 
 
56
57
58
59
60
61
 
 
 
62
63
64
65
66
67
 
68
69
70
71
72
73
74
...
88
89
90
 
91
92
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
 
 
 
 
 
 
 
 
 
 
 
94
95
96
97
98
...
102
103
104
 
 
 
 
 
 
105
106
0
@@ -8,18 +8,23 @@ require 'active_support'
0
 directory = File.dirname(__FILE__)
0
 $:.unshift(directory) unless $:.include?(directory) || $:.include?(File.expand_path(directory))
0
 
0
+require 'httparty/request'
0
+
0
 module HTTParty
0
   class UnsupportedFormat < StandardError; end
0
   class RedirectionTooDeep < StandardError; end
0
+
0
+  AllowedFormats = {:xml => 'text/xml', :json => 'application/json'}
0
   
0
   def self.included(base)
0
     base.extend ClassMethods
0
   end
0
   
0
-  AllowedFormats = {:xml => 'text/xml', :json => 'application/json'}
0
-  SupportedHTTPMethods = [Net::HTTP::Get, Net::HTTP::Post, Net::HTTP::Put, Net::HTTP::Delete]
0
-  
0
   module ClassMethods    
0
+    def default_options
0
+      @@default_options ||= {}
0
+    end
0
+
0
     #
0
     # Set an http proxy
0
     #
0
@@ -28,43 +33,42 @@ module HTTParty
0
     #    http_proxy http://myProxy, 1080
0
     # ....
0
     def http_proxy(addr=nil, port = nil)
0
-     @http_proxyaddr = addr
0
-     @http_proxyport = port
0
+      default_options[:http_proxyaddr] = addr
0
+      default_options[:http_proxyport] = port
0
     end
0
 
0
-    def base_uri(base_uri=nil)
0
-      return @base_uri unless base_uri
0
-      @base_uri = normalize_base_uri(base_uri)
0
+    def base_uri(uri=nil)
0
+      return default_options[:base_uri] unless uri
0
+      default_options[:base_uri] = normalize_base_uri(uri)
0
     end
0
-    
0
+
0
     # Warning: This is not thread safe most likely and
0
     # only works if you use one set of credentials. I
0
     # leave it because it is convenient on some occasions.
0
     def basic_auth(u, p)
0
-      @auth = {:username => u, :password => p}
0
+      default_options[:basic_auth] = {:username => u, :password => p}
0
     end
0
     
0
     # Updates the default query string parameters
0
     # that should be appended to each request.
0
     def default_params(h={})
0
       raise ArgumentError, 'Default params must be a hash' unless h.is_a?(Hash)
0
-      @default_params ||= {}
0
-      return @default_params if h.blank?
0
-      @default_params.merge!(h)
0
+      default_options[:default_params] ||= {}
0
+      default_options[:default_params].merge!(h)
0
     end
0
 
0
     def headers(h={})
0
       raise ArgumentError, 'Headers must be a hash' unless h.is_a?(Hash)
0
-      @headers ||= {}
0
-      return @headers if h.blank?
0
-      @headers.merge!(h)
0
+      default_options[:headers] ||= {}
0
+      default_options[:headers].merge!(h)
0
     end
0
     
0
     def format(f)
0
       raise UnsupportedFormat, "Must be one of: #{AllowedFormats.keys.join(', ')}" unless AllowedFormats.key?(f)
0
-      @format = f
0
+      default_options[:format] = f
0
     end
0
     
0
+    
0
     # TODO: spec out this
0
     def get(path, options={})
0
       send_request Net::HTTP::Get, path, options
0
@@ -84,72 +88,11 @@ module HTTParty
0
     def delete(path, options={})
0
       send_request Net::HTTP::Delete, path, options
0
     end
0
-    
0
+
0
     private
0
-      def http(uri) #:nodoc:
0
-        http = Net::HTTP.new(uri.host, uri.port, @http_proxyaddr, @http_proxyport)
0
-        http.use_ssl = (uri.port == 443)
0
-        http.verify_mode = OpenSSL::SSL::VERIFY_NONE
0
-        http
0
-      end
0
-      
0
-      # FIXME: this method is doing way to much and needs to be split up
0
-      # options can be any or all of:
0
-      #   query       => hash of keys/values or a query string (foo=bar&baz=poo)
0
-      #   body        => hash of keys/values or a query string (foo=bar&baz=poo)
0
-      #   headers     => hash of headers to send request with
0
-      #   basic_auth  => :username and :password to use as basic http authentication (overrides @auth class instance variable)
0
-      # Raises exception Net::XXX (http error code) if an http error occured
0
-      def send_request(klass, path, options={}) #:nodoc:
0
-        options = {:limit => 5}.merge(options)
0
-        options[:limit] = 0 if options.delete(:no_follow)
0
-        
0
-        raise HTTParty::RedirectionTooDeep, 'HTTP redirects too deep' if options[:limit].to_i <= 0
0
-        raise ArgumentError, 'only get, post, put and delete methods are supported' unless SupportedHTTPMethods.include?(klass)
0
-        raise ArgumentError, ':headers must be a hash' if options[:headers] && !options[:headers].is_a?(Hash)
0
-        raise ArgumentError, ':basic_auth must be a hash' if options[:basic_auth] && !options[:basic_auth].is_a?(Hash)
0
-        
0
-        path           = URI.parse(path)
0
-        uri            = path.relative? ? URI.parse("#{base_uri}#{path}") : path
0
-        existing_query = uri.query ? "#{uri.query}&" : ''
0
-        uri.query      = if options[:query].blank?
0
-          existing_query + default_params.to_query
0
-        else
0
-          existing_query + (options[:query].is_a?(Hash) ? default_params.merge(options[:query]).to_query : options[:query])
0
-        end
0
-        
0
-        request        = klass.new(uri.request_uri)
0
-        request.body   = options[:body].is_a?(Hash) ? options[:body].to_query : options[:body] unless options[:body].blank?
0
-        basic_auth     = options.delete(:basic_auth) || @auth
0
-        request.initialize_http_header headers.merge(options[:headers] || {})
0
-        request.basic_auth(basic_auth[:username], basic_auth[:password]) if basic_auth
0
-        response       = http(uri).request(request)
0
-        @format      ||= format_from_mimetype(response['content-type'])
0
-        
0
-        case response
0
-        when Net::HTTPSuccess
0
-          parse_response(response.body)
0
-        when Net::HTTPRedirection
0
-          options[:limit] -= 1
0
-          send_request(klass, response['location'], options)
0
-        else
0
-          response.instance_eval { class << self; attr_accessor :body_parsed; end }
0
-          begin; response.body_parsed = parse_response(response.body); rescue; end
0
-          response.error! # raises  exception corresponding to http error Net::XXX
0
-        end
0
 
0
-      end
0
-      
0
-      def parse_response(body) #:nodoc:
0
-        return nil if body.nil? or body.empty?
0
-        case @format
0
-        when :xml
0
-          Hash.from_xml(body)
0
-        when :json
0
-          ActiveSupport::JSON.decode(body)
0
-        else
0
-          body
0
-        end
0
+      def send_request(http_method, path, options)
0
+        Request.send_request(http_method, path, default_options.merge(options))
0
       end
0
     
0
       # Makes it so uri is sure to parse stuff like google.com with the http
0
@@ -159,11 +102,5 @@ module HTTParty
0
         url.gsub!(/^https?:\/\//i, '')
0
         "http#{'s' if use_ssl}://#{url}"
0
       end
0
-      
0
-      # Uses the HTTP Content-Type header to determine the format of the response
0
-      # It compares the MIME type returned to the types stored in the AllowedFormats hash
0
-      def format_from_mimetype(mimetype) #:nodoc:
0
-        AllowedFormats.each { |k, v| return k if mimetype.include?(v) }
0
-      end
0
   end
0
 end
...
13
14
15
 
 
 
 
16
17
18
...
65
66
67
68
 
69
70
71
72
73
74
75
 
76
77
78
79
80
 
81
82
83
...
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
...
126
127
128
129
 
130
131
 
132
133
134
135
136
137
138
139
140
141
142
 
143
144
145
 
 
 
 
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
 
 
 
 
 
206
207
208
209
210
211
 
 
 
 
 
212
213
214
215
216
217
218
 
 
 
 
219
220
221
...
13
14
15
16
17
18
19
20
21
22
...
69
70
71
 
72
73
74
75
76
77
78
 
79
80
81
82
83
 
84
85
86
87
...
91
92
93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
95
96
...
106
107
108
 
109
110
111
112
113
 
 
 
 
 
 
 
 
 
 
114
115
 
 
116
117
118
119
120
121
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
123
124
125
126
127
 
 
 
 
 
128
129
130
131
132
133
 
 
 
 
 
 
134
135
136
137
138
139
140
0
@@ -13,6 +13,10 @@ end
0
 describe HTTParty do
0
   
0
   describe "base uri" do
0
+    before do
0
+      Foo.base_uri('api.foo.com/v1')
0
+    end
0
+
0
     it "should have reader" do
0
       Foo.base_uri.should == 'http://api.foo.com/v1'
0
     end
0
@@ -65,19 +69,19 @@ describe HTTParty do
0
   describe "basic http authentication" do
0
     it "should work" do
0
       Foo.basic_auth 'foobar', 'secret'
0
-      Foo.instance_variable_get("@auth").should == {:username => 'foobar', :password => 'secret'}
0
+      Foo.default_options[:basic_auth].should == {:username => 'foobar', :password => 'secret'}
0
     end
0
   end
0
   
0
   describe "format" do
0
     it "should allow xml" do
0
       Foo.format :xml
0
-      Foo.instance_variable_get("@format").should == :xml
0
+      Foo.default_options[:format].should == :xml
0
     end
0
     
0
     it "should allow json" do
0
       Foo.format :json
0
-      Foo.instance_variable_get("@format").should == :json
0
+      Foo.default_options[:format].should == :json
0
     end
0
     
0
     it 'should not allow funky format' do
0
@@ -87,30 +91,6 @@ describe HTTParty do
0
     end
0
   end
0
   
0
-  describe 'http' do
0
-    it "should use ssl for port 443" do
0
-      FooWithHttps.send(:http, URI.parse('https://api.foo.com/v1:443')).use_ssl?.should == true
0
-    end
0
-    
0
-    it 'should not use ssl for port 80' do
0
-      Foo.send(:http, URI.parse('http://foobar.com')).use_ssl?.should == false
0
-    end
0
-  end
0
-  
0
-  describe 'parsing responses' do
0
-    it 'should handle xml automatically' do
0
-      xml = %q[<books><book><id>1234</id><name>Foo Bar!</name></book></books>]
0
-      Foo.format :xml
0
-      Foo.send(:parse_response, xml).should == {'books' => {'book' => {'id' => '1234', 'name' => 'Foo Bar!'}}}
0
-    end
0
-    
0
-    it 'should handle json automatically' do
0
-      json = %q[{"books": {"book": {"name": "Foo Bar!", "id": "1234"}}}]
0
-      Foo.format :json
0
-      Foo.send(:parse_response, json).should == {'books' => {'book' => {'id' => '1234', 'name' => 'Foo Bar!'}}}
0
-    end
0
-  end
0
-  
0
   describe "sending requests" do
0
     it "should not work with request method other than get, post, put, delete" do
0
       lambda do
0
@@ -126,96 +106,35 @@ describe HTTParty do
0
     
0
     it 'should require that :basic_auth is a hash if present' do
0
       lambda do
0
-        Foo.send(:send_request, 'get', '/foo', :basic_auth => 'string')
0
+        Foo.get('/foo', :basic_auth => 'string')
0
       end.should raise_error(ArgumentError)
0
     end
0
+  end
0
 
0
-    it "should not attempt to parse empty responses" do
0
-      http = Net::HTTP.new('localhost', 80)
0
-      Foo.stub!(:http).and_return(http)
0
-      response = Net::HTTPNoContent.new("1.1", 204, "No content for you")
0
-      response.stub!(:body).and_return(nil)
0
-      http.stub!(:request).and_return(response)
0
-
0
-      Foo.headers.clear # clear out bogus settings from other specs
0
-      Foo.format :xml
0
-      Foo.get('/bar').should be_nil
0
+  describe "with explicit override of automatic redirect handling" do
0
 
0
-      response.stub!(:body).and_return("")
0
-      Foo.get('bar').should be_nil
0
+    it "should fail with redirected GET" do
0
+      lambda do
0
+        Foo.get('/foo', :no_follow => true)
0
+      end.should raise_error(HTTParty::RedirectionTooDeep)
0
     end
0
 
0
-    describe "that respond with redirects" do
0
-      def setup_http
0
-        @http = Net::HTTP.new('localhost', 80)
0
-        Foo.stub!(:http).and_return(@http)
0
-        @redirect = Net::HTTPFound.new("1.1", 302, "")
0
-        @redirect.stub!(:[]).with('location').and_return('/foo')
0
-        @ok = Net::HTTPOK.new("1.1", 200, "Content for you")
0
-        @ok.stub!(:body).and_return({"foo" => "bar"}.to_xml)
0
-        @http.should_receive(:request).and_return(@redirect, @ok)
0
-        Foo.headers.clear
0
-        Foo.format :xml
0
-      end
0
-
0
-      it "should handle redirects for GET transparently" do
0
-        setup_http
0
-        Foo.get('/foo/').should == {"hash" => {"foo" => "bar"}}
0
-      end
0
-
0
-      it "should handle redirects for POST transparently" do
0
-        setup_http
0
-        Foo.post('/foo/', {:foo => :bar}).should == {"hash" => {"foo" => "bar"}}
0
-      end
0
-
0
-      it "should handle redirects for DELETE transparently" do
0
-        setup_http
0
-        Foo.delete('/foo/').should == {"hash" => {"foo" => "bar"}}
0
-      end
0
-
0
-      it "should handle redirects for PUT transparently" do
0
-        setup_http
0
-        Foo.put('/foo/').should == {"hash" => {"foo" => "bar"}}
0
-      end
0
-
0
-      it "should prevent infinite loops" do
0
-        http = Net::HTTP.new('localhost', 80)
0
-        Foo.stub!(:http).and_return(http)
0
-        redirect = Net::HTTPFound.new("1.1", "302", "Look, over there!")
0
-        redirect.stub!(:[]).with('location').and_return('/foo')
0
-        http.stub!(:request).and_return(redirect)
0
-
0
-        lambda do
0
-          Foo.get('/foo')
0
-        end.should raise_error(HTTParty::RedirectionTooDeep)
0
-      end
0
-
0
-      describe "with explicit override of automatic redirect handling" do
0
-
0
-        it "should fail with redirected GET" do
0
-          lambda do
0
-            Foo.get('/foo', :no_follow => true)
0
-          end.should raise_error(HTTParty::RedirectionTooDeep)
0
-        end
0
-
0
-        it "should fail with redirected POST" do
0
-          lambda do
0
-            Foo.post('/foo', :no_follow => true)
0
-          end.should raise_error(HTTParty::RedirectionTooDeep)
0
-        end
0
+    it "should fail with redirected POST" do
0
+      lambda do
0
+        Foo.post('/foo', :no_follow => true)
0
+      end.should raise_error(HTTParty::RedirectionTooDeep)
0
+    end
0
 
0
-        it "should fail with redirected DELETE" do
0
-          lambda do
0
-            Foo.delete('/foo', :no_follow => true)
0
-          end
0
-        end
0
+    it "should fail with redirected DELETE" do
0
+      lambda do
0
+        Foo.delete('/foo', :no_follow => true)
0
+      end.should raise_error(HTTParty::RedirectionTooDeep)
0
+    end
0
 
0
-        it "should fail with redirected PUT" do
0
-          lambda do
0
-            Foo.put('/foo', :no_follow => true)
0
-          end
0
-        end
0
-      end
0
+    it "should fail with redirected PUT" do
0
+      lambda do
0
+        Foo.put('/foo', :no_follow => true)
0
+      end.should raise_error(HTTParty::RedirectionTooDeep)
0
     end
0
   end
0
 end

Comments