hakon-lacquer 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_DISABLE_SHARED_GEMS: "1"
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem "activesupport", "~> 3.0"
4
+ gem "i18n", "~> 0.4"
5
+ gem "jeweler"
6
+ gem "rake"
7
+ gem "yard"
8
+
9
+ group :development do
10
+ gem "rspec", "~> 2.0"
11
+ end
@@ -0,0 +1,32 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activesupport (3.0.3)
5
+ diff-lcs (1.1.2)
6
+ git (1.2.5)
7
+ i18n (0.5.0)
8
+ jeweler (1.5.2)
9
+ bundler (~> 1.0.0)
10
+ git (>= 1.2.5)
11
+ rake
12
+ rake (0.8.7)
13
+ rspec (2.3.0)
14
+ rspec-core (~> 2.3.0)
15
+ rspec-expectations (~> 2.3.0)
16
+ rspec-mocks (~> 2.3.0)
17
+ rspec-core (2.3.1)
18
+ rspec-expectations (2.3.0)
19
+ diff-lcs (~> 1.1.2)
20
+ rspec-mocks (2.3.0)
21
+ yard (0.6.4)
22
+
23
+ PLATFORMS
24
+ ruby
25
+
26
+ DEPENDENCIES
27
+ activesupport (~> 3.0)
28
+ i18n (~> 0.4)
29
+ jeweler
30
+ rake
31
+ rspec (~> 2.0)
32
+ yard
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Russ Smith
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,83 @@
1
+ = lacquer
2
+
3
+ Rails drop in for Varnish support.
4
+
5
+ == Install
6
+ Basic installation
7
+
8
+ sudo gem install lacquer
9
+
10
+ config/initializers/lacquer.rb
11
+
12
+ Lacquer.configure do |config|
13
+ # Globally enable/disable cache
14
+ config.enable_cache = true
15
+
16
+ # Unless overridden in a controller or action, the default will be used
17
+ config.default_ttl = 1.week
18
+
19
+ # Can be :none, :delayed_job, :resque
20
+ config.job_backend = :none
21
+
22
+ # Array of Varnish servers to manage
23
+ config.varnish_servers << {
24
+ :host => "0.0.0.0", :port => 6082 # if you have authentication enabled, add :secret => "your secret"
25
+ }
26
+
27
+ # Number of retries
28
+ config.retries = 5
29
+
30
+ # config handler (optional, if you use Hoptoad or another error tracking service)
31
+ config.command_error_handler = lambda { |s| HoptoadNotifier.notify(s) }
32
+ end
33
+
34
+ app/controllers/application_controller.rb
35
+
36
+ class ApplicationController < ActionController::Base
37
+ include Lacquer::CacheUtils
38
+ end
39
+
40
+ == Usage
41
+
42
+ To set a custom ttl for a controller:
43
+
44
+ before_filter { |controller| controller.set_cache_ttl(15.minutes) }
45
+
46
+ Clearing the cache:
47
+
48
+ class Posts < ApplicationController
49
+ after_filter :clear_cache, :only => [ :create, :update, :destroy ]
50
+
51
+ private
52
+
53
+ def clear_cache
54
+ clear_cache_for(
55
+ root_path,
56
+ posts_path,
57
+ post_path(@post))
58
+ end
59
+ end
60
+
61
+ == Gotchas
62
+
63
+ The default TTL for most actions is set to 0, since for most cases you'll probably want to be fairly explicit about what pages do get cached by varnish. The default cache header is typically:
64
+
65
+ Cache-Control: max-age=0, no-cache, private
66
+
67
+ This is good for normal controller actions, since you won't want to cache them. If TTL for an action is set to 0, it won't mess with the default header.
68
+
69
+ The key gotcha here is that cached pages strip cookies, so if your application relies on sessions and uses authenticity tokens, the user will need a session cookie set before form actions will work. Setting default TTL to 0 here will make sure these session cookies won't break.
70
+
71
+ As a result, all you have to do to set a cacheable action is the before filter above.
72
+
73
+ == Note on Patches/Pull Requests
74
+
75
+ * Fork the project.
76
+ * Make your feature addition or bug fix.
77
+ * Add tests for it. This is important so I don't break it in a future version unintentionally.
78
+ * Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
79
+ * Send me a pull request. Bonus points for topic branches.
80
+
81
+ == Copyright
82
+
83
+ Copyright (c) 2010 Russ Smith. See LICENSE for details.
@@ -0,0 +1,36 @@
1
+ require "rubygems"
2
+ require "bundler/setup"
3
+
4
+ require "rake"
5
+ require "yard"
6
+ require "rspec"
7
+ require "rspec/core/rake_task"
8
+
9
+ begin
10
+ require 'jeweler'
11
+ Jeweler::Tasks.new do |gem|
12
+ gem.name = "hakon-lacquer"
13
+ gem.summary = %Q{Rails drop in for Varnish support.}
14
+ gem.description = %Q{Rails drop in for Varnish support.}
15
+ gem.email = "russ@bashme.org"
16
+ gem.homepage = "http://github.com/russ/lacquer"
17
+ gem.authors = ["Russ Smith (russ@bashme.org)", "Ryan Johns", "Garry Tan (garry@posterous.com), Gabe da Silveira (gabe@websaviour.com)", "Håkon Lerring"]
18
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
19
+ end
20
+ Jeweler::GemcutterTasks.new
21
+ rescue LoadError
22
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
23
+ end
24
+
25
+ desc "Run all examples"
26
+ RSpec::Core::RakeTask.new(:spec) do |t|
27
+ t.rspec_path = "rspec"
28
+ t.rspec_opts = %w[--color]
29
+ end
30
+
31
+ YARD::Rake::YardocTask.new do |t|
32
+ t.files = [ "lib/**/*.rb" ]
33
+ t.options = [ "--no-private" ]
34
+ end
35
+
36
+ task :default => [ :spec ]
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.4.1
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), 'lib', 'lacquer')
@@ -0,0 +1,87 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{lacquer}
8
+ s.version = "0.4.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Russ Smith (russ@bashme.org)", "Ryan Johns", "Garry Tan (garry@posterous.com), Gabe da Silveira (gabe@websaviour.com)", "H\303\245kon Lerring"]
12
+ s.date = %q{2010-12-28}
13
+ s.description = %q{Rails drop in for Varnish support.}
14
+ s.email = %q{russ@bashme.org}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".bundle/config",
21
+ ".document",
22
+ "Gemfile",
23
+ "Gemfile.lock",
24
+ "LICENSE",
25
+ "README.rdoc",
26
+ "Rakefile",
27
+ "VERSION",
28
+ "init.rb",
29
+ "lacquer.gemspec",
30
+ "lib/generators/lacquer/USAGE",
31
+ "lib/generators/lacquer/install_generator.rb",
32
+ "lib/generators/lacquer/templates/initializer.rb",
33
+ "lib/generators/lacquer/templates/varnish.sample.vcl",
34
+ "lib/lacquer.rb",
35
+ "lib/lacquer/cache_utils.rb",
36
+ "lib/lacquer/configuration.rb",
37
+ "lib/lacquer/delayed_job_job.rb",
38
+ "lib/lacquer/resque_job.rb",
39
+ "lib/lacquer/varnish.rb",
40
+ "rails/init.rb",
41
+ "spec/lacquer/cache_utils_spec.rb",
42
+ "spec/lacquer/delayed_job_job_spec.rb",
43
+ "spec/lacquer/resque_job_spec.rb",
44
+ "spec/lacquer/varnish_spec.rb",
45
+ "spec/spec_helper.rb"
46
+ ]
47
+ s.homepage = %q{http://github.com/russ/lacquer}
48
+ s.require_paths = ["lib"]
49
+ s.rubygems_version = %q{1.3.7}
50
+ s.summary = %q{Rails drop in for Varnish support.}
51
+ s.test_files = [
52
+ "spec/lacquer/cache_utils_spec.rb",
53
+ "spec/lacquer/delayed_job_job_spec.rb",
54
+ "spec/lacquer/resque_job_spec.rb",
55
+ "spec/lacquer/varnish_spec.rb",
56
+ "spec/spec_helper.rb"
57
+ ]
58
+
59
+ if s.respond_to? :specification_version then
60
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
61
+ s.specification_version = 3
62
+
63
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
64
+ s.add_runtime_dependency(%q<activesupport>, ["~> 3.0"])
65
+ s.add_runtime_dependency(%q<i18n>, ["~> 0.4"])
66
+ s.add_runtime_dependency(%q<jeweler>, [">= 0"])
67
+ s.add_runtime_dependency(%q<rake>, [">= 0"])
68
+ s.add_runtime_dependency(%q<yard>, [">= 0"])
69
+ s.add_development_dependency(%q<rspec>, ["~> 2.0"])
70
+ else
71
+ s.add_dependency(%q<activesupport>, ["~> 3.0"])
72
+ s.add_dependency(%q<i18n>, ["~> 0.4"])
73
+ s.add_dependency(%q<jeweler>, [">= 0"])
74
+ s.add_dependency(%q<rake>, [">= 0"])
75
+ s.add_dependency(%q<yard>, [">= 0"])
76
+ s.add_dependency(%q<rspec>, ["~> 2.0"])
77
+ end
78
+ else
79
+ s.add_dependency(%q<activesupport>, ["~> 3.0"])
80
+ s.add_dependency(%q<i18n>, ["~> 0.4"])
81
+ s.add_dependency(%q<jeweler>, [">= 0"])
82
+ s.add_dependency(%q<rake>, [">= 0"])
83
+ s.add_dependency(%q<yard>, [">= 0"])
84
+ s.add_dependency(%q<rspec>, ["~> 2.0"])
85
+ end
86
+ end
87
+
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Installs Lacquer into your Rails application.
3
+
4
+ Provides a sample varnish vcl and a Lacquer
5
+ initializer to get you started.
6
+
7
+ Example:
8
+ rails generate lacquer:install
@@ -0,0 +1,15 @@
1
+ module Lacquer
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ source_root File.expand_path(File.join(File.dirname(__FILE__), "templates"))
5
+
6
+ def copy_initializer
7
+ copy_file("initializer.rb", "config/initializers/lacquer.rb")
8
+ end
9
+
10
+ def copy_vcl
11
+ copy_file("varnish.sample.vcl", "config/varnish.sample.vcl")
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,21 @@
1
+ Lacquer.configure do |config|
2
+ # Globally enable/disable cache
3
+ config.enable_cache = true
4
+
5
+ # Unless overridden in a controller or action, the default will be used
6
+ config.default_ttl = 1.week
7
+
8
+ # Can be :none, :delayed_job, :resque
9
+ config.job_backend = :none
10
+
11
+ # Array of Varnish servers to manage
12
+ config.varnish_servers << {
13
+ :host => "0.0.0.0", :port => 6082 # if you have authentication enabled, add :secret => "your secret"
14
+ }
15
+
16
+ # Number of retries
17
+ config.retries = 5
18
+
19
+ # Config handler (optional, if you use Hoptoad or another error tracking service)
20
+ config.command_error_handler = lambda { |s| HoptoadNotifier.notify(s) }
21
+ end
@@ -0,0 +1,102 @@
1
+ backend default {
2
+ .host = "0.0.0.0";
3
+ .port = "10000";
4
+ }
5
+
6
+ # Handling of requests that are received from clients.
7
+ # First decide whether or not to lookup data in the cache.
8
+ sub vcl_recv {
9
+ # Pipe requests that are non-RFC2616 or CONNECT which is weird.
10
+ if (req.request != "GET" &&
11
+ req.request != "HEAD" &&
12
+ req.request != "PUT" &&
13
+ req.request != "POST" &&
14
+ req.request != "TRACE" &&
15
+ req.request != "OPTIONS" &&
16
+ req.request != "DELETE") {
17
+ pipe;
18
+ }
19
+
20
+ # Pass requests that are not GET or HEAD
21
+ if (req.request != "GET" && req.request != "HEAD") {
22
+ pass;
23
+ }
24
+
25
+ # Pass requests that we know we aren't caching
26
+ if (req.url ~ "^/admin") {
27
+ pass;
28
+ }
29
+
30
+ # Handle compression correctly. Varnish treats headers literally, not
31
+ # semantically. So it is very well possible that there are cache misses
32
+ # because the headers sent by different browsers aren't the same.
33
+ # @see: http://varnish.projects.linpro.no/wiki/FAQ/Compression
34
+ if (req.http.Accept-Encoding) {
35
+ if (req.http.Accept-Encoding ~ "gzip") {
36
+ # if the browser supports it, we'll use gzip
37
+ set req.http.Accept-Encoding = "gzip";
38
+ } elsif (req.http.Accept-Encoding ~ "deflate") {
39
+ # next, try deflate if it is supported
40
+ set req.http.Accept-Encoding = "deflate";
41
+ } else {
42
+ # unknown algorithm. Probably junk, remove it
43
+ remove req.http.Accept-Encoding;
44
+ }
45
+ }
46
+
47
+ # Clear cookie and authorization headers, set grace time, lookup in the cache
48
+ unset req.http.Cookie;
49
+ unset req.http.Authorization;
50
+ set req.grace = 30s;
51
+ lookup;
52
+ }
53
+
54
+ # Called when entering pipe mode
55
+ sub vcl_pipe {
56
+ # If we don't set the Connection: close header, any following
57
+ # requests from the client will also be piped through and
58
+ # left untouched by varnish. We don't want that.
59
+ set req.http.connection = "close";
60
+ pipe;
61
+ }
62
+
63
+ # Called when the requested object has been retrieved from the
64
+ # backend, or the request to the backend has failed
65
+ sub vcl_fetch {
66
+ # Do not cache the object if the backend application does not want us to.
67
+ if (obj.http.Cache-Control ~ "(no-cache|no-store|private|must-revalidate)") {
68
+ pass;
69
+ }
70
+
71
+ # Do not cache the object if the status is not in the 200s
72
+ if (obj.status >= 300) {
73
+ # Remove the Set-Cookie header
74
+ remove obj.http.Set-Cookie;
75
+ pass;
76
+ }
77
+
78
+ # Everything below here should be cached
79
+
80
+ # Remove the Set-Cookie header
81
+ remove obj.http.Set-Cookie;
82
+
83
+ # Set the grace time
84
+ set obj.grace = 30s;
85
+
86
+ # Deliver the object
87
+ deliver;
88
+ }
89
+
90
+ # Called before the response is sent back to the client
91
+ sub vcl_deliver {
92
+ # Force browsers and intermediary caches to always check back with us
93
+ set resp.http.Cache-Control = "private, max-age=0, must-revalidate";
94
+ set resp.http.Pragma = "no-cache";
95
+
96
+ # Add a header to indicate a cache HIT/MISS
97
+ if (obj.hits > 0) {
98
+ set resp.http.X-Cache = "HIT";
99
+ } else {
100
+ set resp.http.X-Cache = "MISS";
101
+ }
102
+ }
@@ -0,0 +1,30 @@
1
+ require "rubygems"
2
+ require "net/telnet"
3
+
4
+ require "openssl"
5
+ require "digest/sha2"
6
+
7
+ require "active_support/core_ext"
8
+
9
+ require "lacquer/configuration"
10
+ require "lacquer/cache_utils"
11
+ require "lacquer/varnish"
12
+
13
+ module Lacquer
14
+ class VarnishError < Exception; end # @private
15
+ class AuthenticationError < VarnishError; end # @private
16
+
17
+ class << self
18
+ attr_accessor :configuration
19
+
20
+ # Call this method to modify defaults in your initailizers.
21
+ #
22
+ # Lacquer.configure do |config|
23
+ # config.varnish_servers << { :host => '0.0.0.0', :port => 6082, :timeout => 5 }
24
+ # end
25
+ def configure
26
+ self.configuration ||= Configuration.new
27
+ yield(configuration)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,52 @@
1
+ module Lacquer
2
+ module CacheUtils
3
+ def self.included(base)
4
+ base.class_eval do
5
+ attr_reader :cache_ttl
6
+
7
+ if respond_to? :before_filter
8
+ before_filter :set_default_cache_ttl
9
+ after_filter :send_cache_control_headers
10
+ end
11
+ end
12
+ end
13
+
14
+ # Instance variable for the action ttl.
15
+ def set_cache_ttl(ttl)
16
+ @cache_ttl = ttl
17
+ end
18
+
19
+ # Called as a before filter to set default ttl
20
+ # for the entire application.
21
+ def set_default_cache_ttl
22
+ set_cache_ttl(Lacquer.configuration.default_ttl)
23
+ end
24
+
25
+ # Sends url.purge command to varnish to clear cache.
26
+ #
27
+ # clear_cache_for(root_path, blog_posts_path, '/other/content/*')
28
+ def clear_cache_for(*paths)
29
+ paths.each do |path|
30
+ case Lacquer.configuration.job_backend
31
+ when :delayed_job
32
+ require 'lacquer/delayed_job_job'
33
+ Delayed::Job.enqueue(Lacquer::DelayedJobJob.new(path))
34
+ when :resque
35
+ require 'lacquer/resque_job'
36
+ Resque.enqueue(Lacquer::ResqueJob, path)
37
+ when :none
38
+ Varnish.new.purge(path)
39
+ end
40
+ end
41
+ end
42
+
43
+ # Sends cache control headers with page.
44
+ # These are the headers that varnish responds to
45
+ # to set cache properly.
46
+ def send_cache_control_headers
47
+ if Lacquer.configuration.enable_cache && @cache_ttl && @cache_ttl != 0
48
+ expires_in(@cache_ttl, :public => true)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,39 @@
1
+ module Lacquer
2
+ class Configuration
3
+ OPTIONS = []
4
+
5
+ # Enable cache
6
+ attr_accessor :enable_cache
7
+
8
+ # Varnish servers
9
+ attr_accessor :varnish_servers
10
+
11
+ # Application default ttl
12
+ attr_accessor :default_ttl
13
+
14
+ # Number of retries before failing
15
+ attr_accessor :retries
16
+
17
+ # Job backend
18
+ attr_accessor :job_backend
19
+
20
+ # Error handler
21
+ attr_accessor :command_error_handler
22
+
23
+ def initialize
24
+ @enable_cache = true
25
+ @varnish_servers = []
26
+ @default_ttl = 0
27
+ @job_backend = :none
28
+ @retries = 5
29
+ @command_error_handler = nil
30
+ end
31
+
32
+ # Returns a hash of all configurable options
33
+ def to_hash
34
+ OPTIONS.inject({}) do |hash, option|
35
+ hash.merge(option.to_sym => send(option))
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ module Lacquer
2
+ class DelayedJobJob < Struct.new(:url)
3
+ def perform
4
+ Varnish.new.purge(url)
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ module Lacquer
2
+ class ResqueJob
3
+ @queue = :lacquer
4
+
5
+ def self.perform(url)
6
+ Varnish.new.purge(url)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,78 @@
1
+ module Lacquer
2
+ class Varnish
3
+ def stats
4
+ send_command('stats').collect do |stats|
5
+ stats = stats.split("\n")
6
+ stats.shift
7
+ stats = stats.collect do |stat|
8
+ stat = stat.strip.match(/(\d+)\s+(.+)$/)
9
+ { :key => stat[2], :value => stat[1] } if stat
10
+ end
11
+ end
12
+ end
13
+
14
+ # Sends the command 'url.purge *path*'
15
+ def purge(path)
16
+ send_command('url.purge ' << path).all? do |result|
17
+ result =~ /200/
18
+ end
19
+ end
20
+
21
+ # Sends commands over telnet to varnish servers listed in the config.
22
+ def send_command(command)
23
+ Lacquer.configuration.varnish_servers.collect do |server|
24
+ retries = 0
25
+ response = nil
26
+ begin
27
+ retries += 1
28
+ connection = Net::Telnet.new(
29
+ 'Host' => server[:host],
30
+ 'Port' => server[:port],
31
+ 'Timeout' => server[:timeout] || 5)
32
+
33
+ if(server[:secret])
34
+ connection.waitfor("Match" => /^107/) do |authentication_request|
35
+ matchdata = /^107 \d{2}\s*(.{32}).*$/m.match(authentication_request) # Might be a bit ugly regex, but it works great!
36
+ salt = matchdata[1]
37
+ if(salt.empty?)
38
+ raise VarnishError, "Bad authentication request"
39
+ end
40
+
41
+ digest = OpenSSL::Digest::Digest.new('sha256')
42
+ digest << salt
43
+ digest << "\n"
44
+ digest << server[:secret]
45
+ digest << "\n"
46
+ digest << salt
47
+ digest << "\n"
48
+
49
+ connection.cmd("String" => "auth #{digest.to_s}", "Match" => /\d{3}/) do |auth_response|
50
+ if(!(/^200/ =~ auth_response))
51
+ raise AuthenticationError, "Could not authenticate"
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ connection.cmd('String' => command, 'Match' => /\n\n/) {|r| response = r.split("\n").first.strip}
58
+ connection.close
59
+ rescue Exception => e
60
+ if retries < Lacquer.configuration.retries
61
+ retry
62
+ else
63
+ if Lacquer.configuration.command_error_handler
64
+ Lacquer.configuration.command_error_handler.call({
65
+ :error_class => "Varnish Error, retried #{Lacquer.configuration.retries} times",
66
+ :error_message => "Error while trying to connect to #{server[:host]}:#{server[:port]}: #{e}",
67
+ :parameters => server,
68
+ :response => response })
69
+ else
70
+ raise VarnishError.new("Error while trying to connect to #{server[:host]}:#{server[:port]} #{e}")
71
+ end
72
+ end
73
+ end
74
+ response
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1 @@
1
+ require 'lacquer'
@@ -0,0 +1,81 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '../../spec_helper')
2
+
3
+ describe "Lacquer" do
4
+ before(:each) do
5
+ @controller = ControllerClass.new
6
+ end
7
+
8
+ describe "talking to varnish" do
9
+ before(:each) do
10
+ @varnish_stub = mock('varnish')
11
+ Lacquer::Varnish.stub!(:new).and_return(@varnish_stub)
12
+ end
13
+
14
+ describe "when backend is :none" do
15
+ before(:each) do
16
+ Lacquer.configuration.job_backend = :none
17
+ end
18
+
19
+ it "sends commands to varnish instantly" do
20
+ @varnish_stub.should_receive(:purge).twice
21
+ @controller.clear_cache_for('/', '/blog/posts')
22
+ end
23
+
24
+ it "calls purge with the correct parameter" do
25
+ @varnish_stub.should_receive(:purge).with('/')
26
+ @controller.clear_cache_for('/')
27
+ end
28
+ end
29
+
30
+ describe "when backend is :delayed_job" do
31
+ it "sends commands to a delayed_job queue" do
32
+ Lacquer.configuration.job_backend = :delayed_job
33
+
34
+ Delayed::Job.should_receive(:enqueue).twice
35
+ @controller.clear_cache_for('/', '/blog/posts')
36
+ end
37
+ end
38
+
39
+ describe "when backend is :resque" do
40
+ it "sends commands to a resque queue" do
41
+ Lacquer.configuration.job_backend = :resque
42
+
43
+ Resque.should_receive(:enqueue).twice
44
+ @controller.clear_cache_for('/', '/blog/posts')
45
+ end
46
+ end
47
+ end
48
+
49
+ describe "when cache is enabled" do
50
+ describe "when no custom ttl is set" do
51
+ it "should send cache control headers based on default ttl" do
52
+ Lacquer.configuration.enable_cache = true
53
+ Lacquer.configuration.default_ttl = 1.week
54
+
55
+ @controller.set_default_cache_ttl
56
+ @controller.should_receive(:expires_in).with(1.week, :public => true)
57
+ @controller.send_cache_control_headers
58
+ end
59
+ end
60
+
61
+ describe "when custom ttl is set" do
62
+ it "should send cache control headers based on custom set ttl" do
63
+ Lacquer.configuration.enable_cache = true
64
+
65
+ @controller.set_cache_ttl(10.week)
66
+ @controller.should_receive(:expires_in).with(10.week, :public => true)
67
+ @controller.send_cache_control_headers
68
+ end
69
+ end
70
+ end
71
+
72
+ it "should allow purge by non-controller sweepers" do
73
+ @varnish_stub = mock('varnish')
74
+ Lacquer::Varnish.stub!(:new).and_return(@varnish_stub)
75
+
76
+ @sweeper = SweeperClass.new
77
+
78
+ @varnish_stub.should_receive(:purge)
79
+ @sweeper.clear_cache_for('/')
80
+ end
81
+ end
@@ -0,0 +1,13 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '../../spec_helper')
2
+
3
+ describe "DelayedJobJob" do
4
+ describe "perform" do
5
+ it "should purge the parameter" do
6
+ @varnish_mock = mock('varnish')
7
+ Lacquer::Varnish.stub!(:new).and_return(@varnish_mock)
8
+
9
+ @varnish_mock.should_receive(:purge).with('/').exactly(1).times
10
+ Lacquer::DelayedJobJob.new('/').perform
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '../../spec_helper')
2
+
3
+ describe "ResqueJob" do
4
+ describe "perform" do
5
+ it "should purge the parameter" do
6
+ @varnish_mock = mock('varnish')
7
+ Lacquer::Varnish.stub!(:new).and_return(@varnish_mock)
8
+
9
+ @varnish_mock.should_receive(:purge).with('/').exactly(1).times
10
+ Lacquer::ResqueJob.perform('/')
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,176 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '../../spec_helper')
2
+
3
+ describe "Varnish" do
4
+ before(:each) do
5
+ @telnet_mock = mock('Net::Telnet')
6
+ Net::Telnet.stub!(:new).and_return(@telnet_mock)
7
+ @telnet_mock.stub!(:close)
8
+ @telnet_mock.stub!(:cmd)
9
+ @telnet_mock.stub!(:puts)
10
+ @telnet_mock.stub!(:waitfor)
11
+ Lacquer.configuration.retries.should == 5
12
+ end
13
+
14
+ describe "with any command" do
15
+ describe "when connection is unsuccessful" do
16
+ it "should raise a Lacquer::VarnishError" do
17
+ @telnet_mock.stub!(:cmd).and_raise(Timeout::Error)
18
+ lambda {
19
+ Lacquer::Varnish.new.purge('/')
20
+ }.should raise_error(Lacquer::VarnishError)
21
+ end
22
+
23
+ it "should retry on failure before erroring" do
24
+ @telnet_mock.stub!(:cmd).and_raise(Timeout::Error)
25
+ Net::Telnet.should_receive(:new).exactly(5).times
26
+ lambda {
27
+ Lacquer::Varnish.new.purge('/')
28
+ }.should raise_error(Lacquer::VarnishError)
29
+ end
30
+
31
+ it "should close the connection afterwards" do
32
+ @telnet_mock.should_receive(:close).exactly(1).times
33
+ Lacquer::Varnish.new.purge('/')
34
+ end
35
+ end
36
+
37
+ describe "when using authentication" do
38
+ after(:each) do
39
+ Lacquer.configuration.varnish_servers.first[:secret] = nil
40
+ end
41
+ describe "with correct secret" do
42
+ before(:each) do
43
+ Lacquer.configuration.varnish_servers.first[:secret] = "the real secret"
44
+ end
45
+
46
+ it "should return successfully when using correct secret" do
47
+ @telnet_mock.stub!(:waitfor).with("Match" => /^107/).and_yield('107 59 \nhaalpffwlcvblmdrinpnjwigwsbiiigq\n\nAuthentication required.\n\n')
48
+ @telnet_mock.stub!(:cmd).with("String" => "auth d218942acc92753db0c9fedddb32cde6158de28e903356caed1808cf0e23a15a", "Match" => /\d{3}/).and_yield('200')
49
+ @telnet_mock.stub!(:cmd).with("String" => "url.purge /", "Match" => /\n\n/).and_yield('200')
50
+
51
+ lambda {
52
+ Lacquer::Varnish.new.purge('/')
53
+ }.should_not raise_error
54
+ end
55
+
56
+ after(:each) do
57
+ Lacquer.configuration.varnish_servers.first[:secret] = nil
58
+ end
59
+ end
60
+
61
+ describe "with wrong secret" do
62
+ before(:each) do
63
+ Lacquer.configuration.varnish_servers.first[:secret] = "the wrong secret"
64
+ end
65
+ it "should raise Lacquer::AuthenticationError when using wrong secret" do
66
+ @telnet_mock.stub!(:waitfor).with("Match" => /^107/).and_yield('107 59 \nhaalpffwlcvblmdrinpnjwigwsbiiigq\n\nAuthentication required.\n\n')
67
+ @telnet_mock.stub!(:cmd).with("String" => "auth 49725ec6723b64774a7ab918a24cba811130e99b7ac4b4c9d21ce9a8144762c8", "Match" => /\d{3}/).and_yield('107')
68
+ @telnet_mock.stub!(:cmd).with("url.purge /").and_yield('200')
69
+
70
+ lambda {
71
+ Lacquer::Varnish.new.purge('/')
72
+ }.should raise_error(Lacquer::VarnishError)
73
+ end
74
+ after(:each) do
75
+ Lacquer.configuration.varnish_servers.first[:secret] = nil
76
+ end
77
+ end
78
+ end
79
+
80
+ describe "when connection is unsuccessful and an error handler is set" do
81
+ before(:each) do
82
+ Lacquer.configuration.command_error_handler = mock("command_error_handler")
83
+ end
84
+ it "should call handler on error" do
85
+ @telnet_mock.stub!(:cmd).and_raise(Timeout::Error)
86
+ Lacquer.configuration.command_error_handler.should_receive(:call).exactly(1).times
87
+ lambda {
88
+ Lacquer::Varnish.new.purge('/')
89
+ }.should_not raise_error(Lacquer::VarnishError)
90
+ end
91
+ end
92
+
93
+ end
94
+
95
+ describe "when sending a stats command" do
96
+ it "should return an array of stats" do
97
+ @telnet_mock.stub!(:cmd).and_yield(%Q[
98
+ 200 2023
99
+ 6263596 Client connections accepted
100
+ 6260911 Client requests received
101
+ 919605 Cache hits for pass
102
+ 2123848 Cache misses
103
+ 6723161 Backend conn. success
104
+ 6641493 Fetch with Length
105
+ 81512 Fetch wanted close
106
+ 11 Fetch failed
107
+ 1648 N struct sess_mem
108
+ 81 N struct sess
109
+ 22781 N struct object
110
+ 23040 N struct objectcore
111
+ 36047 N struct objecthead
112
+ 56108 N struct smf
113
+ 1646 N small free smf
114
+ 263 N large free smf
115
+ 55 N struct vbe_conn
116
+ 804 N worker threads
117
+ 1583 N worker threads created
118
+ 2114 N worker threads limited
119
+ 1609 N overflowed work requests
120
+ 1 N backends
121
+ 1693663 N expired objects
122
+ 400637 N LRU nuked objects
123
+ 10 N LRU moved objects
124
+ 254 HTTP header overflows
125
+ 2506470 Objects sent with write
126
+ 6263541 Total Sessions
127
+ 6260911 Total Requests
128
+ 40 Total pipe
129
+ 4599215 Total pass
130
+ 6722994 Total fetch
131
+ 2607029095 Total header bytes
132
+ 55280196533 Total body bytes
133
+ 6263536 Session Closed
134
+ 5 Session herd
135
+ 511352337 SHM records
136
+ 33376035 SHM writes
137
+ 33177 SHM flushes due to overflow
138
+ 208858 SHM MTX contention
139
+ 246 SHM cycles through buffer
140
+ 6361382 allocator requests
141
+ 54199 outstanding allocations
142
+ 953389056 bytes allocated
143
+ 120352768 bytes free
144
+ 323 SMS allocator requests
145
+ 151528 SMS bytes allocated
146
+ 151528 SMS bytes freed
147
+ 6723053 Backend requests made
148
+ 1 N vcl total
149
+ 1 N vcl available
150
+ 49 N total active purges
151
+ 197 N new purges added
152
+ 148 N old purges deleted
153
+ 6117 N objects tested
154
+ 60375 N regexps tested against
155
+ 140 N duplicate purges removed
156
+ 944407 HCB Lookups without lock
157
+ 3 HCB Lookups with lock
158
+ 2099076 HCB Inserts
159
+ 515351 Objects ESI parsed (unlock)
160
+ 35371 Client uptime
161
+
162
+ 500 22
163
+ Closing CLI connection
164
+ ].strip)
165
+ stats = Lacquer::Varnish.new.stats
166
+ stats.size.should be(1)
167
+ end
168
+ end
169
+
170
+ describe "when sending a purge command" do
171
+ it "should return successfully" do
172
+ @telnet_mock.stub!(:cmd).with("String" => "url.purge /", "Match" => /\n\n/).and_yield('200')
173
+ Lacquer::Varnish.new.purge('/').should be(true)
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,33 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
3
+
4
+ require "lacquer"
5
+ require "rspec"
6
+
7
+ class ControllerClass
8
+ def self.before_filter(arg); end
9
+ def self.after_filter(arg); end
10
+
11
+ include Lacquer::CacheUtils
12
+ end
13
+
14
+ class SweeperClass
15
+ include Lacquer::CacheUtils
16
+ end
17
+
18
+ module Delayed;
19
+ module Job; end
20
+ end
21
+
22
+ module Resque; end
23
+
24
+ Lacquer.configure do |config|
25
+ config.enable_cache = true
26
+ config.default_ttl = 1.week
27
+ config.job_backend = :none
28
+ config.varnish_servers << { :host => "0.0.0.0", :port => 6082 }
29
+ end
30
+
31
+ Rspec.configure do |c|
32
+ c.mock_with :rspec
33
+ end
metadata ADDED
@@ -0,0 +1,186 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hakon-lacquer
3
+ version: !ruby/object:Gem::Version
4
+ hash: 13
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 4
9
+ - 1
10
+ version: 0.4.1
11
+ platform: ruby
12
+ authors:
13
+ - Russ Smith (russ@bashme.org)
14
+ - Ryan Johns
15
+ - Garry Tan (garry@posterous.com), Gabe da Silveira (gabe@websaviour.com)
16
+ - "H\xC3\xA5kon Lerring"
17
+ autorequire:
18
+ bindir: bin
19
+ cert_chain: []
20
+
21
+ date: 2010-12-28 00:00:00 +01:00
22
+ default_executable:
23
+ dependencies:
24
+ - !ruby/object:Gem::Dependency
25
+ name: activesupport
26
+ prerelease: false
27
+ requirement: &id001 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ hash: 7
33
+ segments:
34
+ - 3
35
+ - 0
36
+ version: "3.0"
37
+ type: :runtime
38
+ version_requirements: *id001
39
+ - !ruby/object:Gem::Dependency
40
+ name: i18n
41
+ prerelease: false
42
+ requirement: &id002 !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ hash: 3
48
+ segments:
49
+ - 0
50
+ - 4
51
+ version: "0.4"
52
+ type: :runtime
53
+ version_requirements: *id002
54
+ - !ruby/object:Gem::Dependency
55
+ name: jeweler
56
+ prerelease: false
57
+ requirement: &id003 !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ hash: 3
63
+ segments:
64
+ - 0
65
+ version: "0"
66
+ type: :runtime
67
+ version_requirements: *id003
68
+ - !ruby/object:Gem::Dependency
69
+ name: rake
70
+ prerelease: false
71
+ requirement: &id004 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ hash: 3
77
+ segments:
78
+ - 0
79
+ version: "0"
80
+ type: :runtime
81
+ version_requirements: *id004
82
+ - !ruby/object:Gem::Dependency
83
+ name: yard
84
+ prerelease: false
85
+ requirement: &id005 !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ hash: 3
91
+ segments:
92
+ - 0
93
+ version: "0"
94
+ type: :runtime
95
+ version_requirements: *id005
96
+ - !ruby/object:Gem::Dependency
97
+ name: rspec
98
+ prerelease: false
99
+ requirement: &id006 !ruby/object:Gem::Requirement
100
+ none: false
101
+ requirements:
102
+ - - ~>
103
+ - !ruby/object:Gem::Version
104
+ hash: 3
105
+ segments:
106
+ - 2
107
+ - 0
108
+ version: "2.0"
109
+ type: :development
110
+ version_requirements: *id006
111
+ description: Rails drop in for Varnish support.
112
+ email: russ@bashme.org
113
+ executables: []
114
+
115
+ extensions: []
116
+
117
+ extra_rdoc_files:
118
+ - LICENSE
119
+ - README.rdoc
120
+ files:
121
+ - .bundle/config
122
+ - .document
123
+ - Gemfile
124
+ - Gemfile.lock
125
+ - LICENSE
126
+ - README.rdoc
127
+ - Rakefile
128
+ - VERSION
129
+ - init.rb
130
+ - lacquer.gemspec
131
+ - lib/generators/lacquer/USAGE
132
+ - lib/generators/lacquer/install_generator.rb
133
+ - lib/generators/lacquer/templates/initializer.rb
134
+ - lib/generators/lacquer/templates/varnish.sample.vcl
135
+ - lib/lacquer.rb
136
+ - lib/lacquer/cache_utils.rb
137
+ - lib/lacquer/configuration.rb
138
+ - lib/lacquer/delayed_job_job.rb
139
+ - lib/lacquer/resque_job.rb
140
+ - lib/lacquer/varnish.rb
141
+ - rails/init.rb
142
+ - spec/lacquer/cache_utils_spec.rb
143
+ - spec/lacquer/delayed_job_job_spec.rb
144
+ - spec/lacquer/resque_job_spec.rb
145
+ - spec/lacquer/varnish_spec.rb
146
+ - spec/spec_helper.rb
147
+ has_rdoc: true
148
+ homepage: http://github.com/russ/lacquer
149
+ licenses: []
150
+
151
+ post_install_message:
152
+ rdoc_options: []
153
+
154
+ require_paths:
155
+ - lib
156
+ required_ruby_version: !ruby/object:Gem::Requirement
157
+ none: false
158
+ requirements:
159
+ - - ">="
160
+ - !ruby/object:Gem::Version
161
+ hash: 3
162
+ segments:
163
+ - 0
164
+ version: "0"
165
+ required_rubygems_version: !ruby/object:Gem::Requirement
166
+ none: false
167
+ requirements:
168
+ - - ">="
169
+ - !ruby/object:Gem::Version
170
+ hash: 3
171
+ segments:
172
+ - 0
173
+ version: "0"
174
+ requirements: []
175
+
176
+ rubyforge_project:
177
+ rubygems_version: 1.3.7
178
+ signing_key:
179
+ specification_version: 3
180
+ summary: Rails drop in for Varnish support.
181
+ test_files:
182
+ - spec/lacquer/cache_utils_spec.rb
183
+ - spec/lacquer/delayed_job_job_spec.rb
184
+ - spec/lacquer/resque_job_spec.rb
185
+ - spec/lacquer/varnish_spec.rb
186
+ - spec/spec_helper.rb