tfe-cloudfiles 1.4.7

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ tmp/*
2
+ coverage/*
3
+ doc/*
4
+ pkg/*
5
+ *.gem
data/COPYING ADDED
@@ -0,0 +1,12 @@
1
+ Unless otherwise noted, all files are released under the MIT license, exceptions contain licensing information in them.
2
+
3
+ Copyright (C) 2008 Rackspace US, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10
+
11
+ Except as contained in this notice, the name of Rackspace US, Inc. shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization from Rackspace US, Inc.
12
+
data/Manifest ADDED
@@ -0,0 +1,16 @@
1
+ tfe-cloudfiles.gemspec
2
+ lib/cloudfiles/authentication.rb
3
+ lib/cloudfiles/connection.rb
4
+ lib/cloudfiles/container.rb
5
+ lib/cloudfiles/storage_object.rb
6
+ lib/cloudfiles.rb
7
+ Manifest
8
+ Rakefile
9
+ README.rdoc
10
+ test/cf-testunit.rb
11
+ test/cloudfiles_authentication_test.rb
12
+ test/cloudfiles_connection_test.rb
13
+ test/cloudfiles_container_test.rb
14
+ test/cloudfiles_storage_object_test.rb
15
+ test/test_helper.rb
16
+ TODO
data/README.rdoc ADDED
@@ -0,0 +1,71 @@
1
+ = Rackspace Cloud Files
2
+
3
+ == Description
4
+
5
+ This is a Ruby interface into the Rackspace[http://rackspace.com/] {Cloud Files}[http://www.rackspacecloud.com/cloud_hosting_products/files] service. Cloud Files is reliable, scalable and affordable web-based storage hosting for backing up and archiving all your static content. Cloud Files is the first and only cloud service that leverages a tier one CDN provider to create such an easy and complete storage-to-delivery solution for media content.
6
+
7
+ == Upgrade Gotchas
8
+
9
+ As of gem version 1.4.8, the connection method has changed from positional arguments to a hash of options. This is the new style:
10
+
11
+ cf = CloudFiles::Connection.new(:username => "MY_USERNAME", :api_key => "MY_API_KEY")
12
+
13
+ This is the old style, which still works but is deprecated:
14
+
15
+ cf = CloudFiles::Connection.new("MY_USERNAME","MY_API_KEY")
16
+
17
+ == Installation
18
+
19
+ This source is available on Github[http://github.com/rackspace/ruby-cloudfiles/] and the gem is available on Gemcutter[http://gemcutter.org/]. To install it, do
20
+
21
+ gem sources -a http://gemcutter.org/
22
+
23
+ sudo gem install cloudfiles
24
+
25
+ To use it in a Rails application, add the following information to your config/environment.rb
26
+
27
+ config.gem "cloudfiles"
28
+
29
+
30
+ == Examples
31
+
32
+ See the class definitions for documentation on specific methods and operations.
33
+
34
+ require 'rubygems'
35
+ require 'cloudfiles'
36
+
37
+ # Log into the Cloud Files system
38
+ cf = CloudFiles::Connection.new(:username => "MY_USERNAME", :api_key => "MY_API_KEY")
39
+
40
+ # Get a listing of all containers under this account
41
+ cf.containers
42
+ => ["backup", "Books", "cftest", "test", "video", "webpics"]
43
+
44
+ # Access a specific container
45
+ container = cf.container('test')
46
+
47
+ # See how many objects are under this container
48
+ container.count
49
+ => 3
50
+
51
+ # List the objects
52
+ container.objects
53
+ => ["bigfile.txt", "new.txt", "test.txt"]
54
+
55
+ # Select an object
56
+ object = container.object('test.txt')
57
+
58
+ # Get that object's data
59
+ object.data
60
+ => "This is test data"
61
+
62
+ == Authors
63
+
64
+ Initial work by Major Hayden <major.hayden@rackspace.com>
65
+
66
+ Subsequent work by H. Wade Minter <wade.minter@rackspace.com>
67
+
68
+ == License
69
+
70
+ See COPYING for license information.
71
+ Copyright (c) 2009, Rackspace US, Inc.
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ require './lib/cloudfiles.rb'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gemspec|
6
+ gemspec.name = "tfe-cloudfiles"
7
+ gemspec.summary = "A Ruby API into Rackspace Cloud Files"
8
+ gemspec.description = "A Ruby version of the Rackspace Cloud Files API."
9
+ gemspec.email = ["wade.minter@rackspace.com", "todd@toddeichel.com"]
10
+ gemspec.homepage = "http://www.rackspacecloud.com/cloud_hosting_products/files"
11
+ gemspec.authors = ["H. Wade Minter", "Rackspace Hosting", "Todd Eichel"]
12
+ gemspec.add_dependency('mime-types', '>= 1.16')
13
+ end
14
+ Jeweler::GemcutterTasks.new
15
+ rescue LoadError
16
+ puts "Jeweler not available. Install it with: sudo gem install jeweler"
17
+ end
18
+
19
+ namespace :test do
20
+ desc 'Check test coverage'
21
+ task :coverage do
22
+ rm_f "coverage"
23
+ system("rcov -x '/Library/Ruby/Gems/1.8/gems/' --sort coverage #{File.join(File.dirname(__FILE__), 'test/*_test.rb')}")
24
+ system("open #{File.join(File.dirname(__FILE__), 'coverage/index.html')}") if PLATFORM['darwin']
25
+ end
26
+
27
+ desc 'Remove coverage products'
28
+ task :clobber_coverage do
29
+ rm_r 'coverage' rescue nil
30
+ end
31
+
32
+ end
33
+
34
+
data/TODO ADDED
File without changes
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.4.7
data/lib/cloudfiles.rb ADDED
@@ -0,0 +1,74 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # == Cloud Files API
4
+ # ==== Connects Ruby Applications to Rackspace's {Cloud Files service}[http://www.rackspacecloud.com/cloud_hosting_products/files]
5
+ # Initial work by Major Hayden <major.hayden@rackspace.com>
6
+ #
7
+ # Subsequent work by H. Wade Minter <wade.minter@rackspace.com>
8
+ #
9
+ # See COPYING for license information.
10
+ # Copyright (c) 2009, Rackspace US, Inc.
11
+ # ----
12
+ #
13
+ # === Documentation & Examples
14
+ # To begin reviewing the available methods and examples, peruse the README file, or begin by looking at documentation for the
15
+ # CloudFiles::Connection class.
16
+ #
17
+ # The CloudFiles class is the base class. Not much of note happens here.
18
+ # To create a new CloudFiles connection, use the CloudFiles::Connection.new('user_name', 'api_key') method.
19
+ module CloudFiles
20
+
21
+ VERSION = IO.read(File.dirname(__FILE__) + '/../VERSION')
22
+ require 'net/http'
23
+ require 'net/https'
24
+ require 'rexml/document'
25
+ require 'uri'
26
+ require 'digest/md5'
27
+ require 'time'
28
+ require 'rubygems'
29
+ require 'mime/types'
30
+
31
+ unless "".respond_to? :each_char
32
+ require "jcode"
33
+ $KCODE = 'u'
34
+ end
35
+
36
+ $:.unshift(File.dirname(__FILE__))
37
+ require 'cloudfiles/authentication'
38
+ require 'cloudfiles/connection'
39
+ require 'cloudfiles/container'
40
+ require 'cloudfiles/storage_object'
41
+
42
+ def self.lines(str)
43
+ (str.respond_to?(:lines) ? str.lines : str).to_a.map { |x| x.chomp }
44
+ end
45
+ end
46
+
47
+
48
+
49
+ class SyntaxException < StandardError # :nodoc:
50
+ end
51
+ class ConnectionException < StandardError # :nodoc:
52
+ end
53
+ class AuthenticationException < StandardError # :nodoc:
54
+ end
55
+ class InvalidResponseException < StandardError # :nodoc:
56
+ end
57
+ class NonEmptyContainerException < StandardError # :nodoc:
58
+ end
59
+ class NoSuchObjectException < StandardError # :nodoc:
60
+ end
61
+ class NoSuchContainerException < StandardError # :nodoc:
62
+ end
63
+ class NoSuchAccountException < StandardError # :nodoc:
64
+ end
65
+ class MisMatchedChecksumException < StandardError # :nodoc:
66
+ end
67
+ class IOException < StandardError # :nodoc:
68
+ end
69
+ class CDNNotEnabledException < StandardError # :nodoc:
70
+ end
71
+ class ObjectExistsException < StandardError # :nodoc:
72
+ end
73
+ class ExpiredAuthTokenException < StandardError # :nodoc:
74
+ end
@@ -0,0 +1,52 @@
1
+ module CloudFiles
2
+ class Authentication
3
+ # See COPYING for license information.
4
+ # Copyright (c) 2009, Rackspace US, Inc.
5
+
6
+ # Performs an authentication to the Cloud Files servers. Opens a new HTTP connection to the API server,
7
+ # sends the credentials, and looks for a successful authentication. If it succeeds, it sets the cdmmgmthost,
8
+ # cdmmgmtpath, storagehost, storagepath, authtoken, and authok variables on the connection. If it fails, it raises
9
+ # an AuthenticationException.
10
+ #
11
+ # Should probably never be called directly.
12
+ def initialize(connection)
13
+ path = '/v1.0'
14
+ hdrhash = { "X-Auth-User" => connection.authuser, "X-Auth-Key" => connection.authkey }
15
+ begin
16
+ server = Net::HTTP::Proxy(connection.proxy_host, connection.proxy_port).new('auth.api.rackspacecloud.com',443)
17
+ server.use_ssl = true
18
+ server.verify_mode = OpenSSL::SSL::VERIFY_NONE
19
+ server.start
20
+ rescue
21
+ raise ConnectionException, "Unable to connect to #{server}"
22
+ end
23
+ response = server.get(path,hdrhash)
24
+ if (response.code == "204")
25
+ connection.cdnmgmthost = URI.parse(response["x-cdn-management-url"]).host
26
+ connection.cdnmgmtpath = URI.parse(response["x-cdn-management-url"]).path
27
+ connection.cdnmgmtport = URI.parse(response["x-cdn-management-url"]).port
28
+ connection.cdnmgmtscheme = URI.parse(response["x-cdn-management-url"]).scheme
29
+ connection.storagehost = set_snet(connection,URI.parse(response["x-storage-url"]).host)
30
+ connection.storagepath = URI.parse(response["x-storage-url"]).path
31
+ connection.storageport = URI.parse(response["x-storage-url"]).port
32
+ connection.storagescheme = URI.parse(response["x-storage-url"]).scheme
33
+ connection.authtoken = response["x-auth-token"]
34
+ connection.authok = true
35
+ else
36
+ connection.authtoken = false
37
+ raise AuthenticationException, "Authentication failed"
38
+ end
39
+ server.finish
40
+ end
41
+
42
+ private
43
+
44
+ def set_snet(connection,hostname)
45
+ if connection.snet?
46
+ "snet-#{hostname}"
47
+ else
48
+ hostname
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,317 @@
1
+ module CloudFiles
2
+ class Connection
3
+ # See COPYING for license information.
4
+ # Copyright (c) 2009, Rackspace US, Inc.
5
+
6
+ # Authentication key provided when the CloudFiles class was instantiated
7
+ attr_reader :authkey
8
+
9
+ # Token returned after a successful authentication
10
+ attr_accessor :authtoken
11
+
12
+ # Authentication username provided when the CloudFiles class was instantiated
13
+ attr_reader :authuser
14
+
15
+ # Hostname of the CDN management server
16
+ attr_accessor :cdnmgmthost
17
+
18
+ # Path for managing containers on the CDN management server
19
+ attr_accessor :cdnmgmtpath
20
+
21
+ # Port number for the CDN server
22
+ attr_accessor :cdnmgmtport
23
+
24
+ # URI scheme for the CDN server
25
+ attr_accessor :cdnmgmtscheme
26
+
27
+ # Hostname of the storage server
28
+ attr_accessor :storagehost
29
+
30
+ # Path for managing containers/objects on the storage server
31
+ attr_accessor :storagepath
32
+
33
+ # Port for managing the storage server
34
+ attr_accessor :storageport
35
+
36
+ # URI scheme for the storage server
37
+ attr_accessor :storagescheme
38
+
39
+ # Instance variable that is set when authorization succeeds
40
+ attr_accessor :authok
41
+
42
+ # The total size in bytes under this connection
43
+ attr_reader :bytes
44
+
45
+ # The total number of containers under this connection
46
+ attr_reader :count
47
+
48
+ # Optional proxy variables
49
+ attr_reader :proxy_host
50
+ attr_reader :proxy_port
51
+
52
+ # Creates a new CloudFiles::Connection object. Uses CloudFiles::Authentication to perform the login for the connection.
53
+ # The authuser is the Rackspace Cloud username, the authkey is the Rackspace Cloud API key.
54
+ #
55
+ # Setting the optional retry_auth variable to false will cause an exception to be thrown if your authorization token expires.
56
+ # Otherwise, it will attempt to reauthenticate.
57
+ #
58
+ # Setting the optional snet variable to true or setting an environment variable of RACKSPACE_SERVICENET to any value will cause
59
+ # storage URLs to be returned with a prefix pointing them to the internal Rackspace service network, instead of a public URL.
60
+ #
61
+ # This is useful if you are using the library on a Rackspace-hosted system, as it provides faster speeds, keeps traffic off of
62
+ # the public network, and the bandwidth is not billed.
63
+ #
64
+ # This will likely be the base class for most operations.
65
+ #
66
+ # With gem 1.4.8, the connection style has changed. It is now a hash of arguments. Note that the proxy options are currently only
67
+ # supported in the new style.
68
+ #
69
+ # cf = CloudFiles::Connection.new(:username => "MY_USERNAME", :api_key => "MY_API_KEY", :retry_auth => true, :snet => false, :proxy_host => "localhost", :proxy_port => "1234")
70
+ #
71
+ # The old style (positional arguments) is deprecated and will be removed at some point in the future.
72
+ #
73
+ # cf = CloudFiles::Connection.new(MY_USERNAME, MY_API_KEY, RETRY_AUTH, USE_SNET)
74
+ def initialize(*args)
75
+ if args[0].is_a?(Hash)
76
+ options = args[0]
77
+ @authuser = options[:username] ||( raise AuthenticationException, "Must supply a :username")
78
+ @authkey = options[:api_key] || (raise AuthenticationException, "Must supply an :api_key")
79
+ @retry_auth = options[:retry_auth] || true
80
+ @snet = ENV['RACKSPACE_SERVICENET'] || options[:snet]
81
+ @proxy_host = options[:proxy_host]
82
+ @proxy_port = options[:proxy_port]
83
+ elsif args[0].is_a?(String)
84
+ @authuser = args[0] ||( raise AuthenticationException, "Must supply the username as the first argument")
85
+ @authkey = args[1] || (raise AuthenticationException, "Must supply the API key as the second argument")
86
+ @retry_auth = args[2] || true
87
+ @snet = (ENV['RACKSPACE_SERVICENET'] || args[3]) ? true : false
88
+ end
89
+ @authok = false
90
+ @http = {}
91
+ CloudFiles::Authentication.new(self)
92
+ end
93
+
94
+ # Returns true if the authentication was successful and returns false otherwise.
95
+ #
96
+ # cf.authok?
97
+ # => true
98
+ def authok?
99
+ @authok
100
+ end
101
+
102
+ # Returns true if the library is requesting the use of the Rackspace service network
103
+ def snet?
104
+ @snet
105
+ end
106
+
107
+ # Returns an CloudFiles::Container object that can be manipulated easily. Throws a NoSuchContainerException if
108
+ # the container doesn't exist.
109
+ #
110
+ # container = cf.container('test')
111
+ # container.count
112
+ # => 2
113
+ def container(name)
114
+ CloudFiles::Container.new(self,name)
115
+ end
116
+ alias :get_container :container
117
+
118
+ # Sets instance variables for the bytes of storage used for this account/connection, as well as the number of containers
119
+ # stored under the account. Returns a hash with :bytes and :count keys, and also sets the instance variables.
120
+ #
121
+ # cf.get_info
122
+ # => {:count=>8, :bytes=>42438527}
123
+ # cf.bytes
124
+ # => 42438527
125
+ def get_info
126
+ response = cfreq("HEAD",@storagehost,@storagepath,@storageport,@storagescheme)
127
+ raise InvalidResponseException, "Unable to obtain account size" unless (response.code == "204")
128
+ @bytes = response["x-account-bytes-used"].to_i
129
+ @count = response["x-account-container-count"].to_i
130
+ {:bytes => @bytes, :count => @count}
131
+ end
132
+
133
+ # Gathers a list of the containers that exist for the account and returns the list of container names
134
+ # as an array. If no containers exist, an empty array is returned. Throws an InvalidResponseException
135
+ # if the request fails.
136
+ #
137
+ # If you supply the optional limit and marker parameters, the call will return the number of containers
138
+ # specified in limit, starting after the object named in marker.
139
+ #
140
+ # cf.containers
141
+ # => ["backup", "Books", "cftest", "test", "video", "webpics"]
142
+ #
143
+ # cf.containers(2,'cftest')
144
+ # => ["test", "video"]
145
+ def containers(limit=0,marker="")
146
+ paramarr = []
147
+ paramarr << ["limit=#{URI.encode(limit.to_s).gsub(/&/,'%26')}"] if limit.to_i > 0
148
+ paramarr << ["offset=#{URI.encode(marker.to_s).gsub(/&/,'%26')}"] unless marker.to_s.empty?
149
+ paramstr = (paramarr.size > 0)? paramarr.join("&") : "" ;
150
+ response = cfreq("GET",@storagehost,"#{@storagepath}?#{paramstr}",@storageport,@storagescheme)
151
+ return [] if (response.code == "204")
152
+ raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
153
+ CloudFiles.lines(response.body)
154
+ end
155
+ alias :list_containers :containers
156
+
157
+ # Retrieves a list of containers on the account along with their sizes (in bytes) and counts of the objects
158
+ # held within them. If no containers exist, an empty hash is returned. Throws an InvalidResponseException
159
+ # if the request fails.
160
+ #
161
+ # If you supply the optional limit and marker parameters, the call will return the number of containers
162
+ # specified in limit, starting after the object named in marker.
163
+ #
164
+ # cf.containers_detail
165
+ # => { "container1" => { :bytes => "36543", :count => "146" },
166
+ # "container2" => { :bytes => "105943", :count => "25" } }
167
+ def containers_detail(limit=0,marker="")
168
+ paramarr = []
169
+ paramarr << ["limit=#{URI.encode(limit.to_s).gsub(/&/,'%26')}"] if limit.to_i > 0
170
+ paramarr << ["offset=#{URI.encode(marker.to_s).gsub(/&/,'%26')}"] unless marker.to_s.empty?
171
+ paramstr = (paramarr.size > 0)? paramarr.join("&") : "" ;
172
+ response = cfreq("GET",@storagehost,"#{@storagepath}?format=xml&#{paramstr}",@storageport,@storagescheme)
173
+ return {} if (response.code == "204")
174
+ raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
175
+ doc = REXML::Document.new(response.body)
176
+ detailhash = {}
177
+ doc.elements.each("account/container/") { |c|
178
+ detailhash[c.elements["name"].text] = { :bytes => c.elements["bytes"].text, :count => c.elements["count"].text }
179
+ }
180
+ doc = nil
181
+ return detailhash
182
+ end
183
+ alias :list_containers_info :containers_detail
184
+
185
+ # Returns true if the requested container exists and returns false otherwise.
186
+ #
187
+ # cf.container_exists?('good_container')
188
+ # => true
189
+ #
190
+ # cf.container_exists?('bad_container')
191
+ # => false
192
+ def container_exists?(containername)
193
+ response = cfreq("HEAD",@storagehost,"#{@storagepath}/#{URI.encode(containername).gsub(/&/,'%26')}",@storageport,@storagescheme)
194
+ return (response.code == "204")? true : false ;
195
+ end
196
+
197
+ # Creates a new container and returns the CloudFiles::Container object. Throws an InvalidResponseException if the
198
+ # request fails.
199
+ #
200
+ # Slash (/) and question mark (?) are invalid characters, and will be stripped out. The container name is limited to
201
+ # 256 characters or less.
202
+ #
203
+ # container = cf.create_container('new_container')
204
+ # container.name
205
+ # => "new_container"
206
+ #
207
+ # container = cf.create_container('bad/name')
208
+ # => SyntaxException: Container name cannot contain the characters '/' or '?'
209
+ def create_container(containername)
210
+ raise SyntaxException, "Container name cannot contain the characters '/' or '?'" if containername.match(/[\/\?]/)
211
+ raise SyntaxException, "Container name is limited to 256 characters" if containername.length > 256
212
+ response = cfreq("PUT",@storagehost,"#{@storagepath}/#{URI.encode(containername).gsub(/&/,'%26')}",@storageport,@storagescheme)
213
+ raise InvalidResponseException, "Unable to create container #{containername}" unless (response.code == "201" || response.code == "202")
214
+ CloudFiles::Container.new(self,containername)
215
+ end
216
+
217
+ # Deletes a container from the account. Throws a NonEmptyContainerException if the container still contains
218
+ # objects. Throws a NoSuchContainerException if the container doesn't exist.
219
+ #
220
+ # cf.delete_container('new_container')
221
+ # => true
222
+ #
223
+ # cf.delete_container('video')
224
+ # => NonEmptyContainerException: Container video is not empty
225
+ #
226
+ # cf.delete_container('nonexistent')
227
+ # => NoSuchContainerException: Container nonexistent does not exist
228
+ def delete_container(containername)
229
+ response = cfreq("DELETE",@storagehost,"#{@storagepath}/#{URI.encode(containername).gsub(/&/,'%26')}",@storageport,@storagescheme)
230
+ raise NonEmptyContainerException, "Container #{containername} is not empty" if (response.code == "409")
231
+ raise NoSuchContainerException, "Container #{containername} does not exist" unless (response.code == "204")
232
+ true
233
+ end
234
+
235
+ # Gathers a list of public (CDN-enabled) containers that exist for an account and returns the list of container names
236
+ # as an array. If no containers are public, an empty array is returned. Throws a InvalidResponseException if
237
+ # the request fails.
238
+ #
239
+ # If you pass the optional argument as true, it will only show containers that are CURRENTLY being shared on the CDN,
240
+ # as opposed to the default behavior which is to show all containers that have EVER been public.
241
+ #
242
+ # cf.public_containers
243
+ # => ["video", "webpics"]
244
+ def public_containers(enabled_only = false)
245
+ paramstr = enabled_only == true ? "enabled_only=true" : ""
246
+ response = cfreq("GET",@cdnmgmthost,"#{@cdnmgmtpath}?#{paramstr}",@cdnmgmtport,@cdnmgmtscheme)
247
+ return [] if (response.code == "204")
248
+ raise InvalidResponseException, "Invalid response code #{response.code}" unless (response.code == "200")
249
+ CloudFiles.lines(response.body)
250
+ end
251
+
252
+ # This method actually makes the HTTP calls out to the server
253
+ def cfreq(method,server,path,port,scheme,headers = {},data = nil,attempts = 0,&block) # :nodoc:
254
+ start = Time.now
255
+ headers['Transfer-Encoding'] = "chunked" if data.is_a?(IO)
256
+ hdrhash = headerprep(headers)
257
+ start_http(server,path,port,scheme,hdrhash)
258
+ request = Net::HTTP.const_get(method.to_s.capitalize).new(path,hdrhash)
259
+ if data
260
+ if data.respond_to?(:read)
261
+ request.body_stream = data
262
+ else
263
+ request.body = data
264
+ end
265
+ unless data.is_a?(IO)
266
+ request.content_length = data.respond_to?(:lstat) ? data.stat.size : data.size
267
+ end
268
+ else
269
+ request.content_length = 0
270
+ end
271
+ response = @http[server].request(request,&block)
272
+ raise ExpiredAuthTokenException if response.code == "401"
273
+ response
274
+ rescue Errno::EPIPE, Timeout::Error, Errno::EINVAL, EOFError
275
+ # Server closed the connection, retry
276
+ raise ConnectionException, "Unable to reconnect to #{server} after #{count} attempts" if attempts >= 5
277
+ attempts += 1
278
+ @http[server].finish
279
+ start_http(server,path,port,scheme,headers)
280
+ retry
281
+ rescue ExpiredAuthTokenException
282
+ raise ConnectionException, "Authentication token expired and you have requested not to retry" if @retry_auth == false
283
+ CloudFiles::Authentication.new(self)
284
+ retry
285
+ end
286
+
287
+ private
288
+
289
+ # Sets up standard HTTP headers
290
+ def headerprep(headers = {}) # :nodoc:
291
+ default_headers = {}
292
+ default_headers["X-Auth-Token"] = @authtoken if (authok? && @account.nil?)
293
+ default_headers["X-Storage-Token"] = @authtoken if (authok? && !@account.nil?)
294
+ default_headers["Connection"] = "Keep-Alive"
295
+ default_headers["User-Agent"] = "CloudFiles Ruby API #{VERSION}"
296
+ default_headers.merge(headers)
297
+ end
298
+
299
+ # Starts (or restarts) the HTTP connection
300
+ def start_http(server,path,port,scheme,headers) # :nodoc:
301
+ if (@http[server].nil?)
302
+ begin
303
+ @http[server] = Net::HTTP::Proxy(self.proxy_host, self.proxy_port).new(server,port)
304
+ if scheme == "https"
305
+ @http[server].use_ssl = true
306
+ @http[server].verify_mode = OpenSSL::SSL::VERIFY_NONE
307
+ end
308
+ @http[server].start
309
+ rescue
310
+ raise ConnectionException, "Unable to connect to #{server}"
311
+ end
312
+ end
313
+ end
314
+
315
+ end
316
+
317
+ end