1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

More perf work:

* Move #set_cookie and #delete_cookie inline to optimize. These optimizations should
    almost certainly be sent back upstream to Rack. The optimization involves using
    an ivar for cookies instead of indexing into the headers each time.
  * Was able to use a bare Hash for headers now that cookies have their own joining
    semantics (some code assumed that the raw cookies were an Array).
  * Cache blankness of body on body=
  * Improve expand_cache_key for Arrays of a single element (common in our case)
  * Use a simple layout condition check unless conditions are used
  * Cache visible actions
  * Lazily load the UrlRewriter
  * Make etag an ivar that is set on prepare!
This commit is contained in:
Yehuda Katz 2009-08-10 15:49:33 -07:00
parent 0adbeeb0c9
commit 4bf516e072
11 changed files with 128 additions and 100 deletions

View file

@ -6,17 +6,21 @@ module AbstractController
included do included do
extlib_inheritable_accessor(:_layout_conditions) { Hash.new } extlib_inheritable_accessor(:_layout_conditions) { Hash.new }
extlib_inheritable_accessor(:_action_has_layout) { Hash.new }
_write_layout_method _write_layout_method
end end
module ClassMethods module ClassMethods
def inherited(klass) def inherited(klass)
super super
klass._write_layout_method klass.class_eval do
_write_layout_method
@found_layouts = {}
end
end end
def cache_layout(details) def cache_layout(details)
layout = @found_layouts ||= {} layout = @found_layouts
values = details.values_at(:formats, :locale) values = details.values_at(:formats, :locale)
# Cache nil # Cache nil
@ -27,6 +31,28 @@ module AbstractController
end end
end end
# This module is mixed in if layout conditions are provided. This means
# that if no layout conditions are used, this method is not used
module LayoutConditions
# Determines whether the current action has a layout by checking the
# action name against the :only and :except conditions set on the
# layout.
#
# ==== Returns
# Boolean:: True if the action has a layout, false otherwise.
def _action_has_layout?
conditions = _layout_conditions
if only = conditions[:only]
only.include?(action_name)
elsif except = conditions[:except]
!except.include?(action_name)
else
true
end
end
end
# Specify the layout to use for this class. # Specify the layout to use for this class.
# #
# If the specified layout is a: # If the specified layout is a:
@ -43,6 +69,8 @@ module AbstractController
# :only<#to_s, Array[#to_s]>:: A list of actions to apply this layout to. # :only<#to_s, Array[#to_s]>:: A list of actions to apply this layout to.
# :except<#to_s, Array[#to_s]>:: Apply this layout to all actions but this one # :except<#to_s, Array[#to_s]>:: Apply this layout to all actions but this one
def layout(layout, conditions = {}) def layout(layout, conditions = {})
include LayoutConditions unless conditions.empty?
conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} } conditions.each {|k, v| conditions[k] = Array(v).map {|a| a.to_s} }
self._layout_conditions = conditions self._layout_conditions = conditions
@ -176,21 +204,8 @@ module AbstractController
end end
end end
# Determines whether the current action has a layout by checking the
# action name against the :only and :except conditions set on the
# layout.
#
# ==== Returns
# Boolean:: True if the action has a layout, false otherwise.
def _action_has_layout? def _action_has_layout?
conditions = _layout_conditions
if only = conditions[:only]
only.include?(action_name)
elsif except = conditions[:except]
!except.include?(action_name)
else
true true
end end
end end
end
end end

View file

