Skip to content

Commit

Permalink
Lots more caching specs
Browse files Browse the repository at this point in the history
  • Loading branch information
paul committed Feb 1, 2009
1 parent 87ed8f4 commit 8f7a708
Show file tree
Hide file tree
Showing 12 changed files with 260 additions and 89 deletions.
4 changes: 2 additions & 2 deletions lib/resourceful/cache_manager.rb
Expand Up @@ -78,7 +78,7 @@ def lookup(request)
end

def store(request, response)
return unless response.cachable?
return unless response.cacheable?

@collection[request.uri.to_s][request] = response
end
Expand Down Expand Up @@ -110,7 +110,7 @@ def lookup(request)
end

def store(request, response)
return unless response.cachable?
return unless response.cacheable?

entries = cache_entries_for(request)
entries[request] = response
Expand Down
4 changes: 4 additions & 0 deletions lib/resourceful/exceptions.rb
Expand Up @@ -15,6 +15,10 @@ def initialize(http_request, http_response)
end
end

class MalformedServerResponse < UnsuccessfulHttpRequestError
end


# Exception indicating that the server used a content coding scheme
# that Resourceful is unable to handle.
class UnsupportedContentCoding < Exception
Expand Down
14 changes: 8 additions & 6 deletions lib/resourceful/request.rb
Expand Up @@ -48,7 +48,7 @@ def add_credentials!
def fetch_response
if cached_response
if needs_revalidation?(cached_response)
logger.info(" Cache entry is stale")
logger.info(" Cache needs revalidation")
set_validation_headers!(cached_response)
else
# We're done!
Expand All @@ -58,7 +58,7 @@ def fetch_response

response = perform!

response = revalidate_cached_response(response) if response.not_modified?
response = revalidate_cached_response(response) if cached_response && response.not_modified?
response = follow_redirect(response) if should_be_redirected?(response)
response = retry_with_auth(response) if needs_authorization?(response)

Expand Down Expand Up @@ -100,6 +100,7 @@ def revalidate_cached_response(not_modified_response)

# Follow a redirect response
def follow_redirect(response)
raise MalformedServerResponse.new(self, response) unless response.header.location
if response.moved_permanently?
new_uri = response.header.location.first
logger.info(" Permanently redirected to #{new_uri} - Storing new location.")
Expand Down Expand Up @@ -155,7 +156,7 @@ def invalidates_cache?
# Perform the request, with no magic handling of anything.
def perform!
@request_time = Time.now
logger.debug(" DEBUG: Request Header: #{@header.inspect}")
logger.debug("DEBUG: Request Header: #{@header.inspect}")

http_resp = NetHttpAdapter.make_request(@method, @resource.uri, @body, @header)
@response = Resourceful::Response.new(uri, *http_resp)
Expand Down Expand Up @@ -199,9 +200,10 @@ def uri

# Does this request force us to revalidate the cache?
def forces_revalidation?
if cc = header['Cache-Control']
cc.include?('no-cache') || max_age == 0
else
if max_age == 0 || header.cache_control && cc.include?('no-cache')
logger.info(" Client forced revalidation")
true
else
false
end
end
Expand Down
18 changes: 12 additions & 6 deletions lib/resourceful/response.rb
Expand Up @@ -8,6 +8,7 @@ module Resourceful

class Response
REDIRECT_RESPONSE_CODES = [301,302,303,307]
NORMALLY_CACHEABLE_RESPONSE_CODES = [200, 203, 300, 301, 410]

attr_reader :uri, :code, :header, :body, :response_time
alias headers header
Expand Down Expand Up @@ -50,12 +51,15 @@ def stale?
# Is this response cachable?
#
# @return true|false
def cachable?
return false unless [200, 203, 300, 301, 410].include?(code.to_i)
return false if header['Vary'] and header['Vary'].include?('*')
return false if header['Cache-Control'] and header['Cache-Control'].include?('no-store')

