resourceful 0.2.1 → 0.3.0

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.
data/Manifest ADDED
@@ -0,0 +1,34 @@
1
+ lib/resourceful.rb
2
+ lib/resourceful/authentication_manager.rb
3
+ lib/resourceful/util.rb
4
+ lib/resourceful/resource.rb
5
+ lib/resourceful/memcache_cache_manager.rb
6
+ lib/resourceful/net_http_adapter.rb
7
+ lib/resourceful/http_accessor.rb
8
+ lib/resourceful/stubbed_resource_proxy.rb
9
+ lib/resourceful/header.rb
10
+ lib/resourceful/cache_manager.rb
11
+ lib/resourceful/options_interpreter.rb
12
+ lib/resourceful/response.rb
13
+ lib/resourceful/request.rb
14
+ spec/acceptance_shared_specs.rb
15
+ spec/spec.opts
16
+ spec/acceptance_spec.rb
17
+ spec/simple_http_server_shared_spec_spec.rb
18
+ spec/spec_helper.rb
19
+ spec/resourceful/header_spec.rb
20
+ spec/resourceful/authentication_manager_spec.rb
21
+ spec/resourceful/memcache_cache_manager_spec.rb
22
+ spec/resourceful/response_spec.rb
23
+ spec/resourceful/options_interpreter_spec.rb
24
+ spec/resourceful/http_accessor_spec.rb
25
+ spec/resourceful/stubbed_resource_proxy_spec.rb
26
+ spec/resourceful/request_spec.rb
27
+ spec/resourceful/resource_spec.rb
28
+ spec/resourceful/cache_manager_spec.rb
29
+ spec/resourceful/net_http_adapter_spec.rb
30
+ spec/simple_http_server_shared_spec.rb
31
+ Manifest
32
+ Rakefile
33
+ README.markdown
34
+ MIT-LICENSE
data/README.markdown CHANGED
@@ -37,8 +37,8 @@ Simplest example
37
37
  Get a page requiring HTTP Authentication
38
38
  ----------------------------------------
39
39
 
40
- basic_handler = Resourceful::BasicAuthenticator.new('My Realm', 'admin', 'secret')
41
- http.auth_manager.add_auth_hander(basic_handler)
40
+ my_realm_authenticator = Resourceful::BasicAuthenticator.new('My Realm', 'admin', 'secret')
41
+ http = Resourceful::HttpAccessor.new(:authenticator => my_realm_authenticator)
42
42
  resp = http.resource('http://example.com/').get
43
43
  puts resp.body
44
44
 
@@ -53,8 +53,6 @@ callback. If the callback evaluates to true, it will follow the redirect.
53
53
  resource.on_redirect { |req, resp| resp.header['Location'] =~ /example.com/ }
54
54
  resource.get # Will only follow the redirect if the new location is example.com
55
55
 
56
-
57
-
58
56
  Post a URL encoded form
59
57
  -----------------------
60
58
 
@@ -82,5 +80,5 @@ Delete a resource
82
80
  Copyright
83
81
  ---------
84
82
 
85
- Copyright (c) 2008 Absolute Performance, Inc, Peter Williams; released under the MIT License.
83
+ Copyright (c) 2008 Absolute Performance, Inc, Peter Williams. Released under the MIT License.
86
84
 
data/Rakefile CHANGED
@@ -1,93 +1,41 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
- require 'rake/gempackagetask'
4
- require 'rake/rdoctask'
5
- require 'spec/rake/spectask'
3
+ require 'echoe'
6
4
 
7
- desc 'Default: run unit tests.'
8
- task :default => :spec
5
+ Echoe.new('resourceful', '0.3.0') do |p|
6
+ p.description = "An HTTP library for Ruby that takes advantage of everything HTTP has to offer."
7
+ p.url = "http://github.com/paul/resourceful"
8
+ p.author = "Paul Sadauskas"
9
+ p.email = "psadauskas@gmail.com"
9
10
 