@ -102,11 +102,10 @@ module ActionController
options[:template].sub!(/^\//, '') options[:template].sub!(/^\//, '')
end end
options[:text] = nil if options[:nothing] == true options[:text] = nil if options.delete(:nothing) == true
options[:text] = " " if options.key?(:text) && options[:text].nil?
body = super super || " "
body = [' '] if body.blank?
body
end end
def _handle_method_missing def _handle_method_missing

View file

@ -13,8 +13,10 @@ module ActionController
# Overrides AbstractController::Base#action_method? to return false if the # Overrides AbstractController::Base#action_method? to return false if the
# action name is in the list of hidden actions. # action name is in the list of hidden actions.
def action_method?(action_name) def action_method?(action_name)
self.class.visible_action?(action_name) do
!hidden_actions.include?(action_name) && super !hidden_actions.include?(action_name) && super
end end
end
module ClassMethods module ClassMethods
# Sets all of the actions passed in as hidden actions. # Sets all of the actions passed in as hidden actions.
@ -25,6 +27,16 @@ module ActionController
hidden_actions.merge(args.map! {|a| a.to_s }) hidden_actions.merge(args.map! {|a| a.to_s })
end end
def inherited(klass)
klass.instance_variable_set("@visible_actions", {})
super
end
def visible_action?(action_name)
return @visible_actions[action_name] if @visible_actions.key?(action_name)
@visible_actions[action_name] = yield
end
# Overrides AbstractController::Base#action_methods to remove any methods # Overrides AbstractController::Base#action_methods to remove any methods
# that are listed as hidden methods. # that are listed as hidden methods.
def action_methods def action_methods

View file

@ -4,15 +4,6 @@ module ActionController
include RackConvenience include RackConvenience
def process_action(*)
initialize_current_url
super
end
def initialize_current_url
@url = UrlRewriter.new(request, params.clone)
end
# Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in
# the form of a hash, just like the one you would use for url_for directly. Example: # the form of a hash, just like the one you would use for url_for directly. Example:
# #
@ -40,6 +31,7 @@ module ActionController
when String when String
options options
when Hash when Hash
@url ||= UrlRewriter.new(request, params)
@url.rewrite(rewrite_options(options)) @url.rewrite(rewrite_options(options))
else else
polymorphic_url(options) polymorphic_url(options)

View file

@ -52,7 +52,7 @@ module ActionController #:nodoc:
class TestResponse < ActionDispatch::TestResponse class TestResponse < ActionDispatch::TestResponse
def recycle! def recycle!
@status = 200 @status = 200
@header = ActionDispatch::Response::SimpleHeaderHash.new @header = {}
@writer = lambda { |x| @body << x } @writer = lambda { |x| @body << x }
@block = nil @block = nil
@length = 0 @length = 0

View file

@ -179,7 +179,6 @@ module ActionController
if @controller if @controller
@controller.request = @request @controller.request = @request
@controller.params = {} @controller.params = {}
@controller.send(:initialize_current_url)
end end
end end

View file

@ -173,21 +173,16 @@ module ActionDispatch
end end
end end
# Expand raw_formats by converting Mime::ALL to the Mime::SET.
#
def formats def formats
return raw_formats if ActionController::Base.use_accept_header
# if ActionController::Base.use_accept_header if param = parameters[:format]
# raw_formats.tap do |ret| Array.wrap(Mime[param])
# if ret == ONLY_ALL else
# ret.replace Mime::SET accepts.dup
# elsif all = ret.index(Mime::ALL) end
# ret.delete_at(all) && ret.insert(all, *Mime::SET) else
# end [format]
# end end
# else
# raw_formats + Mime::SET
# end
end end
# Sets the \format by string extension, which can be used to force custom formats # Sets the \format by string extension, which can be used to force custom formats
@ -488,7 +483,7 @@ EOM
# matches the order array. # matches the order array.
# #
def negotiate_mime(order) def negotiate_mime(order)
raw_formats.each do |priority| formats.each do |priority|
if priority == Mime::ALL if priority == Mime::ALL
return order.first return order.first
elsif order.include?(priority) elsif order.include?(priority)
@ -501,18 +496,6 @@ EOM
private private
def raw_formats
if ActionController::Base.use_accept_header
if param = parameters[:format]
Array.wrap(Mime[param])
else
accepts.dup
end
else
[format]
end
end
def named_host?(host) def named_host?(host)
!(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host)) !(host.nil? || /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.match(host))
end end

View file