true
def cacheable?
@cacheable ||= begin
@cacheable = true if NORMALLY_CACHEABLE_RESPONSE_CODES.include?(code.to_i)
@cacheable = false if header.vary && header.vary.include?('*')
@cacheable = false if header.cache_control && header.cache_control.include?('no-cache')
@cacheable = true if header.cache_control && header.cache_control.include?('public')
@cacheable = true if header.cache_control && header.cache_control.include?('private')
@cacheable || false
end
end

# Does this response force revalidation?
Expand Down Expand Up @@ -144,6 +148,8 @@ def body
505 => "HTTP Version Not Supported".freeze,
}.freeze

CODES = CODE_NAMES.keys

CODE_NAMES.each do |code, msg|
method_name = msg.downcase.gsub(/[- ]/, "_")

Expand Down
16 changes: 16 additions & 0 deletions spec/acceptance/authorization_spec.rb
@@ -0,0 +1,16 @@

require File.dirname(__FILE__) + '/../spec_helper'
require 'resourceful'

describe Resourceful do

describe "basic auth" do

end

describe "digest auth" do

end

end

145 changes: 145 additions & 0 deletions spec/acceptance/caching_spec.rb
@@ -0,0 +1,145 @@

require File.dirname(__FILE__) + '/../spec_helper'
require 'resourceful'

describe Resourceful do

describe "caching" do

before do
@http = Resourceful::HttpAccessor.new(:cache_manager => Resourceful::InMemoryCacheManager.new)
if ENV['SPEC_LOGGING']
@http.logger = Resourceful::StdOutLogger.new
end
end

def get_with_errors(resource)
begin
resp = resource.get
rescue Resourceful::UnsuccessfulHttpRequestError => e
resp = e.http_response
end
resp
end

def uri_plus_params(uri, params = {})
uri = uri.is_a?(Addressable::URI) ? uri : Addressable::URI.parse(uri)
uri.query_values = params
uri
end

def uri_for_code(code, params = {})
uri = Addressable::URI.expand_template("http://localhost:3000/code/{code}",
"code" => code.to_s)

uri_plus_params(uri, params)
end

describe "response cacheability" do
Resourceful::Response::NORMALLY_CACHEABLE_RESPONSE_CODES.each do |code|
describe "response code #{code}" do
it "should normally be cached" do
resource = @http.resource(uri_for_code(code))

resp = get_with_errors(resource)
resp.should be_cacheable
end

it "should not be cached if Vary: *" do
resource = @http.resource(uri_for_code(200, "Vary" => "*"))

resp = get_with_errors(resource)
resp.should_not be_cacheable
end

it "should not be cached if Cache-Control: no-cache'" do
resource = @http.resource(uri_for_code(200, "Cache-Control" => "no-cache"))

resp = get_with_errors(resource)
resp.should_not be_cacheable
end
end
end

# I would prefer to do all other codes, but some of them do some magic stuff (100),
# so I'll just spot check.
[201, 206, 302, 307, 404, 500].each do |code|
describe "response code #{code}" do
it "should not normally be cached" do
resource = @http.resource(uri_for_code(code))

resp = get_with_errors(resource)
resp.should_not be_cacheable
end

it "should be cached if Cache-Control: public" do
resource = @http.resource(uri_for_code(code, "Cache-Control" => "public"))

resp = get_with_errors(resource)
resp.should be_cacheable
end

it "should be cached if Cache-Control: private" do
resource = @http.resource(uri_for_code(code, "Cache-Control" => "private"))

resp = get_with_errors(resource)
resp.should be_cacheable
end
end
end

end

describe "expiration" do
it 'should use the cached response if Expire: is in the future' do
in_the_future = (Time.now + 60).httpdate
resource = @http.resource(uri_for_code(200, "Expire" => in_the_future))

resp = resource.get
resp.should_not be_expired

resp = resource.get
resp.should be_ok
resp.should_not be_authoritative
end

