wgibbs-rest-client 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,46 @@
1
+ module RestClient
2
+ module Mixin
3
+ module Response
4
+ attr_reader :net_http_res
5
+
6
+ # HTTP status code, always 200 since RestClient throws exceptions for
7
+ # other codes.
8
+ def code
9
+ @code ||= @net_http_res.code.to_i
10
+ end
11
+
12
+ # A hash of the headers, beautified with symbols and underscores.
13
+ # e.g. "Content-type" will become :content_type.
14
+ def headers
15
+ @headers ||= self.class.beautify_headers(@net_http_res.to_hash)
16
+ end
17
+
18
+ # Hash of cookies extracted from response headers
19
+ def cookies
20
+ @cookies ||= (self.headers[:set_cookie] || []).inject({}) do |out, cookie|
21
+ cookie.split('; ').each do |raw_c|
22
+ key, val = raw_c.split('=')
23
+ unless %w(expires domain path secure).member?(key)
24
+ out[key] = val
25
+ end
26
+ end
27
+ out
28
+ end
29
+ end
30
+
31
+ def self.included(receiver)
32
+ receiver.extend(RestClient::Mixin::Response::ClassMethods)
33
+ end
34
+
35
+ module ClassMethods
36
+ def beautify_headers(headers)
37
+ headers.inject({}) do |out, (key, value)|
38
+ beautiful_key = key.gsub(/-/, '_').to_sym
39
+ out[beautiful_key] = beautiful_key == :set_cookie ? value : value.first
40
+ out
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,30 @@
1
+ require File.dirname(__FILE__) + '/mixin/response'
2
+
3
+ module RestClient
4
+ # The response from RestClient on a raw request looks like a string, but is
5
+ # actually one of these. 99% of the time you're making a rest call all you
6
+ # care about is the body, but on the occassion you want to fetch the
7
+ # headers you can:
8
+ #
9
+ # RestClient.get('http://example.com').headers[:content_type]
10
+ #
11
+ # In addition, if you do not use the response as a string, you can access
12
+ # a Tempfile object at res.file, which contains the path to the raw
13
+ # downloaded request body.
14
+ class RawResponse
15
+ include RestClient::Mixin::Response
16
+
17
+ attr_reader :file
18
+
19
+ def initialize(tempfile, net_http_res)
20
+ @net_http_res = net_http_res
21
+ @file = tempfile
22
+ end
23
+
24
+ def to_s
25
+ @file.open
26
+ @file.read
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,238 @@
1
+ require 'tempfile'
2
+
3
+ module RestClient
4
+ # This class is used internally by RestClient to send the request, but you can also
5
+ # access it internally if you'd like to use a method not directly supported by the
6
+ # main API. For example:
7
+ #
8
+ # RestClient::Request.execute(:method => :head, :url => 'http://example.com')
9
+ #
10
+ class Request
11
+ attr_reader :method, :url, :payload, :headers,
12
+ :cookies, :user, :password, :timeout, :open_timeout,
13
+ :verify_ssl, :ssl_client_cert, :ssl_client_key, :ssl_ca_file,
14
+ :raw_response
15
+
16
+ def self.execute(args)
17
+ new(args).execute
18
+ end
19
+
20
+ def initialize(args)
21
+ @method = args[:method] or raise ArgumentError, "must pass :method"
22
+ @url = args[:url] or raise ArgumentError, "must pass :url"
23
+ @headers = args[:headers] || {}
24
+ @cookies = @headers.delete(:cookies) || args[:cookies] || {}
25
+ @payload = process_payload(args[:payload])
26
+ @user = args[:user]
27
+ @password = args[:password]
28
+ @timeout = args[:timeout]
29
+ @open_timeout = args[:open_timeout]
30
+ @raw_response = args[:raw_response] || false
31
+ @verify_ssl = args[:verify_ssl] || false
32
+ @ssl_client_cert = args[:ssl_client_cert] || nil
33
+ @ssl_client_key = args[:ssl_client_key] || nil
34
+ @ssl_ca_file = args[:ssl_ca_file] || nil
35
+ @tf = nil # If you are a raw request, this is your tempfile
36
+ end
37
+
38
+ def execute
39
+ execute_inner
40
+ rescue Redirect => e
41
+ @url = e.url
42
+ @method = :get
43
+ @payload = nil
44
+ execute
45
+ end
46
+
47
+ def execute_inner
48
+ uri = parse_url_with_auth(url)
49
+ transmit uri, net_http_request_class(method).new(uri.request_uri, make_headers(headers)), payload
50
+ end
51
+
52
+ def make_headers(user_headers)
53
+ unless @cookies.empty?
54
+ user_headers[:cookie] = @cookies.map {|key, val| "#{key.to_s}=#{val}" }.join('; ')
55
+ end
56
+
57
+ default_headers.merge(user_headers).inject({}) do |final, (key, value)|
58
+ final[key.to_s.gsub(/_/, '-').capitalize] = value.to_s
59
+ final
60
+ end
61
+ end
62
+
63
+ def net_http_class
64
+ if RestClient.proxy
65
+ proxy_uri = URI.parse(RestClient.proxy)
66
+ Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password)
67
+ else
68
+ Net::HTTP
69
+ end
70
+ end
71
+
72
+ def net_http_request_class(method)
73
+ Net::HTTP.const_get(method.to_s.capitalize)
74
+ end
75
+
76
+ def parse_url(url)
77
+ url = "http://#{url}" unless url.match(/^http/)
78
+ URI.parse(url)
79
+ end
80
+
81
+ def parse_url_with_auth(url)
82
+ uri = parse_url(url)
83
+ @user = uri.user if uri.user
84
+ @password = uri.password if uri.password
85
+ uri
86
+ end
87
+
88
+ def process_payload(p=nil, parent_key=nil)
89
+ unless p.is_a?(Hash)
90
+ p
91
+ else
92
+ @headers[:content_type] ||= 'application/x-www-form-urlencoded'
93
+ p.keys.map do |k|
94
+ key = parent_key ? "#{parent_key}[#{k}]" : k
95
+ if p[k].is_a? Hash
96
+ process_payload(p[k], key)
97
+ else
98
+ value = URI.escape(p[k].to_s, Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
99
+ "#{key}=#{value}"
100
+ end
101
+ end.join("&")
102
+ end
103
+ end
104
+
105
+ def transmit(uri, req, payload)
106
+ setup_credentials(req)
107
+
108
+ net = net_http_class.new(uri.host, uri.port)
109
+ net.use_ssl = uri.is_a?(URI::HTTPS)
110
+ if @verify_ssl == false
111
+ net.verify_mode = OpenSSL::SSL::VERIFY_NONE
112
+ elsif @verify_ssl.is_a? Integer
113
+ net.verify_mode = @verify_ssl
114
+ end
115
+ net.cert = @ssl_client_cert if @ssl_client_cert
116
+ net.key = @ssl_client_key if @ssl_client_key
117
+ net.ca_file = @ssl_ca_file if @ssl_ca_file
118
+ net.read_timeout = @timeout if @timeout
119
+ net.open_timeout = @open_timeout if @open_timeout
120
+
121
+ display_log request_log
122
+
123
+ net.start do |http|
124
+ res = http.request(req, payload) { |http_response| fetch_body(http_response) }
125
+ result = process_result(res)
126
+ display_log response_log(res)
127
+
128
+ if result.kind_of?(String) or @method == :head
129
+ Response.new(result, res)
130
+ elsif @raw_response
131
+ RawResponse.new(@tf, res)
132
+ else
133
+ nil
134
+ end
135
+ end
136
+ rescue EOFError
137
+ raise RestClient::ServerBrokeConnection
138
+ rescue Timeout::Error
139
+ raise RestClient::RequestTimeout
140
+ end
141
+
142
+ def setup_credentials(req)
143
+ req.basic_auth(user, password) if user
144
+ end
145
+
146
+ def fetch_body(http_response)
147
+ if @raw_response
148
+ # Taken from Chef, which as in turn...
149
+ # Stolen from http://www.ruby-forum.com/topic/166423
150
+ # Kudos to _why!
151
+ @tf = Tempfile.new("rest-client")
152
+ size, total = 0, http_response.header['Content-Length'].to_i
153
+ http_response.read_body do |chunk|
154
+ @tf.write(chunk)
155
+ size += chunk.size
156
+ if size == 0
157
+ display_log("#{@method} #{@url} done (0 length file)")
158
+ elsif total == 0
159
+ display_log("#{@method} #{@url} (zero content length)")
160
+ else
161
+ display_log("#{@method} #{@url} %d%% done (%d of %d)" % [(size * 100) / total, size, total])
162
+ end
163
+ end
164
+ @tf.close
165
+ @tf
166
+ else
167
+ http_response.read_body
168
+ end
169
+ http_response
170
+ end
171
+
172
+ def process_result(res)
173
+ if res.code =~ /\A2\d{2}\z/
174
+ # We don't decode raw requests
175
+ unless @raw_response
176
+ self.class.decode res['content-encoding'], res.body if res.body
177
+ end
178
+ elsif %w(301 302 303).include? res.code
179
+ url = res.header['Location']
180
+
181
+ if url !~ /^http/
182
+ uri = URI.parse(@url)
183
+ uri.path = "/#{url}".squeeze('/')
184
+ url = uri.to_s
185
+ end
186
+
187
+ raise Redirect, url
188
+ elsif res.code == "304"
189
+ raise NotModified, res
190
+ elsif res.code == "401"
191
+ raise Unauthorized, res
192
+ elsif res.code == "404"
193
+ raise ResourceNotFound, res
194
+ else
195
+ raise RequestFailed, res
196
+ end
197
+ end
198
+
199
+ def self.decode(content_encoding, body)
200
+ if content_encoding == 'gzip' and not body.empty?
201
+ Zlib::GzipReader.new(StringIO.new(body)).read
202
+ elsif content_encoding == 'deflate'
203
+ Zlib::Inflate.new.inflate(body)
204
+ else
205
+ body
206
+ end
207
+ end
208
+
209
+ def request_log
210
+ out = []
211
+ out << "RestClient.#{method} #{url.inspect}"
212
+ out << (payload.size > 100 ? "(#{payload.size} byte payload)".inspect : payload.inspect) if payload
213
+ out << headers.inspect.gsub(/^\{/, '').gsub(/\}$/, '') unless headers.empty?
214
+ out.join(', ')
215
+ end
216
+
217
+ def response_log(res)
218
+ size = @raw_response ? File.size(@tf.path) : (res.body.nil? ? 0 : res.body.size)
219
+ "# => #{res.code} #{res.class.to_s.gsub(/^Net::HTTP/, '')} | #{(res['Content-type'] || '').gsub(/;.*$/, '')} #{size} bytes"
220
+ end
221
+
222
+ def display_log(msg)
223
+ return unless log_to = RestClient.log
224
+
225
+ if log_to == 'stdout'
226
+ STDOUT.puts msg
227
+ elsif log_to == 'stderr'
228
+ STDERR.puts msg
229
+ else
230
+ File.open(log_to, 'a') { |f| f.puts msg }
231
+ end
232
+ end
233
+
234
+ def default_headers
235
+ { :accept => '*/*; q=0.5, application/xml', :accept_encoding => 'gzip, deflate' }
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,146 @@
1
+ module RestClient
2
+ # A class that can be instantiated for access to a RESTful resource,
3
+ # including authentication.
4
+ #
5
+ # Example:
6
+ #
7
+ # resource = RestClient::Resource.new('http://some/resource')
8
+ # jpg = resource.get(:accept => 'image/jpg')
9
+ #
10
+ # With HTTP basic authentication:
11
+ #
12
+ # resource = RestClient::Resource.new('http://protected/resource', :user => 'user', :password => 'password')
13
+ # resource.delete
14
+ #
15
+ # With a timeout (seconds):
16
+ #
17
+ # RestClient::Resource.new('http://slow', :timeout => 10)
18
+ #
19
+ # With an open timeout (seconds):
20
+ #
21
+ # RestClient::Resource.new('http://behindfirewall', :open_timeout => 10)
22
+ #
23
+ # You can also use resources to share common headers. For headers keys,
24
+ # symbols are converted to strings. Example:
25
+ #
26
+ # resource = RestClient::Resource.new('http://some/resource', :headers => { :client_version => 1 })
27
+ #
28
+ # This header will be transported as X-Client-Version (notice the X prefix,
29
+ # capitalization and hyphens)
30
+ #
31
+ # Use the [] syntax to allocate subresources:
32
+ #
33
+ # site = RestClient::Resource.new('http://example.com', :user => 'adam', :password => 'mypasswd')
34
+ # site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
35
+ #
36
+ class Resource
37
+ attr_reader :url, :options
38
+
39
+ def initialize(url, options={}, backwards_compatibility=nil)
40
+ @url = url
41
+ if options.class == Hash
42
+ @options = options
43
+ else # compatibility with previous versions
44
+ @options = { :user => options, :password => backwards_compatibility }
45
+ end
46
+ end
47
+
48
+ def get(additional_headers={})
49
+ Request.execute(options.merge(
50
+ :method => :get,
51
+ :url => url,
52
+ :headers => headers.merge(additional_headers)
53
+ ))
54
+ end
55
+
56
+ def post(payload, additional_headers={})
57
+ Request.execute(options.merge(
58
+ :method => :post,
59
+ :url => url,
60
+ :payload => payload,
61
+ :headers => headers.merge(additional_headers)
62
+ ))
63
+ end
64
+
65
+ def put(payload, additional_headers={})
66
+ Request.execute(options.merge(
67
+ :method => :put,
68
+ :url => url,
69
+ :payload => payload,
70
+ :headers => headers.merge(additional_headers)
71
+ ))
72
+ end
73
+
74
+ def delete(additional_headers={})
75
+ Request.execute(options.merge(
76
+ :method => :delete,
77
+ :url => url,
78
+ :headers => headers.merge(additional_headers)
79
+ ))
80
+ end
81
+
82
+ def to_s
83
+ url
84
+ end
85
+
86
+ def user
87
+ options[:user]
88
+ end
89
+
90
+ def password
91
+ options[:password]
92
+ end
93
+
94
+ def headers
95
+ options[:headers] || {}
96
+ end
97
+
98
+ def timeout
99
+ options[:timeout]
100
+ end
101
+
102
+ def open_timeout
103
+ options[:open_timeout]
104
+ end
105
+
106
+ # Construct a subresource, preserving authentication.
107
+ #
108
+ # Example:
109
+ #
110
+ # site = RestClient::Resource.new('http://example.com', 'adam', 'mypasswd')
111
+ # site['posts/1/comments'].post 'Good article.', :content_type => 'text/plain'
112
+ #
113
+ # This is especially useful if you wish to define your site in one place and
114
+ # call it in multiple locations:
115
+ #
116
+ # def orders
117
+ # RestClient::Resource.new('http://example.com/orders', 'admin', 'mypasswd')
118
+ # end
119
+ #
120
+ # orders.get # GET http://example.com/orders
121
+ # orders['1'].get # GET http://example.com/orders/1
122
+ # orders['1/items'].delete # DELETE http://example.com/orders/1/items
123
+ #
124
+ # Nest resources as far as you want:
125
+ #
126
+ # site = RestClient::Resource.new('http://example.com')
127
+ # posts = site['posts']
128
+ # first_post = posts['1']
129
+ # comments = first_post['comments']
130
+ # comments.post 'Hello', :content_type => 'text/plain'
131
+ #
132
+ def [](suburl)
133
+ self.class.new(concat_urls(url, suburl), options)
134
+ end
135
+
136
+ def concat_urls(url, suburl) # :nodoc:
137
+ url = url.to_s
138
+ suburl = suburl.to_s
139
+ if url.slice(-1, 1) == '/' or suburl.slice(0, 1) == '/'
140
+ url + suburl
141
+ else
142
+ "#{url}/#{suburl}"
143
+ end
144
+ end
145
+ end
146
+ end