Permalink
Browse files

git import

  • Loading branch information...
0 parents commit cbd2990e0bf98aab06a07add900a7fb04dcc41be @tobi committed Feb 24, 2008
Showing with 1,197 additions and 0 deletions.
  1. 0 README
  2. +22 −0 Rakefile
  3. +12 −0 init.rb
  4. +89 −0 lib/cache.rb
  5. +112 −0 lib/cacheable.rb
  6. +21 −0 lib/gzip.rb
  7. +732 −0 lib/vendor/memcache.rb
  8. +34 −0 test/cache_test.rb
  9. +43 −0 test/gzip_test.rb
  10. +132 −0 test/requests_test.rb
0 README
No changes.
@@ -0,0 +1,22 @@
+require 'rake'
+require 'rake/testtask'
+require 'rake/rdoctask'
+
+desc 'Default: run unit tests.'
+task :default => :test
+
+desc 'Test the http_auth plugin.'
+Rake::TestTask.new(:test) do |t|
+ t.libs << 'lib'
+ t.pattern = 'test/**/*_test.rb'
+ t.verbose = true
+end
+
+desc 'Generate documentation for the plugin.'
+Rake::RDocTask.new(:rdoc) do |rdoc|
+ rdoc.rdoc_dir = 'rdoc'
+ rdoc.title = 'HttpAuth'
+ rdoc.options << '--line-numbers' << '--inline-source'
+ rdoc.rdoc_files.include('README')
+ rdoc.rdoc_files.include('lib/**/*.rb')
+end
12 init.rb
@@ -0,0 +1,12 @@
+require File.dirname(__FILE__) + '/lib/cache'
+require File.dirname(__FILE__) + '/lib/cacheable'
+
+# Include the cacheable model in action controller so that the cache method is available.
+ActionController::Base.send(:include, Cacheable)
+
+# Load cache configuration from database.yml
+if config = ActiveRecord::Base.configurations[RAILS_ENV]['cache']
+ Cache.establish_connection(config)
+else
+ fail "Could not find cache: section in your database.yml"
+end
@@ -0,0 +1,89 @@
+require File.dirname(__FILE__) + '/vendor/memcache'
+
+module Cache
+
+ module Adapters
+ class NoCache
+ def set(key, value, expiry)
+ end
+ def get(key)
+ end
+
+ def inspect
+ 'No Cache'
+ end
+ end
+
+ class MemoryCache
+ def initialize
+ $cache = {}
+ end
+
+ def set(key, value, expiry)
+ $cache[key] = Marshal.dump(value)
+ end
+
+ def get(key)
+ Marshal.load($cache[key]) if $cache.has_key?(key)
+ end
+
+ def inspect
+ 'Memory Cache'
+ end
+ end
+ end
+
+ mattr_accessor :adapter
+ self.adapter = Adapters::NoCache.new
+
+ def self.establish_connection(config)
+ self.adapter = case config['adapter']
+ when 'memory'
+ Adapters::MemoryCache.new
+ when 'memcached'
+ MemCache.new(config['servers'], :namespace => config['namespace'] || 'cacheable')
+ else
+ Adapters::NoCache.new
+ end
+
+ RAILS_DEFAULT_LOGGER.info "** Using cache: #{adapter.inspect}"
+ end
+
+ def self.get(key, expiry = 0)
+ if result = adapter.get(key)
+ return result
+ end
+
+ if block_given?
+ block_result = yield
+ set(key, block_result, expiry)
+ return block_result
+ end
+
+ return nil
+ rescue MemCache::MemCacheError
+ nil
+ end
+
+ def self.set(key, value, expiry = 0)
+ adapter.set(key, value, expiry)
+ true
+ rescue MemCache::MemCacheError
+ false
+ end
+
+ def self.stats
+ adapter.respond_to?(:stats) ? adapter.stats : {}
+ rescue MemCache::MemCacheError
+ {}
+ end
+
+ def self.flush_all
+ adapter.respond_to?(:flush_all) ? adapter.flush_all : false
+ rescue MemCache::MemCacheError
+ false
+ else
+ true
+ end
+
+end
@@ -0,0 +1,112 @@
+require 'digest/md5'
+require File.dirname(__FILE__) + '/gzip'
+
+module Cacheable
+
+ def self.included(base)
+ base.after_filter :update_cache
+ end
+
+ # Override cacheable with extra logic which decides
+ # weather or not we should cache the current action
+ def cacheable?
+ ActionController::Base.perform_caching
+ end
+
+ # Only get? and head? requests should be cacheable
+ #
+ def cacheable_request?
+ request.get? or request.head?
+ end
+
+
+ # By default we only cache requests which are answered by
+ # a OK header and which weren't cached in first place.
+ # TODO: Should be also cache 404s and such things? may reduce more database load yet...
+ def cacheable_response?
+ # We only cache successful requests...
+ headers['Status'].to_i == 200
+ end
+
+ # Override this method with additonal namespace information
+ # which should modifiy the lookup key
+ def cache_namespace
+ ''
+ end
+
+ def cache_key
+ request.env['REQUEST_URI']
+ end
+
+ def cache(key = cache_key, namespace = cache_namespace, options = {})
+ return yield unless cacheable? && cacheable_request?
+
+ @cache_key = "#{namespace}#{key}"
+ @cache_key_hash = Digest::MD5.hexdigest(@cache_key)
+
+
+ # Can we save bandwidth by ignoring instructing the
+ # client to simply re-display its local cache?
+ if request.env["HTTP_IF_NONE_MATCH"] == @cache_key_hash
+ response.headers["X-Cache"] = "hit: client"
+ head :not_modified
+ return
+ end
+
+ if hit = Cache.get(@cache_key_hash)
+ status, content_type, body = *hit
+
+ response.headers['X-Cache'] = 'hit: server'
+ response.headers['Content-Type'] = content_type
+
+ if accepts = accept_encoding
+ response.headers['Content-Encoding'] = accepts
+ render :text => body, :status => status
+ else
+ render :text => GZip.decompress(body), :status => status
+ end
+ else
+ response.headers['X-Cache'] = 'miss'
+ @cache_miss = true
+ @cache_expiry = options[:expire] || 0
+
+ # Yield to the block, this request cannot be handled from cache
+ yield
+ end
+
+ logger.info "Cache: #{headers['X-Cache']}"
+ end
+
+ def update_cache
+ return unless cacheable? and cacheable_request? and cacheable_response?
+
+ headers["ETag"] = @cache_key_hash
+
+ # Store a compressed representation of the content in memcached.
+ if @cache_miss
+ logger.info 'Cache: store'
+ Cache.set @cache_key_hash, [headers['Status'].to_i, headers['Content-Type'], compressed_response], @cache_expiry
+ end
+
+ # If the client accepts gzip compressed content thats what it will get
+ if accepts = accept_encoding and !response_compressed?
+ response.body = compressed_response
+ response.headers['Content-Encoding'] = accepts
+ end
+ end
+
+ private
+
+ def response_compressed?
+ !response.headers['Content-Encoding'].blank?
+ end
+
+ def accept_encoding
+ request.env['HTTP_ACCEPT_ENCODING'].to_s =~ /(x-gzip|gzip)/ ? $1 : nil
+ end
+
+ def compressed_response
+ @compressed_response ||= GZip.compress(response.body)
+ end
+
+end
@@ -0,0 +1,21 @@
+require 'zlib'
+require 'stringio'
+
+
+module GZip
+ class Stream < StringIO
+ def close; rewind; end
+ end
+
+ def self.decompress(source)
+ Zlib::GzipReader.new(StringIO.new(source)).read
+ end
+
+ def self.compress(source)
+ output = Stream.new
+ gz = Zlib::GzipWriter.new(output)
+ gz.write(source)
+ gz.close
+ output.string
+ end
+end
Oops, something went wrong.

0 comments on commit cbd2990

Please sign in to comment.