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 +34 -0
- data/README.markdown +3 -5
- data/Rakefile +23 -75
- data/lib/resourceful/authentication_manager.rb +25 -3
- data/lib/resourceful/cache_manager.rb +113 -104
- data/lib/resourceful/http_accessor.rb +20 -5
- data/lib/resourceful/memcache_cache_manager.rb +85 -0
- data/lib/resourceful/net_http_adapter.rb +1 -0
- data/lib/resourceful/request.rb +12 -0
- data/lib/resourceful/resource.rb +13 -9
- data/lib/resourceful/response.rb +2 -1
- data/resourceful.gemspec +52 -0
- data/spec/acceptance_spec.rb +46 -1
- data/spec/resourceful/authentication_manager_spec.rb +49 -4
- data/spec/resourceful/cache_manager_spec.rb +79 -67
- data/spec/resourceful/http_accessor_spec.rb +55 -11
- data/spec/resourceful/memcache_cache_manager_spec.rb +111 -0
- data/spec/resourceful/options_interpreter_spec.rb +9 -1
- data/spec/resourceful/request_spec.rb +9 -2
- data/spec/resourceful/resource_spec.rb +52 -11
- data/spec/simple_http_server_shared_spec.rb +3 -4
- data/spec/simple_http_server_shared_spec_spec.rb +9 -0
- data/spec/spec_helper.rb +1 -2
- metadata +81 -32
- data/lib/resourceful/version.rb +0 -1
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
|
-
|
41
|
-
http.
|
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
|
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 '
|
4
|
-
require 'rake/rdoctask'
|
5
|
-
require 'spec/rake/spectask'
|
3
|
+
require 'echoe'
|
6
4
|
|
7
|
-
|
8
|
-
|
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
|
-
|
11
|
-
|
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
|
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.
|
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
|
-
|
72
|
-
|
73
|
-
end
|
25
|
+
desc 'Default: Run Specs'
|
26
|
+
task :default => :spec
|
74
27
|
|
75
|
-
desc
|
76
|
-
task :
|
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
|
-
|
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 "
|
86
|
-
|
87
|
-
|
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
|
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
|
78
|
+
return false unless challenge_header = challenge_response.header['WWW-Authenticate']
|
71
79
|
begin
|
72
|
-
challenge = HTTPAuth::Digest::Challenge.from_header(
|
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
|
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
|
-
#
|
12
|
-
#
|
12
|
+
# Finds a previously cached response to the provided request. The
|
13
|
+
# response returned may be stale.
|
13
14
|
#
|
14
|
-
# @param
|
15
|
-
# The request
|
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 <
|
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 <
|
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
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
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
|
-
#
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
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
|
-
|
139
|
-
|
140
|
-
#
|
141
|
-
#
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
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
|
-
|
160
|
-
|
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
|
-
|
178
|
+
header
|
179
|
+
end
|
171
180
|
|
172
|
-
end # class
|
181
|
+
end # class CacheEntry
|
173
182
|
|
174
183
|
end
|