10
- desc "Run all tests"
11
- task :test => :spec
11
+ p.ignore_pattern = ["pkg/*", "tmp/*"]
12
+ p.dependencies = ['addressable', 'httpauth', 'rspec', 'facets', 'andand']
13
+ p.development_dependencies = ['thin', 'yard']
14
+ end
15
+
16
+ require 'spec/rake/spectask'
12
17
 
13
- desc "Verify Resourceful against it's specs"
18
+ desc 'Run all specs'
14
19
  Spec::Rake::SpecTask.new(:spec) do |t|
15
20
  t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
16
21
  t.libs << 'lib'
17
- t.pattern = 'spec/**/*_spec.rb'
18
- end
19
-
20
- begin
21
- gem 'yard', '>=0.2.3'
22
- require 'yard'
23
- desc 'Generate documentation for Resourceful.'
24
- YARD::Rake::YardocTask.new do |t|
25
- t.files = ['lib/**/*.rb', 'README']
26
- end
27
- rescue Exception
28
- # install YARD to generate documentation
29
- end
30
-
31
- desc 'Removes all temporary files'
32
- task :clean
33
-
34
- ##############################################################################
35
- # Packaging & Installation
36
- ##############################################################################
37
-
38
- RESOURCEFUL_VERSION = "0.2.1"
39
-
40
- windows = (PLATFORM =~ /win32|cygwin/) rescue nil
41
-
42
- SUDO = windows ? "" : "sudo"
43
-
44
- task :resourceful => [:clean, :rdoc, :package]
45
-
46
- spec = Gem::Specification.new do |s|
47
- s.name = "resourceful"
48
- s.version = RESOURCEFUL_VERSION
49
- s.platform = Gem::Platform::RUBY
50
- s.author = "Paul Sadauskas & Peter Williams"
51
- s.email = "psadauskas@gmail.com"
52
- s.homepage = "https://github.com/paul/resourceful/tree/master"
53
- s.summary = "Resourceful provides a convenient Ruby API for making HTTP requests."
54
- s.description = s.summary
55
- s.rubyforge_project = 'resourceful'
56
- s.require_path = "lib"
57
- s.files = %w( MIT-LICENSE README.markdown Rakefile ) + Dir["{spec,lib}/**/*"]
58
-
59
- # rdoc
60
- s.has_rdoc = false
61
-
62
- # Dependencies
63
- s.add_dependency "addressable"
64
- s.add_dependency "httpauth"
65
- s.add_dependency "rspec"
66
- s.add_dependency "facets"
67
-
68
- s.required_ruby_version = ">= 1.8.6"
22
+ t.spec_files = FileList['spec/**/*_spec.rb']
69
23
  end
70
24
 
71
- Rake::GemPackageTask.new(spec) do |package|
72
- package.gem_spec = spec
73
- end
25
+ desc 'Default: Run Specs'
26
+ task :default => :spec
74
27
 