@ -32,18 +32,7 @@ module ActionDispatch # :nodoc:
# end # end
# end # end
class Response < Rack::Response class Response < Rack::Response
class SimpleHeaderHash < Hash attr_accessor :request, :blank
def to_hash
result = {}
each do |k,v|
v = v.join("\n") if v.is_a?(Array)
result[k] = v
end
result
end
end
attr_accessor :request
attr_reader :cache_control attr_reader :cache_control
attr_writer :header, :sending_file attr_writer :header, :sending_file
@ -51,19 +40,23 @@ module ActionDispatch # :nodoc:
def initialize def initialize
@status = 200 @status = 200
@header = SimpleHeaderHash.new @header = {}
@cache_control = {} @cache_control = {}
@writer = lambda { |x| @body << x } @writer = lambda { |x| @body << x }
@block = nil @block = nil
@length = 0 @length = 0
@body = [] @body, @cookie = [], []
@sending_file = false @sending_file = false
yield self if block_given? yield self if block_given?
end end
def cache_control
@cache_control ||= {}
end
def write(str) def write(str)
s = str.to_s s = str.to_s
@writer.call s @writer.call s
@ -95,7 +88,10 @@ module ActionDispatch # :nodoc:
str str
end end
EMPTY = " "
def body=(body) def body=(body)
@blank = true if body == EMPTY
@body = body.respond_to?(:to_str) ? [body] : body @body = body.respond_to?(:to_str) ? [body] : body
end end
@ -137,19 +133,16 @@ module ActionDispatch # :nodoc:
end end
def etag def etag
headers['ETag'] @etag
end end
def etag? def etag?
headers.include?('ETag') @etag
end end
def etag=(etag) def etag=(etag)
if etag.blank? key = ActiveSupport::Cache.expand_cache_key(etag)
headers.delete('ETag') @etag = %("#{Digest::MD5.hexdigest(key)}")
else
headers['ETag'] = %("#{Digest::MD5.hexdigest(ActiveSupport::Cache.expand_cache_key(etag))}")
end
end end
CONTENT_TYPE = "Content-Type" CONTENT_TYPE = "Content-Type"
@ -157,7 +150,7 @@ module ActionDispatch # :nodoc:
cattr_accessor(:default_charset) { "utf-8" } cattr_accessor(:default_charset) { "utf-8" }
def assign_default_content_type_and_charset! def assign_default_content_type_and_charset!
return if !headers[CONTENT_TYPE].blank? return if headers[CONTENT_TYPE].present?
@content_type ||= Mime::HTML @content_type ||= Mime::HTML
@charset ||= self.class.default_charset @charset ||= self.class.default_charset
@ -171,7 +164,8 @@ module ActionDispatch # :nodoc:
def prepare! def prepare!
assign_default_content_type_and_charset! assign_default_content_type_and_charset!
handle_conditional_get! handle_conditional_get!
self["Set-Cookie"] ||= "" self["Set-Cookie"] = @cookie.join("\n")
self["ETag"] = @etag if @etag
end end
def each(&callback) def each(&callback)
@ -197,7 +191,7 @@ module ActionDispatch # :nodoc:
# assert_equal 'AuthorOfNewPage', r.cookies['author'] # assert_equal 'AuthorOfNewPage', r.cookies['author']
def cookies def cookies
cookies = {} cookies = {}
if header = headers['Set-Cookie'] if header = @cookie
header = header.split("\n") if header.respond_to?(:to_str) header = header.split("\n") if header.respond_to?(:to_str)
header.each do |cookie| header.each do |cookie|
if pair = cookie.split(';').first if pair = cookie.split(';').first
@ -209,9 +203,40 @@ module ActionDispatch # :nodoc:
cookies cookies
end end
def set_cookie(key, value)
case value
when Hash
domain = "; domain=" + value[:domain] if value[:domain]
path = "; path=" + value[:path] if value[:path]
# According to RFC 2109, we need dashes here.
# N.B.: cgi.rb uses spaces...
expires = "; expires=" + value[:expires].clone.gmtime.
strftime("%a, %d-%b-%Y %H:%M:%S GMT") if value[:expires]
secure = "; secure" if value[:secure]
httponly = "; HttpOnly" if value[:httponly]
value = value[:value]
end
value = [value] unless Array === value
cookie = Rack::Utils.escape(key) + "=" +
value.map { |v| Rack::Utils.escape v }.join("&") +
"#{domain}#{path}#{expires}#{secure}#{httponly}"
@cookie << cookie
end
def delete_cookie(key, value={})
@cookie.reject! { |cookie|
cookie =~ /\A#{Rack::Utils.escape(key)}=/
}
set_cookie(key,
{:value => '', :path => nil, :domain => nil,
:expires => Time.at(0) }.merge(value))
end
private private
def handle_conditional_get! def handle_conditional_get!
if etag? || last_modified? || !cache_control.empty? if etag? || last_modified? || !@cache_control.empty?
set_conditional_cache_control! set_conditional_cache_control!
elsif nonempty_ok_response? elsif nonempty_ok_response?
self.etag = @body self.etag = @body
@ -227,21 +252,18 @@ module ActionDispatch # :nodoc:
end end
end end
EMPTY_RESPONSE = [" "]
def nonempty_ok_response? def nonempty_ok_response?
ok = !@status || @status == 200 @status == 200 && string_body?
ok && string_body? && @body != EMPTY_RESPONSE
end end
def string_body? def string_body?
!body_parts.respond_to?(:call) && body_parts.any? && body_parts.all? { |part| part.is_a?(String) } !@blank && @body.respond_to?(:all?) && @body.all? { |part| part.is_a?(String) }
end end
DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate" DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
def set_conditional_cache_control! def set_conditional_cache_control!
control = cache_control control = @cache_control
if control.empty? if control.empty?
headers["Cache-Control"] = DEFAULT_CACHE_CONTROL headers["Cache-Control"] = DEFAULT_CACHE_CONTROL

