stickler 2.0.2 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/.gitignore +1 -0
  2. data/HISTORY.asciidoc +17 -8
  3. data/LICENSE +13 -54
  4. data/README.asciidoc +29 -5
  5. data/Rakefile +11 -10
  6. data/TODO.asciidoc +6 -0
  7. data/bin/stickler +5 -2
  8. data/examples/fetch-a-gem +6 -0
  9. data/examples/gemcutter_repo.ru +1 -1
  10. data/lib/stickler.rb +1 -1
  11. data/lib/stickler/client.rb +1 -0
  12. data/lib/stickler/client/list.rb +30 -0
  13. data/lib/stickler/client/mirror.rb +5 -5
  14. data/lib/stickler/client/push.rb +1 -1
  15. data/lib/stickler/middleware/gemcutter.rb +2 -2
  16. data/lib/stickler/middleware/helpers.rb +37 -42
  17. data/lib/stickler/middleware/index.rb +51 -19
  18. data/lib/stickler/repository/index.rb +46 -9
  19. data/lib/stickler/repository/local.rb +75 -20
  20. data/lib/stickler/repository/null.rb +0 -2
  21. data/lib/stickler/repository/remote.rb +81 -64
  22. data/lib/stickler/repository/rubygems_authenticator.rb +3 -8
  23. data/lib/stickler/server.rb +1 -1
  24. data/lib/stickler/spec_lite.rb +13 -10
  25. data/lib/stickler/version.rb +2 -2
  26. data/man/stickler.asciidoc +3 -1
  27. data/spec/data/gems/foo-2.0.0a.gem +0 -0
  28. data/spec/index_spec_helpers.rb +71 -0
  29. data/spec/middleware/local_spec.rb +58 -10
  30. data/spec/middleware/not_found_spec.rb +1 -0
  31. data/spec/repository/index_spec.rb +15 -0
  32. data/spec/repository/local_spec.rb +20 -5
  33. data/spec/repository/remote_spec.rb +2 -3
  34. data/spec/spec.opts +1 -1
  35. data/spec/spec_helper.rb +8 -6
  36. data/spec/spec_lite_spec.rb +19 -6
  37. data/tasks/man.rake +1 -1
  38. metadata +74 -40
  39. data/spec/middleware/common_gem_server_helpers.rb +0 -69
  40. data/spec/middleware/index_spec.rb +0 -26
  41. data/spec/middleware/legacy_gem_server_behavior.rb +0 -31
  42. data/spec/middleware/modern_gem_server_behavior.rb +0 -22
@@ -7,27 +7,33 @@ require 'stickler/logable'
7
7
  require 'stickler/paths'
8
8
 
9
9
  module Stickler::Middleware
10
- # Index is a Rack middleware that passes all requests through except for those
11
- # matching these two urls:
10
+ # Index is a Rack middleware that passes all requests through except for the
11
+ # following urls:
12
12
  #
13
13
  # <b>/specs.#{Gem.marhsal_version}.gz</b>:: The [ name, version, platform ] index
14
14
  # of <b>all<b> the gems in the
15
15
  # entire repository
16
16
  #
17
- # <b>/latest_specs.#{Gem.marshal_version}.gz</b>:: The [ name, version, # platform ] index
17
+ # <b>/latest_specs.#{Gem.marshal_version}.gz</b>:: The [ name, version, platform ] index
18
18
  # of the <b>most recent</b> version of each
19
19
  # gem in the repository.
20
20
  #
21
- # For these 2 urls, it respond reponds with the summation of all the specs
22
- # that are in <tt>env['stickler.specs']</tt>. If there are no specs in that
23
- # environment variable, then it returns with an empty index.
21
+ # <b>/prerelease_specs.#{Gem.marshal-version}.gz</b>:: The [ name, version, platform ] index
22
+ # of the <b>prerelease</b> versions of the
23
+ # prerelease gems in the repository
24
+ #
25
+ # <b>/quick/Marshal.#{Gem.marshal_version}/*.gemspec.rz</b>:: The gemspec of each gem
26
+ #
27
+ # <b>/gems/*.gem</b>:: The actual .gem file to serve
28
+ #
29
+ # For the <b>specs</b> urls, it responds with the summation of all the specs
30
+ # that are in all the repositories served by stickler
24
31
  #
