Skip to content

Commit

Permalink
Added Varnish CacheManager class and initial integration into the exp…
Browse files Browse the repository at this point in the history
…iration workflow
  • Loading branch information
bfcapell authored and Jon de Andres committed Oct 9, 2012
1 parent deefde1 commit 6f9f017
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 39 deletions.
13 changes: 1 addition & 12 deletions ubiquo_design/app/models/page.rb
Expand Up @@ -125,7 +125,7 @@ def publish
:published_id => published_page.id
)

expire_varnish if Rails.env.production?
UbiquoDesign.cache_manager.expire_page(self) if Rails.env.production?
end
return true
rescue Exception => e
Expand Down Expand Up @@ -157,17 +157,6 @@ def expire_varnish
true
end

def varnish_request(url)
Rails.logger.warn "PURGING #{url}"
puts "PURGING #{url}"
begin
http = Net::HTTP.new(VARNISH_SERVER)
http.send_request('BAN', url)
rescue
''
end
end

# Returns true if the page has been published
def published?
published_id
Expand Down
15 changes: 0 additions & 15 deletions ubiquo_design/lib/ubiquo_design/cache_managers/base.rb
Expand Up @@ -286,21 +286,6 @@ def with_instance_content(widget, options)
if options[:current_model].present? && options[:current_model].to_s != key.to_s
next
end
if val[:identifier].is_a?(Hash)
if options[:scope].respond_to?(:params)
true_identifier = val[:identifier].keys.first
else
true_identifier = val[:identifier].values.first
end
elsif val[:identifier].is_a?(Array)
if options[:scope].respond_to?(:params)
true_identifier = val[:identifier].first
else
true_identifier = val[:identifier].last
end
else
true_identifier = val[:identifier]
end

p_i = key.to_s + '_'
p_i += process_params(val, options)
Expand Down
102 changes: 102 additions & 0 deletions ubiquo_design/lib/ubiquo_design/cache_managers/varnish.rb
@@ -0,0 +1,102 @@
module UbiquoDesign
module CacheManagers
# Varnish implementation for the cache manager
class Varnish < UbiquoDesign::CacheManagers::Base

# CONFIG = Ubiquo::Config.context(:ubiquo_design).get(:varnish)

class << self

# This method is called when rendering +page+ and returns a hash where
# the keys are the ids of the page widgets that are esi widgets,
# and the value is an esi:include tag
def multi_get(page, options = {})
{}.tap do |widgets_by_id|
request = options[:scope].request
page.blocks.each do |block|
block.real_block.widgets.each do |widget|
if esi_widget?(widget)
new_params = request.query_parameters.merge('widget' => widget.id)
esi_url = request.url.gsub("?#{request.query_string}", '') + "?#{new_params.to_query}"
widgets_by_id[widget.id] = "<esi:include src=#{esi_url.to_json} />"
end
end
end
end
end

# Caches the content of a widget
# Simply return, since the real caching is done by Varnish when the request is finished
def cache(widget_id, contents, options = {}); end

# Returns true if the widget is an esi widget
def esi_widget?(widget)
# TODO
defined? ESI_ENABLED
end

# Expires the applicable content of a widget given its id
# This means all the urls where the widget is cached
# +widget+ is a Widget instance
def expire(widget)
Rails.logger.debug "-- Expiring Varnish --"

# We ban all the urls of the related page that also contain the widget id
# e.g. /url/of/page?param=4&widget=42
ban_url = Regexp.escape(widget.page.url + "?") + ".*widget=#{widget.id}"

# And we also ban all the urls that do not contain the widget param
# (i.e. the "full page", which can have different representations if
# it has different params).
# This is needed since else the esi fragment would be new,
# but the page would still be cached.
# The other cached pages with the "widget" param are in fact
# other widgets of this page, which have not been modified
# e.g. /url/of/page?param=4 (this will be expired because !~)
ban_negative_url = Regexp.escape(widget.page.url + '?') + ".*widget="

# Now do the real job. This is the correct order to avoid recaching old data
ban(ban_url)
ban_negative(ban_negative_url)
end

# Expires a +page+, with all its possibles urls and params
def expire_page(page)
# We cannot simply ban url_page* since url_page could be a segment of
# another page, so:
# ban the url_page with params
ban(Regexp.escape(page.url + "?"))
# ban the exact page url, with or without trailing slash
ban(Regexp.escape(page.url) + "[\/]?$")
end

protected

# Bans all urls that match +url+ (which is interpreted as a regexp)
def ban(url)
varnish_request('BAN', url)
end

# Bans all urls not matching +url+
def ban_negative(url)
varnish_request('BAN_NEG', url)
end

# removes the widget content from the store
def varnish_request method, url
Rails.logger.debug "Varnish #{method} request for url #{url}"
# TODO deal with multiple servers
begin
http = Net::HTTP.new(VARNISH_SERVER, VARNISH_PORT)
http.set_debug_output($stderr) # TODO temporal
http.send_request(method, url)
rescue
Rails.logger.warn "Cache is not available, impossible to delete cache: "+ $!.inspect
end
end

end

end
end
end
15 changes: 3 additions & 12 deletions ubiquo_design/lib/ubiquo_design/render_page.rb
Expand Up @@ -22,21 +22,12 @@ def render_page(page)
def render_block(block, cached_widgets = {})
uhook_collect_widgets(block) do |widget|
next unless widget.valid?
if esi_widget?(widget)
new_params = request.query_parameters.merge('widget' => widget.id)
"<esi:include src=#{"#{request.url.gsub(request.query_string, new_params.to_query)}".to_json} />"
else
(cached_widgets[widget.id] || render_widget(widget)).tap do |output|
# A widget didn't return an string, return inmediately
return unless output
end
(cached_widgets[widget.id] || render_widget(widget)).tap do |output|
# A widget didn't return an string, return inmediately
return unless output
end
end
end

def esi_widget?(widget)
Rails.env.production?
end

def template_directory
Rails.env.test? ? File.join(ActiveSupport::TestCase.fixture_path, "templates") :
Expand Down

0 comments on commit 6f9f017

Please sign in to comment.