View file

@ -72,7 +72,6 @@ module ActionView
@response = ActionController::TestResponse.new @response = ActionController::TestResponse.new
@params = {} @params = {}
send(:initialize_current_url)
end end
end end

View file

@ -536,7 +536,6 @@ class FragmentCachingTest < ActionController::TestCase
@controller.params = @params @controller.params = @params
@controller.request = @request @controller.request = @request
@controller.response = @response @controller.response = @response
@controller.send(:initialize_current_url)
@controller.send(:initialize_template_class, @response) @controller.send(:initialize_template_class, @response)
@controller.send(:assign_shortcuts, @request, @response) @controller.send(:assign_shortcuts, @request, @response)
end end

View file

@ -62,19 +62,27 @@ module ActiveSupport
end end
end end
RAILS_CACHE_ID = ENV["RAILS_CACHE_ID"]
RAILS_APP_VERION = ENV["RAILS_APP_VERION"]
EXPANDED_CACHE = RAILS_CACHE_ID || RAILS_APP_VERION
def self.expand_cache_key(key, namespace = nil) def self.expand_cache_key(key, namespace = nil)
expanded_cache_key = namespace ? "#{namespace}/" : "" expanded_cache_key = namespace ? "#{namespace}/" : ""
if ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"] if EXPANDED_CACHE
expanded_cache_key << "#{ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]}/" expanded_cache_key << "#{RAILS_CACHE_ID || RAILS_APP_VERION}/"
end end
expanded_cache_key << case expanded_cache_key <<
when key.respond_to?(:cache_key) if key.respond_to?(:cache_key)
key.cache_key key.cache_key
when key.is_a?(Array) elsif key.is_a?(Array)
if key.size > 1
key.collect { |element| expand_cache_key(element) }.to_param key.collect { |element| expand_cache_key(element) }.to_param
when key else
key.first.to_param
end
elsif key
key.to_param key.to_param
end.to_s end.to_s