resourceful 0.3.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest +24 -28
- data/Rakefile +44 -14
- data/lib/resourceful.rb +11 -21
- data/lib/resourceful/authentication_manager.rb +3 -2
- data/lib/resourceful/cache_manager.rb +58 -1
- data/lib/resourceful/exceptions.rb +34 -0
- data/lib/resourceful/header.rb +95 -0
- data/lib/resourceful/http_accessor.rb +0 -2
- data/lib/resourceful/memcache_cache_manager.rb +3 -13
- data/lib/resourceful/net_http_adapter.rb +15 -5
- data/lib/resourceful/request.rb +180 -18
- data/lib/resourceful/resource.rb +38 -141
- data/lib/resourceful/response.rb +142 -95
- data/resourceful.gemspec +9 -7
- data/spec/acceptance/authorization_spec.rb +16 -0
- data/spec/acceptance/caching_spec.rb +192 -0
- data/spec/acceptance/header_spec.rb +24 -0
- data/spec/acceptance/redirecting_spec.rb +12 -0
- data/spec/acceptance/resource_spec.rb +84 -0
- data/spec/acceptance_shared_specs.rb +12 -17
- data/spec/{acceptance_spec.rb → old_acceptance_specs.rb} +27 -57
- data/spec/simple_sinatra_server.rb +74 -0
- data/spec/simple_sinatra_server_spec.rb +98 -0
- data/spec/spec_helper.rb +21 -7
- metadata +50 -42
- data/spec/resourceful/authentication_manager_spec.rb +0 -249
- data/spec/resourceful/cache_manager_spec.rb +0 -223
- data/spec/resourceful/header_spec.rb +0 -38
- data/spec/resourceful/http_accessor_spec.rb +0 -164
- data/spec/resourceful/memcache_cache_manager_spec.rb +0 -111
- data/spec/resourceful/net_http_adapter_spec.rb +0 -96
- data/spec/resourceful/options_interpreter_spec.rb +0 -102
- data/spec/resourceful/request_spec.rb +0 -186
- data/spec/resourceful/resource_spec.rb +0 -600
- data/spec/resourceful/response_spec.rb +0 -238
- data/spec/resourceful/stubbed_resource_proxy_spec.rb +0 -58
- data/spec/simple_http_server_shared_spec.rb +0 -162
- data/spec/simple_http_server_shared_spec_spec.rb +0 -212
data/Manifest
CHANGED
@@ -1,34 +1,30 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
1
|
+
Manifest
|
2
|
+
spec/spec.opts
|
3
|
+
spec/simple_sinatra_server_spec.rb
|
4
|
+
spec/simple_sinatra_server.rb
|
5
|
+
spec/acceptance/header_spec.rb
|
6
|
+
spec/acceptance/caching_spec.rb
|
7
|
+
spec/acceptance/resource_spec.rb
|
8
|
+
spec/acceptance/redirecting_spec.rb
|
9
|
+
spec/acceptance/authorization_spec.rb
|
10
|
+
spec/acceptance_shared_specs.rb
|
11
|
+
spec/spec_helper.rb
|
12
|
+
spec/old_acceptance_specs.rb
|
13
|
+
README.markdown
|
14
|
+
resourceful.gemspec
|
15
|
+
lib/resourceful/cache_manager.rb
|
16
|
+
lib/resourceful/exceptions.rb
|
6
17
|
lib/resourceful/net_http_adapter.rb
|
7
|
-
lib/resourceful/http_accessor.rb
|
8
18
|
lib/resourceful/stubbed_resource_proxy.rb
|
9
19
|
lib/resourceful/header.rb
|
10
|
-
lib/resourceful/
|
11
|
-
lib/resourceful/options_interpreter.rb
|
12
|
-
lib/resourceful/response.rb
|
20
|
+
lib/resourceful/authentication_manager.rb
|
13
21
|
lib/resourceful/request.rb
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
22
|
+
lib/resourceful/resource.rb
|
23
|
+
lib/resourceful/response.rb
|
24
|
+
lib/resourceful/util.rb
|
25
|
+
lib/resourceful/http_accessor.rb
|
26
|
+
lib/resourceful/options_interpreter.rb
|
27
|
+
lib/resourceful/memcache_cache_manager.rb
|
28
|
+
lib/resourceful.rb
|
32
29
|
Rakefile
|
33
|
-
README.markdown
|
34
30
|
MIT-LICENSE
|
data/Rakefile
CHANGED
@@ -1,16 +1,22 @@
|
|
1
1
|
require 'rubygems'
|
2
2
|
require 'rake'
|
3
|
-
require '
|
3
|
+
require 'lib/resourceful'
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
p.url = "http://github.com/paul/resourceful"
|
8
|
-
p.author = "Paul Sadauskas"
|
9
|
-
p.email = "psadauskas@gmail.com"
|
5
|
+
begin
|
6
|
+
require 'echoe'
|
10
7
|
|
11
|
-
|
12
|
-
|
13
|
-
|
8
|
+
Echoe.new('resourceful', Resourceful::VERSION) do |p|
|
9
|
+
p.description = "An HTTP library for Ruby that takes advantage of everything HTTP has to offer."
|
10
|
+
p.url = "http://github.com/paul/resourceful"
|
11
|
+
p.author = "Paul Sadauskas"
|
12
|
+
p.email = "psadauskas@gmail.com"
|
13
|
+
|
14
|
+
p.ignore_pattern = ["pkg/*", "tmp/*"]
|
15
|
+
p.dependencies = ['addressable', 'httpauth', 'rspec', 'facets', 'andand']
|
16
|
+
p.development_dependencies = ['thin', 'yard', 'sinatra']
|
17
|
+
end
|
18
|
+
rescue LoadError => e
|
19
|
+
puts "install 'echoe' gem to be able to build the gem"
|
14
20
|
end
|
15
21
|
|
16
22
|
require 'spec/rake/spectask'
|
@@ -19,7 +25,27 @@ desc 'Run all specs'
|
|
19
25
|
Spec::Rake::SpecTask.new(:spec) do |t|
|
20
26
|
t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
|
21
27
|
t.libs << 'lib'
|
22
|
-
t.spec_files = FileList['spec
|
28
|
+
t.spec_files = FileList['spec/acceptance/*_spec.rb']
|
29
|
+
end
|
30
|
+
|
31
|
+
desc 'Run the specs for the server'
|
32
|
+
Spec::Rake::SpecTask.new('spec:server') do |t|
|
33
|
+
t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
|
34
|
+
t.libs << 'lib'
|
35
|
+
t.spec_files = FileList['spec/simple_sinatra_server_spec.rb']
|
36
|
+
end
|
37
|
+
|
38
|
+
begin
|
39
|
+
require 'spec/simple_sinatra_server'
|
40
|
+
desc "Run the sinatra echo server, with loggin"
|
41
|
+
task :server do
|
42
|
+
Sinatra::Default.set(
|
43
|
+
:run => true,
|
44
|
+
:logging => true
|
45
|
+
)
|
46
|
+
end
|
47
|
+
rescue LoadError => e
|
48
|
+
puts "Install 'sinatra' gem to run the server"
|
23
49
|
end
|
24
50
|
|
25
51
|
desc 'Default: Run Specs'
|
@@ -28,11 +54,15 @@ task :default => :spec
|
|
28
54
|
desc 'Run all tests'
|
29
55
|
task :test => :spec
|
30
56
|
|
31
|
-
|
57
|
+
begin
|
58
|
+
require 'yard'
|
32
59
|
|
33
|
-
desc "Generate Yardoc"
|
34
|
-
YARD::Rake::YardocTask.new do |t|
|
35
|
-
|
60
|
+
desc "Generate Yardoc"
|
61
|
+
YARD::Rake::YardocTask.new do |t|
|
62
|
+
t.files = ['lib/**/*.rb', 'README.markdown']
|
63
|
+
end
|
64
|
+
rescue LoadError => e
|
65
|
+
puts "Install 'yard' gem to generate docs"
|
36
66
|
end
|
37
67
|
|
38
68
|
desc "Update rubyforge documentation"
|
data/lib/resourceful.rb
CHANGED
@@ -1,28 +1,18 @@
|
|
1
|
-
require 'resourceful/http_accessor'
|
2
1
|
|
3
|
-
|
2
|
+
__DIR__ = File.dirname(__FILE__)
|
4
3
|
|
5
|
-
|
6
|
-
|
4
|
+
$LOAD_PATH.unshift __DIR__ unless
|
5
|
+
$LOAD_PATH.include?(__DIR__) ||
|
6
|
+
$LOAD_PATH.include?(File.expand_path(__DIR__))
|
7
7
|
|
8
|
-
|
9
|
-
Connection
|
10
|
-
Keep-Alive
|
11
|
-
Proxy-Authenticate
|
12
|
-
Proxy-Authorization
|
13
|
-
TE
|
14
|
-
Trailers
|
15
|
-
Transfer-Encoding
|
16
|
-
Upgrade
|
17
|
-
}
|
8
|
+
require 'resourceful/util'
|
18
9
|
|
19
|
-
|
20
|
-
|
21
|
-
Content-MD5
|
22
|
-
ETag
|
23
|
-
Last-Modified
|
24
|
-
Expires
|
25
|
-
}
|
10
|
+
require 'resourceful/header'
|
11
|
+
require 'resourceful/http_accessor'
|
26
12
|
|
13
|
+
# Resourceful is a library that provides a high level HTTP interface.
|
14
|
+
module Resourceful
|
15
|
+
VERSION = "0.5.0"
|
16
|
+
RESOURCEFUL_USER_AGENT_TOKEN = "Resourceful/#{VERSION}(Ruby/#{RUBY_VERSION})"
|
27
17
|
|
28
18
|
end
|
@@ -20,8 +20,9 @@ module Resourceful
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def add_credentials(request)
|
23
|
-
|
24
|
-
|
23
|
+
@authenticators.each do |authenticator|
|
24
|
+
authenticator.add_credentials_to(request) if authenticator.can_handle?(request)
|
25
|
+
end
|
25
26
|
end
|
26
27
|
|
27
28
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'resourceful/header'
|
2
2
|
require 'andand'
|
3
3
|
require 'facets/kernel/returning'
|
4
|
+
require 'digest/md5'
|
4
5
|
|
5
6
|
module Resourceful
|
6
7
|
|
@@ -40,6 +41,13 @@ module Resourceful
|
|
40
41
|
# @param uri<String>
|
41
42
|
# The uri of the resource to be invalidated
|
42
43
|
def invalidate(uri); end
|
44
|
+
|
45
|
+
protected
|
46
|
+
|
47
|
+
# Returns an alphanumeric hash of a URI
|
48
|
+
def uri_hash(uri)
|
49
|
+
Digest::MD5.hexdigest(uri)
|
50
|
+
end
|
43
51
|
end
|
44
52
|
|
45
53
|
# This is the default cache, and does not do any caching. All lookups
|
@@ -70,7 +78,7 @@ module Resourceful
|
|
70
78
|
end
|
71
79
|
|
72
80
|
def store(request, response)
|
73
|
-
return unless response.
|
81
|
+
return unless response.cacheable?
|
74
82
|
|
75
83
|
@collection[request.uri.to_s][request] = response
|
76
84
|
end
|
@@ -80,6 +88,55 @@ module Resourceful
|
|
80
88
|
end
|
81
89
|
end # class InMemoryCacheManager
|
82
90
|
|
91
|
+
# Stores cache entries in a directory on the filesystem. Similarly to the
|
92
|
+
# InMemoryCacheManager there are no limits on storage, so this will eventually
|
93
|
+
# eat up all your disk!
|
94
|
+
class FileCacheManager < AbstractCacheManager
|
95
|
+
# Create a new FileCacheManager
|
96
|
+
#
|
97
|
+
# @param [String] location
|
98
|
+
# A directory on the filesystem to store cache entries. This directory
|
99
|
+
# will be created if it doesn't exist
|
100
|
+
def initialize(location="/tmp/resourceful")
|
101
|
+
require 'fileutils'
|
102
|
+
require 'yaml'
|
103
|
+
@dir = FileUtils.mkdir_p(location)
|
104
|
+
end
|
105
|
+
|
106
|
+
def lookup(request)
|
107
|
+
returning(cache_entries_for(request)[request]) do |response|
|
108
|
+
response.authoritative = false if response
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def store(request, response)
|
113
|
+
return unless response.cacheable?
|
114
|
+
|
115
|
+
entries = cache_entries_for(request)
|
116
|
+
entries[request] = response
|
117
|
+
File.open(cache_file(request.uri), "w") {|fh| fh.write( YAML.dump(entries) ) }
|
118
|
+
end
|
119
|
+
|
120
|
+
def invalidate(uri);
|
121
|
+
File.unlink(cache_file(uri));
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
def cache_entries_for(request)
|
127
|
+
if File.readable?( cache_file(request.uri) )
|
128
|
+
YAML.load_file( cache_file(request.uri) )
|
129
|
+
else
|
130
|
+
Resourceful::CacheEntryCollection.new
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def cache_file(uri)
|
135
|
+
"#{@dir}/#{uri_hash(uri)}"
|
136
|
+
end
|
137
|
+
end # class FileCacheManager
|
138
|
+
|
139
|
+
|
83
140
|
# The collection of cached entries. Nominally all the entry in a
|
84
141
|
# collection of this sort will be for the same resource but that is
|
85
142
|
# not required to be true.
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
module Resourceful
|
3
|
+
|
4
|
+
# This exception used to indicate that the request did not succeed.
|
5
|
+
# The HTTP response is included so that the appropriate actions can
|
6
|
+
# be taken based on the details of that response
|
7
|
+
class UnsuccessfulHttpRequestError < Exception
|
8
|
+
attr_reader :http_response, :http_request
|
9
|
+
|
10
|
+
# Initialize new error from the HTTP request and response attributes.
|
11
|
+
def initialize(http_request, http_response)
|
12
|
+
super("#{http_request.method} request to <#{http_request.uri}> failed with code #{http_response.code}")
|
13
|
+
@http_request = http_request
|
14
|
+
@http_response = http_response
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class MalformedServerResponse < UnsuccessfulHttpRequestError
|
19
|
+
end
|
20
|
+
|
21
|
+
|
22
|
+
# Exception indicating that the server used a content coding scheme
|
23
|
+
# that Resourceful is unable to handle.
|
24
|
+
class UnsupportedContentCoding < Exception
|
25
|
+
end
|
26
|
+
|
27
|
+
# Raised when a body is supplied, but not a content-type header
|
28
|
+
class MissingContentType < ArgumentError
|
29
|
+
def initialize
|
30
|
+
super("A Content-Type must be specified when an entity-body is supplied.")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
data/lib/resourceful/header.rb
CHANGED
@@ -25,6 +25,101 @@ module Resourceful
|
|
25
25
|
def capitalize(k)
|
26
26
|
k.to_s.downcase.gsub(/^.|[-_\s]./) { |x| x.upcase }.gsub('_', '-')
|
27
27
|
end
|
28
|
+
|
29
|
+
def each_field(&blk)
|
30
|
+
to_hash.each { |k,v|
|
31
|
+
blk.call capitalize(k), v
|
32
|
+
}
|
33
|
+
end
|
34
|
+
|
35
|
+
HEADERS = %w[
|
36
|
+
Accept
|
37
|
+
Accept-Charset
|
38
|
+
Accept-Encoding
|
39
|
+
Accept-Language
|
40
|
+
Accept-Ranges
|
41
|
+
Age
|
42
|
+
Allow
|
43
|
+
Authorization
|
44
|
+
Cache-Control
|
45
|
+
Connection
|
46
|
+
Content-Encoding
|
47
|
+
Content-Language
|
48
|
+
Content-Length
|
49
|
+
Content-Location
|
50
|
+
Content-MD5
|
51
|
+
Content-Range
|
52
|
+
Content-Type
|
53
|
+
Date
|
54
|
+
ETag
|
55
|
+
Expect
|
56
|
+
Expires
|
57
|
+
From
|
58
|
+
Host
|
59
|
+
If-Match
|
60
|
+
If-Modified-Since
|
61
|
+
If-None-Match
|
62
|
+
If-Range
|
63
|
+
If-Unmodified-Since
|
64
|
+
Keep-Alive
|
65
|
+
Last-Modified
|
66
|
+
Location
|
67
|
+
Max-Forwards
|
68
|
+
Pragma
|
69
|
+
Proxy-Authenticate
|
70
|
+
Proxy-Authorization
|
71
|
+
Range
|
72
|
+
Referer
|
73
|
+
Retry-After
|
74
|
+
Server
|
75
|
+
TE
|
76
|
+
Trailer
|
77
|
+
Transfer-Encoding
|
78
|
+
Upgrade
|
79
|
+
User-Agent
|
80
|
+
Vary
|
81
|
+
Via
|
82
|
+
Warning
|
83
|
+
WWW-Authenticate
|
84
|
+
]
|
85
|
+
|
86
|
+
HEADERS.each do |header|
|
87
|
+
const = header.upcase.gsub('-', '_')
|
88
|
+
meth = header.downcase.gsub('-', '_')
|
89
|
+
|
90
|
+
class_eval <<-RUBY, __FILE__, __LINE__
|
91
|
+
#{const} = "#{header}".freeze # ACCEPT = "accept".freeze
|
92
|
+
|
93
|
+
def #{meth} # def accept
|
94
|
+
self[#{const}] # self[ACCEPT]
|
95
|
+
end # end
|
96
|
+
|
97
|
+
def #{meth}=(str) # def accept=(str)
|
98
|
+
self[#{const}] = str # self[ACCEPT] = str
|
99
|
+
end # end
|
100
|
+
RUBY
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
HOP_BY_HOP_HEADERS = [
|
105
|
+
CONNECTION,
|
106
|
+
KEEP_ALIVE,
|
107
|
+
PROXY_AUTHENTICATE,
|
108
|
+
PROXY_AUTHORIZATION,
|
109
|
+
TE,
|
110
|
+
TRAILER,
|
111
|
+
TRANSFER_ENCODING,
|
112
|
+
UPGRADE
|
113
|
+
].freeze
|
114
|
+
|
115
|
+
NON_MODIFIABLE_HEADERS = [
|
116
|
+
CONTENT_LOCATION,
|
117
|
+
CONTENT_MD5,
|
118
|
+
ETAG,
|
119
|
+
LAST_MODIFIED,
|
120
|
+
EXPIRES
|
121
|
+
].freeze
|
122
|
+
|
28
123
|
end
|
29
124
|
end
|
30
125
|
|
@@ -28,8 +28,6 @@ module Resourceful
|
|
28
28
|
# provided by the Resourceful library. Conceptually this object
|
29
29
|
# acts a collection of all the resources available via HTTP.
|
30
30
|
class HttpAccessor
|
31
|
-
RESOURCEFUL_VERSION = "0.3.1"
|
32
|
-
RESOURCEFUL_USER_AGENT_TOKEN = "Resourceful/#{RESOURCEFUL_VERSION}(Ruby/#{RUBY_VERSION})"
|
33
31
|
|
34
32
|
# A logger object to which messages about the activities of this
|
35
33
|
# object will be written. This should be an object that responds
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "resourceful/cache_manager"
|
2
2
|
|
3
3
|
require 'memcache'
|
4
4
|
require 'facets/kernel/returning'
|
@@ -58,7 +58,7 @@ module Resourceful
|
|
58
58
|
# @param [String] uri
|
59
59
|
# The uri of the resource to be invalidated
|
60
60
|
def invalidate(uri)
|
61
|
-
@memcache.delete(
|
61
|
+
@memcache.delete(uri_hash(uri))
|
62
62
|
end
|
63
63
|
|
64
64
|
|
@@ -69,17 +69,7 @@ module Resourceful
|
|
69
69
|
attr_reader :memcache
|
70
70
|
|
71
71
|
def cache_entries_for(a_request)
|
72
|
-
@memcache.get(a_request.
|
72
|
+
@memcache.get(uri_hash(a_request.uri)) || Resourceful::CacheEntryCollection.new
|
73
73
|
end
|
74
74
|
end
|
75
|
-
|
76
|
-
module MemCacheKey
|
77
|
-
def to_mc_key
|
78
|
-
Digest::MD5.hexdigest(uri)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
class Request
|
83
|
-
include MemCacheKey
|
84
|
-
end
|
85
75
|
end
|