25
32
  # == Options
26
33
  #
27
34
  # This class is also the base class for all the other GemServer type
28
35
  # middlewares, so there is an optional behavior to NOT respond to the index
29
- # url requests and just append the spec, or latest_specs to
30
- # env['stickler.specs'] instead of serving the values out of there.
36
+ # url requests.
31
37
  #
32
38
  # <b>:serve_indexes</b>:: +true+ or +false+ it defaults to +true+. This
33
39
  # option is used when Index is used in a stack
@@ -72,7 +78,6 @@ module Stickler::Middleware
72
78
  end
73
79
 
74
80
  get '/' do
75
- append_specs
76
81
  if @serve_indexes then
77
82
  erb :index
78
83
  else
@@ -83,24 +88,29 @@ module Stickler::Middleware
83
88
  #
84
89
  # Respond to the requests for the <b>all gems</b> index
85
90
  #
86
- get %r{\A/specs.#{Gem.marshal_version}(\.gz)?\Z} do |with_compression|
87
- append_specs
88
- serve_indexes( with_compression )
91
+ get %r{\A/specs.#{Gem.marshal_version}(\.gz)?\Z} do |compression|
92
+ serve_indexes( released_specs, compression )
89
93
  end
90
94
 
91
95
  #
92
96
  # Respond to the requests for the <b>latest gems</b> index
93
97
  #
94
- get %r{\A/latest_specs.#{Gem.marshal_version}(\.gz)?\Z} do |with_compression|
95
- append_latest_specs
96
- serve_indexes( with_compression )
98
+ get %r{\A/latest_specs.#{Gem.marshal_version}(\.gz)?\Z} do |compression|
99
+ serve_indexes( latest_specs, compression)
100
+ end
101
+
102
+ #
103
+ # Respond to the request for the <b>pre release gems</b> index
104
+ #
105
+ get %r{\A/prerelease_specs.#{Gem.marshal_version}(\.gz)?\Z} do |compression|
106
+ serve_indexes( prerelease_specs, compression )
97
107
  end
98
108
 
99
109
  #
100
110
  # Serve the indexes up as the response if @serve_indexes is true. Otherwise
101
111
  # return false
102
112
  #
103
- def serve_indexes( with_compression = :none )
113
+ def serve_indexes( specs, with_compression = :none )
104
114
  if @serve_indexes then
105
115
  self.compression = to_compression_flag( with_compression )
106
116
  return marshalled_specs( specs )
@@ -128,8 +138,10 @@ module Stickler::Middleware
128
138
  # Serve up a gemspec. This is really only used by the child classes.
129
139
  # an Index instance will never have any gemspecs to return
130
140
  #
131
- get %r{\A/quick/Marshal.#{Gem.marshal_version}/(.*?)-([0-9.]+)(-.*?)?\.gemspec\.rz\Z} do
132
- name, version, platform, with_compression = *params[:captures]
141
+ # To support pre-releases the a-z has been added to the version
142
+ #
143
+ get %r{\A/quick/Marshal.#{Gem.marshal_version}/(.*?)-([0-9.]+[0-9a-z.]*)(-.*?)?\.gemspec\.rz\Z} do
144
+ name, version, platform = *params[:captures]
133
145
  spec = Stickler::SpecLite.new( name, version, platform )
134
146
  full_path = @repo.full_path_to_specification( spec )
135
147
  if full_path and File.exist?( full_path ) then
@@ -145,7 +157,27 @@ module Stickler::Middleware
145
157
  # everywhere
146
158
  #
147
159
  def marshalled_specs( spec_a )
148
- marshal( spec_a.collect { |s| s.to_rubygems_a } )
160
+ a = spec_a.collect { |s| s.to_rubygems_a }
161
+ marshal( optimize_specs( a ) )
162
+ end
163
+
164
+ #
165
+ # Optimize the specs marshalling by using identical objects as much as possible. this
166
+ # is take directly from RubyGems source code. See rubygems/indexer.rb
167
+ # #compact_specs
168
+ #
169
+ def optimize_specs( specs )
170
+ names = {}
171
+ versions = {}
172
+ platforms = {}
173
+
174
+ specs.collect do |(name, version, platform)|
175
+ names[name] = name unless names.include?( name )
176
+ versions[version] = version unless versions.include?( version )
177
+ platforms[platform] = platform unless platforms.include?( platform )
178
+
179
+ [ names[name], versions[version], platforms[platform] ]
180
+ end
149
181
  end
150
182
 
151
183
  def marshal( data )
@@ -18,11 +18,18 @@ module Stickler::Repository
18
18
  # The directory the specs live
19
19
  attr_reader :spec_dir
20
20
 
21
- #
21
+ # The last time the repository directory was modified
22
+ attr_reader :last_modified_time
23
+
24
+ # The number of entries in the spec directory
25
+ attr_reader :last_entry_count
22
26
 
23
27
  def initialize( spec_dir )
24
- @specs = []
25
- @spec_dir = spec_dir
28
+ @specs = []
29
+ @spec_dir = spec_dir
30
+ @last_modified_time = nil
31
+ @last_entry_count = nil
32
+ @spec_glob = File.join( @spec_dir, "*.gemspec" )
26
33
  load_specs
27
34
  end
28
35
 
@@ -31,9 +38,14 @@ module Stickler::Repository
31
38
  return @specs
32
39
  end
33
40
 
41
+ #
42
+ # return all the latest specs in the repository, do not include pre-release
43
+ # gems
44
+ #
34
45
  def latest_specs
35
46
  latest = {}
36
47
  specs.each do |s|
48
+ next if s.prerelease?
37
49
  if old_spec = latest[s.name] then
38
50
  if old_spec.version < s.version then
39
51
  latest[s.name] = s
@@ -45,25 +57,49 @@ module Stickler::Repository
45
57
  latest.values
46
58
  end
47
59
 
60
+ #
61
+ # return just the list of pre-release specs
62
+ #
63
+ def prerelease_specs
64
+ specs.select { |s| s.prerelease? }
65
+ end
66
+
67
+ #
68
+ # return just the list of release specs
69
+ #
70
+ def released_specs
71
+ specs.select { |s| not s.prerelease? }
72
+ end
73
+
48
74
  def load_specs
49
75
  load_specs_in_dir( self.spec_dir )
50
76
  end
51
77
 
52
78
  def reload_necessary?
53
- return true
54
- # return true unless @last_modified_time
55
- # current_modified_time = File.stat( self.spec_dir ).mtime
56
- # return (current_modified_time > @last_modified_time )
79
+ return true unless @last_modified_time
80
+ return true unless @last_entry_count
81
+ return true if (self.current_modified_time > @last_modified_time )
82
+ return true if (self.current_entry_count != @last_entry_count )
83
+ return false
57
84
  end
58
85
 
59
- def last_modified_time
86
+ def current_modified_time
60
87
  File.stat( self.spec_dir ).mtime
61
88
  end
62
89
 
90
+ def current_entry_count
91
+ Dir.glob( @spec_glob ).size
92
+ end
93
+
63
94
  def spec_dir=( d )
64
95
  raise Error, "#{d} is not a directory" unless File.directory?( d )
65
96
  @spec_dir = d
66
- @last_modified_time = File.stat( d ).mtime
97
+ update_reload_conditions
98
+ end
99
+
100
+ def update_reload_conditions
101
+ @last_modified_time = self.current_modified_time
102
+ @last_entry_count = self.current_entry_count
67
103
  end
68
104
 
69
105
  def load_specs_in_dir( spec_dir )
@@ -75,6 +111,7 @@ module Stickler::Repository
75
111
  next unless entry =~ /\.gemspec\Z/
76
112
  add_spec_from_file( File.join( spec_dir, entry ) )
77
113
  end
114
+ update_reload_conditions
78
115
  end
79
116
 
80
117
  def add_spec_from_file( path )
@@ -1,9 +1,11 @@
1
1
  require 'stickler/spec_lite'
2
+ require 'stickler/logable'
2
3
  require 'stickler/repository'
3
4
  require 'stickler/repository/api'
4
5
  require 'stickler/repository/index'
5
6
  require 'addressable/uri'
6
7
  require 'tempfile'
8
+ require 'forwardable'
7
9
 
8
10
  module Stickler::Repository
9
11
  #
@@ -18,6 +20,10 @@ module Stickler::Repository
18
20
  #
19
21
  class Local
20
22
  class Error < ::Stickler::Repository::Error; end
23
+ include Stickler::Logable
24
+
25
+ # The name to give to this repository
26
+ attr_reader :name
21
27
 
22
28
  # the root directory of the repository
23
29
  attr_reader :root_dir
@@ -34,13 +40,73 @@ module Stickler::Repository
34
40
  # the index of the repository
35
41
  attr_reader :index
36
42
 
37
- def initialize( root_dir )
43
+ # mutex for synchronizing local instance allocations
44
+ @mutex = Mutex.new
45
+
46
+ # The list of repos keyd by root_dir
47
+ @repos = Hash.new
48
+
49
+ #
50
+ # Return the list of all the Local repositories
51
+ #
52
+ def self.repos
53
+ return @repos
54
+ end
55
+
56
+ #
57
+ # Purge the list of local repos that are known. This is mainly used in
58
+ # testing.
59
+ #
60
+ def self.purge
61
+ @mutex.synchronize { @repos.clear }
62
+ end
63
+
64
+ #
65
+ # :call-seq:
66
+ # Local.new( '/tmp/repo' )
67
+ # Local.new( '/tmp/repo', "Temporary Repo" )
68
+ #
69
+ # Create a new Local repository. Local repository instances
70
+ # are shared if the +root_dir+ is the same. That is
71
+ #
72
+ # repo1 = Local.new( '/foo/bar' )
73
+ # repo2 = Local.new( '/foo/bar' )
74
+ #
75
+ # repo1 and repo2 will be references to the sname object
76
+ #
77
+ # If a new is called for an already existing repo, and the +name+
78
+ # of the repo is not nil, and different than the existing repo
79
+ # an exception is raised.
80
+ #
81
+ def self.new( root_dir, name = nil )
82
+ repo = nil
83
+ @mutex.synchronize do
84
+ local_key = File.expand_path( root_dir ) + File::SEPARATOR
85
+ repo = @repos[local_key]
86
+ if repo.nil? then
87
+ repo = super( local_key, name )
88
+ @repos[local_key] = repo
89
+ else
90
+ if name and (repo.name != name) then
91
+ raise Error, "A repository already exists for #{root_dir} with name has the name #{repo.name} which conflicts with the given name #{name}"
92
+ end
93
+ end
94
+ end
95
+ repo
96
+ end
97
+
98
+ def initialize( root_dir, name = nil )
38
99
  @root_dir = File.expand_path( root_dir ) + File::SEPARATOR
100
+ @name = name || @root_dir
39
101
  @gems_dir = File.join( @root_dir, 'gems/' )
40
102
  @specifications_dir = File.join( @root_dir, 'specifications/' )
41
103
  @temp_dir = File.join( @root_dir, "tmp/" )
42
- @index = ::Stickler::Repository::Index.new( @specifications_dir )
104
+
105
+ # setup the dirs before doing the index because the @specifications_dir
106
+ # may not exist yet.
43
107
  setup_dirs
108
+
109
+ @index = ::Stickler::Repository::Index.new( @specifications_dir )
44
110
  end
45
111
 
46
112
  #
@@ -66,25 +132,14 @@ module Stickler::Repository
66
132
  end
67
133
 
68
134
  #
69
- # A list of all the specs in the repo
135
+ # Forward some calls directly to the index
70
136
  #
71
- def specs
72
- @index.specs
73
- end
74
-
75
- #
76
- # A list of just the latests specs in the repo
77
- #
78
- def latest_specs
79
- @index.latest_specs
80
- end
81
-
82
- #
83
- # The last time this index was modified
84
- #
85
- def last_modified_time
86
- @index.last_modified_time
87
- end
137
+ extend Forwardable
138
+ def_delegators :@index, :specs,
139
+ :released_specs,
140
+ :latest_specs,
141
+ :prerelease_specs,
142
+ :last_modified_time
88
143
 
89
144
  #
90
145
  # See Api#search_for
@@ -45,8 +45,6 @@ module Stickler::Repository
45
45
  alias :latest_specs :empty_array
46
46
  alias :search_for :empty_array
47
47
 
48
- def
49
-
50
48
  def specs
51
49
  Array.new
52
50
  end
@@ -1,5 +1,6 @@
1
- require 'resourceful'
1
+ require 'excon'
2
2
  require 'stickler/repository'
3
+ require 'stickler/version'
3
4
  require 'stickler/repository/api'
4
5
  require 'stickler/repository/rubygems_authenticator'
5
6
  require 'stringio'
@@ -11,19 +12,13 @@ module ::Stickler::Repository
11
12
  # cutter api (push/yank/unyank). The legacy gem server api is not utilized.
12
13
  #
13
14
  class Remote
14
- # the http client
15
- attr_reader :http
16
15
 
17
- def initialize( repo_uri, options = {} )
18
- options[:authenticator] ||= Stickler::Repository::RubygemsAuthenticator.new
19
- options[:cache_manager] ||= Resourceful::InMemoryCacheManager.new
20
- if options.delete(:debug) then
21
- options[:logger] ||= Resourceful::StdOutLogger.new
22
- end
16
+ attr_reader :authenticator
23
17
 
24
- @uri = Addressable::URI.parse( ensure_http( ensure_trailing_slash( repo_uri ) ) )
25
- @http = Resourceful::HttpAccessor.new( options )
26
- @specs_list = nil
18
+ def initialize( repo_uri, options = {} )
19
+ @authenticator = options[:authenticator] || Stickler::Repository::RubygemsAuthenticator.new
20
+ @uri = Addressable::URI.parse( ensure_http( ensure_trailing_slash( repo_uri ) ) )
21
+ @specs_list = nil
27
22
  end
28
23
 
29
24
  #
@@ -80,13 +75,11 @@ module ::Stickler::Repository
80
75
  def push( path )
81
76
  spec = speclite_from_gem_file( path )
82
77
  raise Stickler::Repository::Error, "gem #{spec.full_name} already exists in remote repository" if remote_gem_file_exist?( spec )
83
- begin
84
- resp = push_resource.post( IO.read( path ) )
85
- rescue Resourceful::UnsuccessfulHttpRequestError => e
86
- msg = "Failure pushing #{path} to remote repository : response code => #{e.http_response.code}, response message => '#{e.http_response.body}'"
87
- raise Stickler::Repository::Error, msg
88
- end
78
+ resp = resource_request( push_resource, :body => IO.read( path ) )
89
79
  return spec
80
+ rescue Excon::Errors::Error => e
81
+ msg = "Failure pushing #{path} to remote repository : response code => #{e.response.status}, response message => '#{e.response.body}'"
82
+ raise Stickler::Repository::Error, msg
90
83
  end
91
84
 
92
85
  #
@@ -94,15 +87,11 @@ module ::Stickler::Repository
94
87
  #
95
88
  def yank( spec )
96
89
  return nil unless remote_gem_file_exist?( spec )
97
- begin
98
- form_data = Resourceful::UrlencodedFormData.new
99
- form_data.add( "gem_name", spec.name )
100
- form_data.add( "version", spec.version.to_s )
101
- yank_resource.request( :delete, form_data.read, {'Content-Type' => form_data.content_type } )
102
- return full_uri_to_gem( spec )
103
- rescue Resourceful::UnsuccessfulHttpRequestError => e
104
- raise Stickler::Repository::Error, "Failure yanking: #{e.inspect}"
105
- end
90
+ query = { :gem_name => spec.name, :version => spec.version.to_s }
91
+ resp = resource_request( yank_resource, :query => query )
92
+ return full_uri_to_gem( spec )
93
+ rescue Excon::Errors::Error => e
94
+ raise Stickler::Repository::Error, "Failure yanking: #{e.inspect}"
106
95
  end
107
96
 
108
97
  #
@@ -110,12 +99,10 @@ module ::Stickler::Repository
110
99
  #
111
100
  def delete( spec )
112
101
  return false unless remote_gem_file_exist?( spec )
113
- begin
114
- gem_resource( spec ).delete
115
- return true
116
- rescue Resourceful::UnsuccessfulHttpRequestError => e
117
- return false
118
- end
102
+ resource_request( gem_resource( spec ), :method => :delete )
103
+ return true
104
+ rescue Excon::Errors::Error => e
105
+ return false
119
106
  end
120
107
 
121
108
  #
@@ -123,22 +110,21 @@ module ::Stickler::Repository
123
110
  #
124
111
  def open( spec, &block )
125
112
  return nil unless remote_gem_file_exist?( spec )
126
- begin
127
- data = download_resource( gem_resource( spec ) )
128
- io = StringIO.new( data , "rb" )
129
- if block_given? then
130
- begin
131
- yield io
132
- ensure
133
- io.close
134
- end
135
- else
136
- return io
113
+ data = download_resource( gem_resource( spec ) )
114
+ io = StringIO.new( data , "rb" )
115
+ if block_given? then
116
+ begin
117
+ yield io
118
+ ensure
119
+ io.close
137
120
  end
138
- rescue Resourceful::UnsuccessfulHttpRequestError => e
139
- return nil
121
+ else
122
+ return io
140
123
  end
141
124
  nil
125
+ rescue Excon::Errors::Error => e
126
+ $stderr.puts e.inspect
127
+ return nil
142
128
  end
143
129
 
144
130
  private
@@ -162,7 +148,7 @@ module ::Stickler::Repository
162
148
  end
163
149
 
164
150
  def specs_list_resource
165
- @specs_list_resource ||= @http.resource( specs_list_uri )
151
+ @specs_list_resource ||= Excon.new( specs_list_uri.to_s, :method => :get, :expects => [200] )
166
152
  end
167
153
 
168
154
  def push_uri
@@ -170,7 +156,11 @@ module ::Stickler::Repository
170
156
  end
171
157
 
172
158
  def push_resource
173
- @push_resource ||= @http.resource( push_uri, { 'Content-Type' => 'application/octet-stream' } )
159
+ unless @push_resource then
160
+ params = { :method => :post, :headers => { 'Content-Type' => 'application/octet-stream' }, :expects => [ 201, 200 ] }
161
+ @push_resource = Excon.new( push_uri.to_s, params )
162
+ end
163
+ return @push_resource
174
164
  end
175
165
 
176
166
  def yank_uri
@@ -178,11 +168,17 @@ module ::Stickler::Repository
178
168
  end
179
169
 
180
170
  def yank_resource
181
- @yank_resource ||= @http.resource( yank_uri )
171
+ unless @yank_resource then
172
+ params = { :method => :delete,
173
+ :headers => { 'Content-Type' => 'application/x-www-form-urlencoded' },
174
+ :expects => [200] }
175
+ @yank_resource = Excon.new( yank_uri.to_s, params )
176
+ end
177
+ return @yank_resource
182
178
  end
183
179
 
184
180
  def gem_resource( spec )
185
- @http.resource( full_uri_to_gem( spec ) )
181
+ Excon.new( full_uri_to_gem( spec ), :method => :get, :expects => [200] )
186
182
  end
187
183
 
188
184
  def download_specs_list
@@ -198,15 +194,14 @@ module ::Stickler::Repository
198
194
  end
199
195
 
200
196
  def download_uri( uri )
201
- download_resource( http.resource( uri ) )
197
+ download_resource( Excon.new( uri ) )
202
198
  end
203
199
 
204
200
  def download_resource( resource )
205
- begin
206
- resource.get.body
207
- rescue Resourceful::UnsuccessfulHttpRequestError => e
208
- return false
209
- end
201
+ resource_request( resource, :method => :get, :expects => [200] ).body
202
+ rescue Excon::Errors::Error => e
203
+ puts e.inspect
204
+ return false
210
205
  end
211
206
 
212
207
  def remote_gem_file_exist?( spec )
@@ -215,14 +210,10 @@ module ::Stickler::Repository
215
210
  end
216
211
 
217
212
  def remote_uri_exist?( uri )
218
- begin
219
- # FIXME: bug in Resourceful that uses cached HEAD responses
220
- # to satisfy later GET requests.
221
- rc = http.resource( uri ).head( 'Cache-Control' => 'no-store' ).successful?
222
- return rc
223
- rescue Resourceful::UnsuccessfulHttpRequestError => e
224
- return false
225
- end
213
+ rc = resource_request( Excon.new( uri.to_s ), :method => :head, :expects => [200] )
214
+ return true
215
+ rescue Excon::Errors::Error => e
216
+ return false
226
217
  end
227
218
 
228
219
  def speclite_from_gem_file( path )
@@ -237,5 +228,31 @@ module ::Stickler::Repository
237
228
  def speclite_from_specification( spec )
238
229
  Stickler::SpecLite.new( spec.name, spec.version.to_s, spec.platform )
239
230
  end
231
+
232
+ def resource_request( resource, params = {} )
233
+ trys = 0
234
+ begin
235
+ resource.connection[:headers]['User-Agent'] = "Stickler Client v#{Stickler::VERSION}"
236
+ resource.connection[:headers].delete('Authorization')
237
+ if authenticator.handles?( resource.connection[:scheme], resource.connection[:host] ) then
238
+ resource.connection[:headers]['Authorization'] = authenticator.credentials
239
+ end
240
+ trys += 1
241
+ #puts "Making request #{resource.connection.inspect} with extra params #{params.inspect}"
242
+ resource.request( params )
243
+ rescue Excon::Errors::MovedPermanently, Excon::Errors::Found,
244
+ Excon::Errors::SeeOther, Excon::Errors::TemporaryRedirect => redirect
245
+ # follow a redirect, it is only allowable to follow redirects from a GET or
246
+ # HEAD request. Only follow a few times though.
247
+ raise redirect unless [ :get, :head ].include?( redirect.request[:method] )
248
+ raise redirect if trys > 5
249
+ #puts "Redirecting to #{redirect.response.headers['Location']}"
250
+ resource = Excon::Connection.new( redirect.response.headers['Location'],
251
+ { :headers => resource.connection[:headers],
252
+ :query => resource.connection[:headers],
253
+ :method => resource.connection[:method] } )
254
+ retry
255
+ end
256
+ end
240
257
  end
241
258
  end