resourceful 0.2

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/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ Copyright (c) 2008 Absolute-Performance, Inc
2
+ Copyright (c) 2008 Peter Williams
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining
5
+ a copy of this software and associated documentation files (the
6
+ "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish,
8
+ distribute, sublicense, and/or sell copies of the Software, and to
9
+ permit persons to whom the Software is furnished to do so, subject to
10
+ the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,86 @@
1
+ Resourceful
2
+ ===========
3
+
4
+ Resourceful provides a convenient Ruby API for making HTTP requests.
5
+
6
+ Features:
7
+
8
+ * GET, PUT, POST and DELETE HTTP requests
9
+ * HTTP Basic and Digest authentication
10
+ * HTTP Caching with pluggable backends
11
+ * Follow redirects based on the results of a callback
12
+
13
+ More Info
14
+ =========
15
+
16
+ * Source: [Github](http://github.com/paul/resourceful/tree/master)
17
+ * Bug Tracking: [Lighthouse](http://resourceful.lighthouseapp.com)
18
+ * Project Page: [Rubyforge](http://rubyforge.org/projects/resourceful/)
19
+ * Documentation: [API Docs](http://resourceful.rubyforge.org)
20
+
21
+ Examples
22
+ ========
23
+
24
+ Getting started
25
+ ---------------
26
+
27
+ gem install resourceful
28
+
29
+ Simplest example
30
+ ---------------
31
+
32
+ require 'resourceful'
33
+ http = Resourceful::HttpAccessor.new
34
+ resp = http.resource('http://rubyforge.org').get
35
+ puts resp.body
36
+
37
+ Get a page requiring HTTP Authentication
38
+ ----------------------------------------
39
+
40
+ basic_handler = Resourceful::BasicAuthenticator.new('My Realm', 'admin', 'secret')
41
+ http.auth_manager.add_auth_hander(basic_handler)
42
+ resp = http.resource('http://example.com/').get
43
+ puts resp.body
44
+
45
+ Redirection based on callback results
46
+ -------------------------------------
47
+
48
+ Resourceful will by default follow redirects on read requests (GET and HEAD), but not for
49
+ POST, etc. If you want to follow a redirect after a post, you will need to set the resource#on_redirect
50
+ callback. If the callback evaluates to true, it will follow the redirect.
51
+
52
+ resource = http.resource('http://example.com/redirect_me')
53
+ resource.on_redirect { |req, resp| resp.header['Location'] =~ /example.com/ }
54
+ resource.get # Will only follow the redirect if the new location is example.com
55
+
56
+
57
+
58
+ Post a URL encoded form
59
+ -----------------------
60
+
61
+ require 'resourceful'
62
+ http = Resourceful::HttpAccessor.new
63
+ resp = http.resource('http://mysite.example/service').
64
+ post('hostname=test&level=super', :content_type => 'application/x-www-form-urlencoded')
65
+
66
+ Put an XML document
67
+ -------------------
68
+
69
+ require 'resourceful'
70
+ http = Resourceful::HttpAccessor.new
71
+ resp = http.resource('http://mysite.example/service').
72
+ put('<?xml version="1.0"?><test/>', :content_type => 'application/xml')
73
+
74
+ Delete a resource
75
+ -----------------
76
+
77
+ require 'resourceful'
78
+ http = Resourceful::HttpAccessor.new
79
+ resp = http.resource('http://mysite.example/service').delete
80
+
81
+
82
+ Copyright
83
+ ---------
84
+
85
+ Copyright (c) 2008 Absolute Performance, Inc, Peter Williams; released under the MIT License.
86
+
data/Rakefile ADDED
@@ -0,0 +1,89 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'rake/gempackagetask'
4
+ require 'rake/rdoctask'
5
+ require 'spec/rake/spectask'
6
+
7
+ desc 'Default: run unit tests.'
8
+ task :default => :spec
9
+
10
+ desc "Run all tests"
11
+ task :test => :spec
12
+
13
+ desc "Verify Resourceful against it's specs"
14
+ Spec::Rake::SpecTask.new(:spec) do |t|
15
+ t.libs << 'lib'
16
+ t.pattern = 'spec/**/*_spec.rb'
17
+ end
18
+
19
+ begin
20
+ gem 'yard', '>=0.2.3'
21
+ require 'yard'
22
+ desc 'Generate documentation for Resourceful.'
23
+ YARD::Rake::YardocTask.new do |t|
24
+ t.files = ['lib/**/*.rb', 'README']
25
+ end
26
+ rescue Exception
27
+ # install YARD to generate documentation
28
+ end
29
+
30
+ desc 'Removes all temporary files'
31
+ task :clean
32
+
33
+ ##############################################################################
34
+ # Packaging & Installation
35
+ ##############################################################################
36
+
37
+ RESOURCEFUL_VERSION = "0.2"
38
+
39
+ windows = (PLATFORM =~ /win32|cygwin/) rescue nil
40
+
41
+ SUDO = windows ? "" : "sudo"
42
+
43
+ task :resourceful => [:clean, :rdoc, :package]
44
+
45
+ spec = Gem::Specification.new do |s|
46
+ s.name = "resourceful"
47
+ s.version = RESOURCEFUL_VERSION
48
+ s.platform = Gem::Platform::RUBY
49
+ s.author = "Paul Sadauskas & Peter Williams"
50
+ s.email = "psadauskas@gmail.com"
51
+ s.homepage = "https://github.com/paul/resourceful/tree/master"
52
+ s.summary = "Resourceful provides a convenient Ruby API for making HTTP requests."
53
+ s.description = s.summary
54
+ s.rubyforge_project = 'resourceful'
55
+ s.require_path = "lib"
56
+ s.files = %w( MIT-LICENSE README.markdown Rakefile ) + Dir["{spec,lib}/**/*"]
57
+
58
+ # rdoc
59
+ s.has_rdoc = false
60
+
61
+ # Dependencies
62
+ s.add_dependency "addressable"
63
+ s.add_dependency "httpauth"
64
+ s.add_dependency "rspec"
65
+ s.add_dependency "thin"
66
+ s.add_dependency "facets"
67
+
68
+ s.required_ruby_version = ">= 1.8.6"
69
+ end
70
+
71
+ Rake::GemPackageTask.new(spec) do |package|
72
+ package.gem_spec = spec
73
+ end
74
+
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
79
+
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
84
+
85
+ desc "Run :clean and uninstall the .gem"
86
+ task :uninstall => :clean do
87
+ sh %{#{SUDO} gem uninstall resourceful}
88
+ end
89
+
@@ -0,0 +1,28 @@
1
+ require 'resourceful/http_accessor'
2
+
3
+ require 'resourceful/util'
4
+
5
+ # Resourceful is a library that provides a high level HTTP interface.
6
+ module Resourceful
7
+
8
+ HOP_BY_HOP_HEADERS = %w{
9
+ Connection
10
+ Keep-Alive
11
+ Proxy-Authenticate
12
+ Proxy-Authorization
13
+ TE
14
+ Trailers
15
+ Transfer-Encoding
16
+ Upgrade
17
+ }
18
+
19
+ NON_MODIFIABLE_HEADERS = %w{
20
+ Content-Location
21
+ Content-MD5
22
+ ETag
23
+ Last-Modified
24
+ Expires
25
+ }
26
+
27
+
28
+ end
@@ -0,0 +1,85 @@
1
+ require 'rubygems'
2
+ require 'httpauth'
3
+ require 'addressable/uri'
4
+
5
+ module Resourceful
6
+
7
+ class AuthenticationManager
8
+ def initialize
9
+ @authenticators = []
10
+ end
11
+
12
+ def add_auth_handler(authenticator)
13
+ @authenticators << authenticator
14
+ end
15
+
16
+ def associate_auth_info(challenge)
17
+ @authenticators.each do |authenticator|
18
+ authenticator.update_credentials(challenge) if authenticator.valid_for?(challenge)
19
+ end
20
+ end
21
+
22
+ def add_credentials(request)
23
+ authenticator = @authenticators.find { |authenticator| authenticator.can_handle?(request) }
24
+ authenticator.add_credentials_to(request) if authenticator
25
+ end
26
+
27
+ end
28
+
29
+ class BasicAuthenticator
30
+
31
+ def initialize(realm, username, password)
32
+ @realm, @username, @password = realm, username, password
33
+ @domain = nil
34
+ end
35
+
36
+ def valid_for?(challenge_response)
37
+ return false unless challenge_response.header['WWW-Authenticate']
38
+
39
+ !challenge_response.header['WWW-Authenticate'].grep(/^\s*basic/i).find do |a_challenge|
40
+ @realm.downcase == /realm="([^"]+)"/i.match(a_challenge)[1].downcase
41
+ end.nil?
42
+ end
43
+
44
+ def update_credentials(challenge)
45
+ @domain = Addressable::URI.parse(challenge.uri).host
46
+ end
47
+
48
+ def can_handle?(request)
49
+ Addressable::URI.parse(request.uri).host == @domain
50
+ end
51
+
52
+ def add_credentials_to(request)
53
+ request.header['Authorization'] = credentials
54
+ end
55
+
56
+ def credentials
57
+ HTTPAuth::Basic.pack_authorization(@username, @password)
58
+ end
59
+
60
+ end
61
+
62
+ class DigestAuthenticator
63
+
64
+ def initialize(realm, username, password)
65
+ @realm, @username, @password = realm, username, password
66
+ @domain = nil
67
+ end
68
+
69
+ def valid_for?(challenge_response)
70
+ return false unless challenge = challenge_response.header['WWW-Authenticate']
71
+ begin
72
+ challenge = HTTPAuth::Digest::Challenge.from_header(challenge.first)
73
+ rescue HTTPAuth::UnwellformedHeader
74
+ return false
75
+ end
76
+ challenge.realm == @realm
77
+ end
78
+
79
+
80
+
81
+ end
82
+
83
+
84
+ end
85
+
@@ -0,0 +1,160 @@
1
+ require 'resourceful/header'
2
+
3
+ module Resourceful
4
+
5
+ class CacheManager
6
+ def initialize
7
+ raise NotImplementedError,
8
+ "Use one of CacheManager's child classes instead. Try NullCacheManager if you don't want any caching at all."
9
+ end
10
+
11
+ # Search for a cached representation that can be used to fulfill the
12
+ # the given request. If none are found, returns nil.
13
+ #
14
+ # @param request<Resourceful::Request>
15
+ # The request to use for searching.
16
+ def lookup(request); end
17
+
18
+ # Store a response in the cache.
19
+ #
20
+ # This method is smart enough to not store responses that cannot be
21
+ # cached (Vary: * or Cache-Control: no-cache, private, ...)
22
+ #
23
+ # @param request<Resourceful::Request>
24
+ # The request used to obtain the response. This is needed so the
25
+ # values from the response's Vary header can be stored.
26
+ # @param response<Resourceful::Response>
27
+ # The response to be stored.
28
+ def store(request, response); end
29
+
30
+ # Selects the headers from the request named by the response's Vary header
31
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.5.6
32
+ #
33
+ # @param request<Resourceful::Request>
34
+ # The request used to obtain the response.
35
+ # @param response<Resourceful::Response>
36
+ # The response obtained from the request.
37
+ def select_request_headers(request, response)
38
+ header = Resourceful::Header.new
39
+
40
+ response.header['Vary'].each do |name|
41
+ header[name] = request.header[name]
42
+ end if response.header['Vary']
43
+
44
+ header
45
+ end
46
+ end
47
+
48
+ # This is the default cache, and does not do any caching. All lookups
49
+ # result in nil, and all attempts to store a response are a no-op.
50
+ class NullCacheManager < CacheManager
51
+ def initialize; end
52
+
53
+ def lookup(request)
54
+ nil
55
+ end
56
+
57
+ def store(request, response); end
58
+ end
59
+
60
+ # This is a nieve implementation of caching. Unused entries are never
61
+ # removed, and this may eventually eat up all your memory and cause your
62
+ # machine to explode.
63
+ class InMemoryCacheManager < CacheManager
64
+
65
+ def initialize
66
+ @collection = Hash.new(CacheEntryCollection.new)
67
+ end
68
+
69
+ def lookup(request)
70
+ entry = @collection[request.resource.uri][request]
71
+ response = entry.response if entry
72
+ response.authoritative = false if response
73
+
74
+ response
75
+ end
76
+
77
+ def store(request, response)
78
+ return unless response.cachable?
79
+
80
+ entry = CacheEntry.new(request.request_time,
81
+ select_request_headers(request, response),
82
+ response)
83
+
84
+ @collection[request.resource.uri][request] = entry
85
+ end
86
+
87
+ # The collection of all cached entries for a single resource (uri).
88
+ class CacheEntryCollection
89
+ include Enumerable
90
+
91
+ def initialize
92
+ @entries = []
93
+ end
94
+
95
+ # Iterates over the entries. Needed for Enumerable
96
+ def each(&block)
97
+ @entries.each(&block)
98
+ end
99
+
100
+ # Looks of a Entry that could fullfil the request. Returns nil if none
101
+ # was found.
102
+ #
103
+ # @param request<Resourceful::Request>
104
+ # The request to use for the lookup.
105
+ def [](request)
106
+ self.each do |entry|
107
+ return entry if entry.valid_for?(request)
108
+ end
109
+ return nil
110
+ end
111
+
112
+ # Saves an entry into the collection. Replaces any existing ones that could
113
+ # be used with the updated response.
114
+ #
115
+ # @param request<Resourceful::Request>
116
+ # The request that was used to obtain the response
117
+ # @param cache_entry<CacheEntry>
118
+ # The cache_entry generated from response that was obtained.
119
+ def []=(request, cache_entry)
120
+ @entries.delete_if { |e| e.valid_for?(request) }
121
+ @entries.unshift cache_entry
122
+ end
123
+
124
+ end # class CacheEntryCollection
125
+
126
+ # Contains everything we need to know to build a response for a request using the
127
+ # stored request.
128
+ class CacheEntry
129
+ # request_vary_headers is a HttpHeader with keys from the
130
+ # Vary header of the response, plus the values from the matching
131
+ # fields in the request
132
+ attr_accessor :request_time, :request_vary_headers, :response
133
+
134
+ # @param request_time<Time>
135
+ # Client-generated timestamp for when the request was made
136
+ # @param request_vary_headers<Resourceful::HttpHeader>
137
+ # A HttpHeader constructed from the keys listed in the vary headers
138
+ # of the response, and values obtained from those headers in the request
139
+ # @param response<Resourceful::Response>
140
+ # The Response obhect to be stored.
141
+ def initialize(request_time, request_vary_headers, response)
142
+ @request_time, @request_vary_headers, @response = request_time, request_vary_headers, response
143
+ end
144
+
145
+ # Returns true if this entry may be used to fullfil the given request,
146
+ # according to the vary headers.
147
+ #
148
+ # @param request<Resourceful::Request>
149
+ # The request to do the lookup on.
150
+ def valid_for?(request)
151
+ @request_vary_headers.all? do |key, value|
152
+ request.header[key] == value
153
+ end
154
+ end
155
+
156
+ end # class CacheEntry
157
+
158
+ end # class InMemoryCacheManager
159
+
160
+ end