75
- desc "Run :package and install the resulting .gem"
76
- task :install => :package do
77
- sh %{#{SUDO} gem install --local pkg/resourceful-#{RESOURCEFUL_VERSION}.gem --no-rdoc --no-ri}
78
- end
28
+ desc 'Run all tests'
29
+ task :test => :spec
79
30
 
80
- desc "Run :package and install the resulting .gem with jruby"
81
- task :jinstall => :package do
82
- sh %{#{SUDO} jruby -S gem install pkg/resourceful-#{RESOURCEFUL_VERSION}.gem --no-rdoc --no-ri}
83
- end
31
+ require 'yard'
84
32
 
85
- desc "Run :clean and uninstall the .gem"
86
- task :uninstall => :clean do
87
- sh %{#{SUDO} gem uninstall resourceful}
33
+ desc "Generate Yardoc"
34
+ YARD::Rake::YardocTask.new do |t|
35
+ t.files = ['lib/**/*.rb', 'README.markdown']
88
36
  end
89
37
 
90
38
  desc "Update rubyforge documentation"
91
- task :update_docs do
39
+ task :update_docs => :yardoc do
92
40
  puts %x{rsync -aPz doc/* psadauskas@resourceful.rubyforge.org:/var/www/gforge-projects/resourceful/}
93
41
  end
@@ -61,22 +61,44 @@ module Resourceful
61
61
 
62
62
  class DigestAuthenticator
63
63
 
64
+ attr_reader :username, :password, :realm, :domain, :challenge
65
+
64
66
  def initialize(realm, username, password)
65
- @realm, @username, @password = realm, username, password
67
+ @realm = realm
68
+ @username, @password = username, password
66
69
  @domain = nil
67
70
  end
68
71
 
72
+ def update_credentials(challenge_response)
73
+ @domain = Addressable::URI.parse(challenge_response.uri).host
74
+ @challenge = HTTPAuth::Digest::Challenge.from_header(challenge_response.header['WWW-Authenticate'].first)
75
+ end
76
+
69
77
  def valid_for?(challenge_response)
70
- return false unless challenge = challenge_response.header['WWW-Authenticate']
78
+ return false unless challenge_header = challenge_response.header['WWW-Authenticate']
71
79
  begin
72
- challenge = HTTPAuth::Digest::Challenge.from_header(challenge.first)
80
+ challenge = HTTPAuth::Digest::Challenge.from_header(challenge_header.first)
73
81
  rescue HTTPAuth::UnwellformedHeader
74
82
  return false
75
83
  end
76
84
  challenge.realm == @realm
77
85
  end
78
86
 
87
+ def can_handle?(request)
88
+ Addressable::URI.parse(request.uri).host == @domain
89
+ end
79
90
 
91
+ def add_credentials_to(request)
92
+ request.header['Authorization'] = credentials_for(request)
93
+ end
94
+
95
+ def credentials_for(request)
96
+ HTTPAuth::Digest::Credentials.from_challenge(@challenge,
97
+ :username => @username,
98
+ :password => @password,
99
+ :method => request.method.to_s.upcase,
100
+ :uri => Addressable::URI.parse(request.uri).path).to_header
101
+ end
80
102
 
81
103
  end
82
104
 
@@ -1,20 +1,24 @@
1
1
  require 'resourceful/header'
2
+ require 'andand'
2
3
 
3
4
  module Resourceful
4
5
 
5
- class CacheManager
6
+ class AbstractCacheManager
6
7
  def initialize
7
8
  raise NotImplementedError,
8
9
  "Use one of CacheManager's child classes instead. Try NullCacheManager if you don't want any caching at all."
9
10
  end
10
11
 
11
- # Search for a cached representation that can be used to fulfill the
12
- # the given request. If none are found, returns nil.
12
+ # Finds a previously cached response to the provided request. The
13
+ # response returned may be stale.
13
14
  #
14
- # @param request<Resourceful::Request>
15
- # The request to use for searching.
15
+ # @param [Resourceful::Request] request
16
+ # The request for which we are looking for a response.
17
+ #
18
+ # @return [Resourceful::Response]
19
+ # A (possibly stale) response for the request provided.
16
20
  def lookup(request); end
17
-
21
+
18
22
  # Store a response in the cache.
19
23
  #
20
24
  # This method is smart enough to not store responses that cannot be
@@ -35,29 +39,11 @@ module Resourceful
35
39
  # @param uri<String>
36
40
  # The uri of the resource to be invalidated
37
41
  def invalidate(uri); end
38
-
39
- # Selects the headers from the request named by the response's Vary header
40
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.6
41
- #
42
- # @param request<Resourceful::Request>
43
- # The request used to obtain the response.
44
- # @param response<Resourceful::Response>
45
- # The response obtained from the request.
46
- def select_request_headers(request, response)
47
- header = Resourceful::Header.new
48
-
49
- response.header['Vary'].first.split(',').each do |name|
50
- name.strip!
51
- header[name] = request.header[name]
52
- end if response.header['Vary']
53
-
54
- header
55
- end
56
42
  end
57
43
 
58
44
  # This is the default cache, and does not do any caching. All lookups
59
45
  # result in nil, and all attempts to store a response are a no-op.
60
- class NullCacheManager < CacheManager
46
+ class NullCacheManager < AbstractCacheManager
61
47
  def initialize; end
62
48
 
63
49
  def lookup(request)
@@ -70,105 +56,128 @@ module Resourceful
70
56
  # This is a nieve implementation of caching. Unused entries are never
71
57
  # removed, and this may eventually eat up all your memory and cause your
72
58
  # machine to explode.
73
- class InMemoryCacheManager < CacheManager
59
+ class InMemoryCacheManager < AbstractCacheManager
74
60
 
75
61
  def initialize
76
62
  @collection = Hash.new{ |h,k| h[k] = CacheEntryCollection.new}
77
63
  end
78
64
 
79
65
  def lookup(request)
80
- entry = @collection[request.uri.to_s][request]
81
- response = entry.response if entry
82
- response.authoritative = false if response
83
-
84
- response
66
+ returning(@collection[request.uri.to_s][request]) do |response|
67
+ response.authoritative = false if response
68
+ end
85
69
  end
86
70
 
87
71
  def store(request, response)
88
72
  return unless response.cachable?
89
73
 
90
- entry = CacheEntry.new(request.request_time,
91
- select_request_headers(request, response),
92
- response)
93
-
94
- @collection[request.uri.to_s][request] = entry
74
+ @collection[request.uri.to_s][request] = response
95
75
  end
96
76
 
97
77
  def invalidate(uri)
98
78
  @collection.delete(uri)
99
79
  end
80
+ end # class InMemoryCacheManager
81
+
82
+ # The collection of cached entries. Nominally all the entry in a
83
+ # collection of this sort will be for the same resource but that is
84
+ # not required to be true.
85
+ class CacheEntryCollection
86
+ include Enumerable
87
+
88
+ def initialize
89
+ @entries = []
90
+ end
91
+
92
+ # Iterates over the entries. Needed for Enumerable
93
+ def each(&block)
94
+ @entries.each(&block)
95
+ end
96
+
97
+ # Looks of a Entry that could fullfil the request. Returns nil if none
98
+ # was found.
99
+ #
100
+ # @param [Resourceful::Request] request
101
+ # The request to use for the lookup.
102
+ #
103
+ # @return [Resourceful::Response]
104
+ # The cached response for the specified request if one is available.
105
+ def [](request)
106
+ find { |an_entry| an_entry.valid_for?(request) }.andand.response
107
+ end
108
+
109
+ # Saves an entry into the collection. Replaces any existing ones that could
110
+ # be used with the updated response.
111
+ #
112
+ # @param [Resourceful::Request] request
113
+ # The request that was used to obtain the response
114
+ # @param [Resourceful::Response] response
115
+ # The cache_entry generated from response that was obtained.
116
+ def []=(request, response)
117
+ @entries.delete_if { |e| e.valid_for?(request) }
118
+ @entries << CacheEntry.new(request, response)
119
+
120
+ response
121
+ end
122
+ end # class CacheEntryCollection
123
+
124
+ # Represents a previous request and cached response with enough
125
+ # detail to determine construct a cached response to a matching
126
+ # request in the future. It also understands what a matching
127
+ # request means.
128
+ class CacheEntry
129
+ # request_vary_headers is a HttpHeader with keys from the Vary
130
+ # header of the response, plus the values from the matching fields
131
+ # in the request
132
+ attr_reader :request_vary_headers
133
+
134
+ # The time at which the client believes the request was made.
135
+ attr_reader :request_time
136
+
137
+ # The URI of the request
138
+ attr_reader :request_uri
139
+
140
+ # The response to that we are caching
141
+ attr_reader :response
142
+
143
+ # @param [Resourceful::Request] request
144
+ # The request whose response we are storing in the cache.
145
+ # @param response<Resourceful::Response>
146
+ # The Response obhect to be stored.
147
+ def initialize(request, response)
148
+ @request_uri = request.uri
149
+ @request_time = request.request_time
150
+ @request_vary_headers = select_request_headers(request, response)
151
+ @response = response
152
+ end
100
153
 
101
- # The collection of all cached entries for a single resource (uri).
102
- class CacheEntryCollection
103
- include Enumerable
104
-
105
- def initialize
106
- @entries = []
107
- end
108
-
109
- # Iterates over the entries. Needed for Enumerable
110
- def each(&block)
111
- @entries.each(&block)
112
- end
113
-
114
- # Looks of a Entry that could fullfil the request. Returns nil if none
115
- # was found.
116
- #
117
- # @param request<Resourceful::Request>
118
- # The request to use for the lookup.
119
- def [](request)
120
- self.each do |entry|
121
- return entry if entry.valid_for?(request)
122
- end
123
- return nil
124
- end
125
-
126
- # Saves an entry into the collection. Replaces any existing ones that could
127
- # be used with the updated response.
128
- #
129
- # @param request<Resourceful::Request>
130
- # The request that was used to obtain the response
131
- # @param cache_entry<CacheEntry>
132
- # The cache_entry generated from response that was obtained.
133
- def []=(request, cache_entry)
134
- @entries.delete_if { |e| e.valid_for?(request) }
135
- @entries.unshift cache_entry
136
- end
154
+ # Returns true if this entry may be used to fullfil the given request,
155
+ # according to the vary headers.
156
+ #
157
+ # @param request<Resourceful::Request>
158
+ # The request to do the lookup on.
159
+ def valid_for?(request)
160
+ request.uri == @request_uri and
161
+ @request_vary_headers.all? {|key, value| request.header[key] == value}
162
+ end
137
163
 
138
- end # class CacheEntryCollection
139
-
140
- # Contains everything we need to know to build a response for a request using the
141
- # stored request.
142
- class CacheEntry
143
- # request_vary_headers is a HttpHeader with keys from the
144
- # Vary header of the response, plus the values from the matching
145
- # fields in the request
146
- attr_accessor :request_time, :request_vary_headers, :response
147
-
148
- # @param request_time<Time>
149
- # Client-generated timestamp for when the request was made
150
- # @param request_vary_headers<Resourceful::HttpHeader>
151
- # A HttpHeader constructed from the keys listed in the vary headers
152
- # of the response, and values obtained from those headers in the request
153
- # @param response<Resourceful::Response>
154
- # The Response obhect to be stored.
155
- def initialize(request_time, request_vary_headers, response)
156
- @request_time, @request_vary_headers, @response = request_time, request_vary_headers, response
157
- end
164
+ # Selects the headers from the request named by the response's Vary header
165
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.6
166
+ #
167
+ # @param [Resourceful::Request] request
168
+ # The request used to obtain the response.
169
+ # @param [Resourceful::Response] response
170
+ # The response obtained from the request.
171
+ def select_request_headers(request, response)
172
+ header = Resourceful::Header.new
158
173
 
159
- # Returns true if this entry may be used to fullfil the given request,
160
- # according to the vary headers.
161
- #
162
- # @param request<Resourceful::Request>
163
- # The request to do the lookup on.
164
- def valid_for?(request)
165
- @request_vary_headers.all? do |key, value|
166
- request.header[key] == value
167
- end
168
- end
174
+ response.header['Vary'].each do |name|
175
+ header[name] = request.header[name]
176
+ end if response.header['Vary']
169
177
 
170
- end # class CacheEntry
178
+ header
179
+ end
171
180
 
172
- end # class InMemoryCacheManager
181
+ end # class CacheEntry
173
182
 
174
183
  end