it 'should revalidate the cached response if the response is expired' do
in_the_past = (Time.now - 60).httpdate
resource = @http.resource(uri_for_code(200, "Expire" => in_the_past))

resp = resource.get
resp.should be_expired

resp = resource.get
resp.should be_ok
resp.should be_authoritative
end
end

describe "Not Modified responses" do
it "should replace the 304 with whats in the cache" do
now = Time.now.httpdate

resource = @http.resource(uri_plus_params('http://localhost:3000/cached',
"modified" => now))

resp = resource.get
resp.should be_authoritative
body = resp.body

resp = resource.get("Cache-Control" => "max-age=0")
resp.should be_authoritative
resp.body.should == body
end

it "should merge the headers"
end

describe "cache invalidation" do

end

end

end

12 changes: 12 additions & 0 deletions spec/acceptance/header_spec.rb
@@ -0,0 +1,12 @@

require File.dirname(__FILE__) + '/../spec_helper'
require 'resourceful'

describe Resourceful do

describe "manipulating headers" do

end

end

12 changes: 12 additions & 0 deletions spec/acceptance/redirecting_spec.rb
@@ -0,0 +1,12 @@

require File.dirname(__FILE__) + '/../spec_helper'
require 'resourceful'

describe Resourceful do

describe "redirects" do

end

end

1 change: 0 additions & 1 deletion spec/acceptance/resource_spec.rb
Expand Up @@ -18,7 +18,6 @@
it 'should set the user agent string on the default header' do
@resource.default_header.should have_key('User-Agent')
@resource.default_header['User-Agent'].should == Resourceful::RESOURCEFUL_USER_AGENT_TOKEN

end

describe "GET" do
Expand Down
40 changes: 0 additions & 40 deletions spec/acceptance_spec.rb
Expand Up @@ -12,16 +12,6 @@
@accessor = Resourceful::HttpAccessor.new
end

it 'should #get a resource, and return a response object' do
resource = @accessor.resource('http://localhost:3000/get')
resp = resource.get
resp.should be_instance_of(Resourceful::Response)
resp.code.should == 200
resp.body.should == 'Hello, world!'
resp.header.should be_instance_of(Resourceful::Header)
resp.header['Content-Type'].should == ['text/plain']
end

it 'should set additional headers on the #get' do
resource = @accessor.resource('http://localhost:3000/echo_header')
resp = resource.get(:foo => :bar)
Expand All @@ -30,36 +20,6 @@
resp.body.should =~ /"HTTP_FOO"=>"bar"/
end

it 'should #post a resource, and return the response' do
resource = @accessor.resource('http://localhost:3000/post')
resp = resource.post('Hello world from POST', :content_type => 'text/plain')
resp.should be_instance_of(Resourceful::Response)
resp.code.should == 201
resp.body.should == 'Hello world from POST'
resp.header.should be_instance_of(Resourceful::Header)
resp.header['Content-Type'].should == ['text/plain']
end

it 'should #put a resource, and return the response' do
resource = @accessor.resource('http://localhost:3000/put')
resp = resource.put('Hello world from PUT', :content_type => 'text/plain')
resp.should be_instance_of(Resourceful::Response)
resp.code.should == 200
resp.body.should == 'Hello world from PUT'
resp.header.should be_instance_of(Resourceful::Header)
resp.header['Content-Type'].should == ['text/plain']
end

it 'should #delete a resource, and return a response' do
resource = @accessor.resource('http://localhost:3000/delete')
resp = resource.delete
resp.should be_instance_of(Resourceful::Response)
resp.code.should == 200
resp.body.should == 'KABOOM!'
resp.header.should be_instance_of(Resourceful::Header)
resp.header['Content-Type'].should == ['text/plain']
end

it 'should take an optional default header for reads' do
resource = @accessor.resource('http://localhost:3000/echo_header', :foo => :bar)
resp = resource.get
Expand Down

0 comments on commit 8f7a708

Please sign in to comment.