wgibbs-rest-client 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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