From eae95520a1225057c716b51c00f5f213d7a51465 Mon Sep 17 00:00:00 2001 From: guilherme silveira Date: Tue, 26 Oct 2010 17:33:54 -0200 Subject: [PATCH] supporting cache through use_trait --- Gemfile | 2 + Gemfile.lock | 2 + lib/restfulie/client/feature/cache.rb | 4 + lib/restfulie/client/http/response_handler.rb | 67 --------------- lib/restfulie/server.rb | 6 ++ lib/restfulie/server/action_controller.rb | 6 +- .../action_controller/cacheable_responder.rb | 77 ------------------ .../server/action_controller/trait.rb | 10 +++ .../action_controller/trait/cacheable.rb | 81 +++++++++++++++++++ 9 files changed, 106 insertions(+), 149 deletions(-) delete mode 100644 lib/restfulie/client/http/response_handler.rb delete mode 100644 lib/restfulie/server/action_controller/cacheable_responder.rb create mode 100644 lib/restfulie/server/action_controller/trait.rb create mode 100644 lib/restfulie/server/action_controller/trait/cacheable.rb diff --git a/Gemfile b/Gemfile index 0a5c8c94..5f1bdb10 100644 --- a/Gemfile +++ b/Gemfile @@ -9,6 +9,8 @@ gem "json_pure" gem "sqlite3-ruby" gem "yard" +gem "respondie" + if RUBY_VERSION < "1.9" gem "ruby-debug" else diff --git a/Gemfile.lock b/Gemfile.lock index a00e61da..c01cacbd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -72,6 +72,7 @@ GEM thor (~> 0.14.0) rake (0.8.7) rcov (0.9.8) + respondie (0.9.0) rspec (2.0.0.beta.19) rspec-core (= 2.0.0.beta.19) rspec-expectations (= 2.0.0.beta.19) @@ -117,6 +118,7 @@ DEPENDENCIES rack-conneg rails (>= 3.0.0) rcov + respondie rspec-rails (>= 2.0.0.beta.19) ruby-debug sinatra diff --git a/lib/restfulie/client/feature/cache.rb b/lib/restfulie/client/feature/cache.rb index fc3fa480..c9cb3d81 100644 --- a/lib/restfulie/client/feature/cache.rb +++ b/lib/restfulie/client/feature/cache.rb @@ -1,7 +1,11 @@ class Restfulie::Client::Feature::Cache def execute(flow, request, response, env) + Restfulie::Client.cache_provider.get([request.host, request.path], request) resp = flow.continue(request, response, env) + unless resp.kind_of? Exception + Restfulie::Client.cache_provider.put([request.host, request.path], request, response) + end end end diff --git a/lib/restfulie/client/http/response_handler.rb b/lib/restfulie/client/http/response_handler.rb deleted file mode 100644 index fd692b1a..00000000 --- a/lib/restfulie/client/http/response_handler.rb +++ /dev/null @@ -1,67 +0,0 @@ -module Restfulie - module Client - module HTTP #:nodoc: - #=ResponseHandler - # You can change instance registering a class according to the code. - # - #==Example - # - # class RequestExecutor - # include RequestAdapter - # def initialize(host) - # self.host=host - # end - # end - # - # class FakeResponse < Restfulie::Client::HTTP::Response - # end - # - # Restfulie::Client::HTTP::ResponseHandler.register(201,FakeResponse) - # @re = Restfulie::Client::HTTP::RequestExecutor.new('http://restfulie.com') - # puts @re.as('application/atom+xml').get!('/posts').class.to_i #=> FakeResponse - # - module ResponseHandler - @@response_handlers = {} - ## - # :singleton-method: - # Response handlers attribute reader - # * code: HTTP status code - def self.handlers(code) - @@response_handlers[code] - end - - ## - # :singleton-method: - # Use to register response handlers - # - # * code: HTTP status code - # * response_class: Response class - # - #==Example: - # class FakeResponse < ::Restfulie::Client::HTTP::Response - # end - # - # Restfulie::Client::HTTP::ResponseHandler.register(200,FakeResponse) - # - def self.register(code,response_class) - @@response_handlers[code] = response_class - end - - ## - # :singleton-method: - # Request Adapter uses this method to choose response instance - # - # *method: :get,:post,:delete,:head,:put - # *path: '/posts' - # *http_response - # - def self.handle(method, path, http_response) - response_class = @@response_handlers[http_response.code.to_i] || Response - headers = {} - http_response.header.each { |k, v| headers[k] = v } - response_class.new( method, path, http_response.code.to_i, http_response.body, headers) - end - end - end - end -end diff --git a/lib/restfulie/server.rb b/lib/restfulie/server.rb index d1d4dea0..3d765f51 100644 --- a/lib/restfulie/server.rb +++ b/lib/restfulie/server.rb @@ -1,3 +1,4 @@ +require 'respondie' require 'restfulie/common' module Restfulie @@ -16,4 +17,9 @@ class ActionController::Base def self.restfulie include Restfulie::Server::ActionController::Base end + + def self.use_trait(&block) + Respondie::Builder.new("Restfulie::Server::ActionController::Trait::$trait$", self).instance_eval(&block) + end + end diff --git a/lib/restfulie/server/action_controller.rb b/lib/restfulie/server/action_controller.rb index 863fcbc9..689b05ac 100644 --- a/lib/restfulie/server/action_controller.rb +++ b/lib/restfulie/server/action_controller.rb @@ -2,11 +2,7 @@ module Restfulie module Server module ActionController #:nodoc: if defined?(::ActionController) || defined?(::ApplicationController) - autoload :ParamsParser, 'restfulie/server/action_controller/params_parser' - autoload :CacheableResponder, 'restfulie/server/action_controller/cacheable_responder' - autoload :CreatedResponder, 'restfulie/server/action_controller/created_responder' - autoload :RestfulResponder, 'restfulie/server/action_controller/restful_responder' - autoload :Base, 'restfulie/server/action_controller/base' + Dir["#{File.dirname(__FILE__)}/action_controller/*.rb"].each {|f| autoload File.basename(f)[0..-4].camelize.to_sym, f } end end end diff --git a/lib/restfulie/server/action_controller/cacheable_responder.rb b/lib/restfulie/server/action_controller/cacheable_responder.rb deleted file mode 100644 index d551b58b..00000000 --- a/lib/restfulie/server/action_controller/cacheable_responder.rb +++ /dev/null @@ -1,77 +0,0 @@ -class Expires - def do_http_cache(responder) - if responder.options[:expires_in] - responder.controller.send :expires_in,responder.options[:expires_in] - true - else - false - end - end -end - -class LastModifieds - # default implementation that will check whether caching can be applied - def do_http_cache?(responder) - responder.resources.flatten.select do |resource| - resource.respond_to?(:updated_at) - end && - responder.controller.response.last_modified.nil? && - !new_record?(responder) - end - - def do_http_cache(responder) - return false unless do_http_cache?(responder) - - timestamp = responder.resources.flatten.select do |resource| - resource.respond_to?(:updated_at) - end.map do |resource| - (resource.updated_at || Time.now).utc - end.max - - responder.controller.response.last_modified = timestamp if timestamp - end - - def new_record?(responder) - responder.resource.respond_to?(:new_record?) && responder.resource.new_record? - end - -end - -module Restfulie - module Server - module ActionController - module CacheableResponder - - CACHES = [::Expires.new, ::LastModifieds.new] - - def to_format - cached = CACHES.inject(false) do |cached, cache| - cached || cache.do_http_cache(self) - end - if ::ActionController::Base.perform_caching && !cached.nil? - set_public_cache_control! - head :not_modified if fresh = request.fresh?(controller.response) - fresh - else - super - end - end - - private - - def set_public_cache_control! - cache_control = cache_control_headers.split(",").map {|k| k.strip } - cache_control.delete("private") - cache_control.delete("no-cache") - cache_control << "public" - controller.response.headers["Cache-Control"] = cache_control.join(', ') - end - - def cache_control_headers - controller.response.headers["Cache-Control"] || "" - end - - end - end - end -end diff --git a/lib/restfulie/server/action_controller/trait.rb b/lib/restfulie/server/action_controller/trait.rb new file mode 100644 index 00000000..085d5ee8 --- /dev/null +++ b/lib/restfulie/server/action_controller/trait.rb @@ -0,0 +1,10 @@ +module Restfulie + module Server + module ActionController #:nodoc: + module Trait #:nodoc: + puts "loading trait..." + Dir["#{File.dirname(__FILE__)}/trait/*.rb"].each {|f| autoload File.basename(f)[0..-4].camelize.to_sym, f } + end + end + end +end diff --git a/lib/restfulie/server/action_controller/trait/cacheable.rb b/lib/restfulie/server/action_controller/trait/cacheable.rb new file mode 100644 index 00000000..917ab239 --- /dev/null +++ b/lib/restfulie/server/action_controller/trait/cacheable.rb @@ -0,0 +1,81 @@ +class Expires + def do_http_cache(responder, headers) + if responder.options[:expires_in] + headers << "max-age=#{responder.options[:expires_in]}" + true + else + false + end + end +end + +class LastModifieds + # default implementation that will check whether caching can be applied + def accepts?(responder) + responder.resources.flatten.select do |resource| + resource.respond_to?(:updated_at) + end && + responder.controller.response.last_modified.nil? && + !new_record?(responder) + end + + def do_http_cache(responder, headers) + return false unless accepts?(responder) + + timestamps = responder.resources.flatten.select do |resource| + resource.respond_to?(:updated_at) + end.map do |resource| + (resource.updated_at || Time.now).utc + end + + timestamp = timestamps.max + + responder.controller.response.last_modified = timestamp if timestamp + end + + def new_record?(responder) + responder.resource.respond_to?(:new_record?) && responder.resource.new_record? + end + +end + +module Restfulie::Server::ActionController + module Trait + module Cacheable + + CACHES = [::Expires.new, ::LastModifieds.new] + + def to_format + headers = cache_control_headers + cached = CACHES.inject(false) do |cached, cache| + cached || cache.do_http_cache(self, headers) + end + if ::ActionController::Base.perform_caching && cached + set_public_cache_control(headers) + controller.response.headers["Cache-Control"] = headers.join(', ') + fresh = request.fresh?(controller.response) + if fresh + head :not_modified + else + super + end + else + super + end + end + + private + + def set_public_cache_control(headers) + headers.delete("private") + headers.delete("no-cache") + headers << "public" + end + + def cache_control_headers + (controller.response.headers["Cache-Control"] || "").split(",").map {|k| k.strip } + end + + end + end +end