提交 0adbeeb0 编写于 作者: Y Yehuda Katz

Got overhead down from 127 to 85. All tests pass:

  * Tentatively replaced HeaderHash with SimpleHeaderHash, which does not preserve
    case but does handle converting Arrays to Strings in to_hash. This requires
    further discussion.
  * Moved default_charset to ActionDispatch::Response to avoid having to hop over
    to ActionController. Ideally, this would be a constant on AD::Response, but
    some tests expect to be able to change it dynamically and I didn't want to change
    them yet.
  * Completely override #initialize from Rack::Response. Previously, it was creating
    a HeaderHash, and then we were creating an entirely new one. There is no way to
    call super without incurring the overhead of creating a HeaderHash.
  * Override #write from Rack::Response. Its implementation tracks Content-Length,
    and doing so adds additional overhead that could be mooted if other middleware
    changes the body. It is more efficiently done at the top-level server.
  * Change sending_file to an instance_variable instead of header inspection. In
    general, if a state is important, it should be set as a property of the response
    not reconstructed later.
  * Set the Etag to @body instead of .body. AS::Cache.expand_cache_key handles
    Arrays fine, and it's more efficient to let it handle the body parts, since
    it is not forced to create a joined String.
  * If we detect the default cache control case, just set it, rather than setting
    the constituent parts and then running the normal (expensive) code to generate
    the string.
上级 9e62d6d1
...@@ -25,8 +25,9 @@ class ::ActionController::ActionControllerError < StandardError #:nodoc: ...@@ -25,8 +25,9 @@ class ::ActionController::ActionControllerError < StandardError #:nodoc:
cattr_accessor :relative_url_root cattr_accessor :relative_url_root
self.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT'] self.relative_url_root = ENV['RAILS_RELATIVE_URL_ROOT']
cattr_accessor :default_charset class << self
self.default_charset = "utf-8" delegate :default_charset=, :to => "ActionDispatch::Response"
end
# cattr_reader :protected_instance_variables # cattr_reader :protected_instance_variables
cattr_accessor :protected_instance_variables cattr_accessor :protected_instance_variables
......
...@@ -145,7 +145,6 @@ def send_file(path, options = {}) #:doc: ...@@ -145,7 +145,6 @@ def send_file(path, options = {}) #:doc:
def send_data(data, options = {}) #:doc: def send_data(data, options = {}) #:doc:
logger.info "Sending data #{options[:filename]}" if logger logger.info "Sending data #{options[:filename]}" if logger
send_file_headers! options.merge(:length => data.bytesize) send_file_headers! options.merge(:length => data.bytesize)
@performed_render = false
render :status => options[:status], :text => data render :status => options[:status], :text => data
end end
...@@ -175,6 +174,8 @@ def send_file_headers!(options) ...@@ -175,6 +174,8 @@ def send_file_headers!(options)
'Content-Transfer-Encoding' => 'binary' 'Content-Transfer-Encoding' => 'binary'
) )
response.sending_file = true
# Fix a problem with IE 6.0 on opening downloaded files: # Fix a problem with IE 6.0 on opening downloaded files:
# If Cache-Control: no-cache is set (which Rails does by default), # If Cache-Control: no-cache is set (which Rails does by default),
# IE removes the file it just downloaded from its cache immediately # IE removes the file it just downloaded from its cache immediately
......
...@@ -52,7 +52,7 @@ def recycle! ...@@ -52,7 +52,7 @@ def recycle!
class TestResponse < ActionDispatch::TestResponse class TestResponse < ActionDispatch::TestResponse
def recycle! def recycle!
@status = 200 @status = 200
@header = Rack::Utils::HeaderHash.new @header = ActionDispatch::Response::SimpleHeaderHash.new
@writer = lambda { |x| @body << x } @writer = lambda { |x| @body << x }
@block = nil @block = nil
@length = 0 @length = 0
......
...@@ -32,18 +32,42 @@ module ActionDispatch # :nodoc: ...@@ -32,18 +32,42 @@ module ActionDispatch # :nodoc:
# end # end
# end # end
class Response < Rack::Response class Response < Rack::Response
class SimpleHeaderHash < Hash
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_accessor :request
attr_reader :cache_control attr_reader :cache_control
attr_writer :header attr_writer :header, :sending_file
alias_method :headers=, :header= alias_method :headers=, :header=
delegate :default_charset, :to => 'ActionController::Base'
def initialize def initialize
super @status = 200
@header = SimpleHeaderHash.new
@cache_control = {} @cache_control = {}
@header = Rack::Utils::HeaderHash.new
@writer = lambda { |x| @body << x }
@block = nil
@length = 0
@body = []
@sending_file = false
yield self if block_given?
end
def write(str)
s = str.to_s
@writer.call s
str
end end
def status=(status) def status=(status)
...@@ -128,20 +152,20 @@ def etag=(etag) ...@@ -128,20 +152,20 @@ def etag=(etag)
end end
end end
def sending_file? CONTENT_TYPE = "Content-Type"
headers["Content-Transfer-Encoding"] == "binary"
end 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].blank?
@content_type ||= Mime::HTML @content_type ||= Mime::HTML
@charset ||= default_charset @charset ||= self.class.default_charset
type = @content_type.to_s.dup type = @content_type.to_s.dup
type << "; charset=#{@charset}" unless sending_file? type << "; charset=#{@charset}" unless @sending_file
headers["Content-Type"] = type headers[CONTENT_TYPE] = type
end end
def prepare! def prepare!
...@@ -168,17 +192,6 @@ def write(str) ...@@ -168,17 +192,6 @@ def write(str)
str str
end end
def set_cookie(key, value)
if value.has_key?(:http_only)
ActiveSupport::Deprecation.warn(
"The :http_only option in ActionController::Response#set_cookie " +
"has been renamed. Please use :httponly instead.", caller)
value[:httponly] ||= value.delete(:http_only)
end
super(key, value)
end
# Returns the response cookies, converted to a Hash of (name => value) pairs # Returns the response cookies, converted to a Hash of (name => value) pairs
# #
# assert_equal 'AuthorOfNewPage', r.cookies['author'] # assert_equal 'AuthorOfNewPage', r.cookies['author']
...@@ -201,7 +214,7 @@ def handle_conditional_get! ...@@ -201,7 +214,7 @@ 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
if request && request.etag_matches?(etag) if request && request.etag_matches?(etag)
self.status = 304 self.status = 304
...@@ -214,30 +227,37 @@ def handle_conditional_get! ...@@ -214,30 +227,37 @@ def handle_conditional_get!
end end
end end
EMPTY_RESPONSE = [" "]
def nonempty_ok_response? def nonempty_ok_response?
ok = !@status || @status == 200 ok = !@status || @status == 200
ok && 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) } !body_parts.respond_to?(:call) && body_parts.any? && body_parts.all? { |part| part.is_a?(String) }
end end
DEFAULT_CACHE_CONTROL = "max-age=0, private, must-revalidate"
def set_conditional_cache_control! def set_conditional_cache_control!
if cache_control.empty? control = cache_control
cache_control.merge!(:public => false, :max_age => 0, :must_revalidate => true)
end
public_cache, max_age, must_revalidate, extras = if control.empty?
cache_control.values_at(:public, :max_age, :must_revalidate, :extras) headers["Cache-Control"] = DEFAULT_CACHE_CONTROL
else
extras = control[:extras]
max_age = control[:max_age]
options = []
options << "max-age=#{max_age}" if max_age
options << (control[:public] ? "public" : "private")
options << "must-revalidate" if control[:must_revalidate]
options.concat(extras) if extras
options = [] headers["Cache-Control"] = options.join(", ")
options << "max-age=#{max_age}" if max_age end
options << (public_cache ? "public" : "private")
options << "must-revalidate" if must_revalidate
options.concat(extras) if extras
headers["Cache-Control"] = options.join(", ")
end end
end end
end end
...@@ -46,11 +46,12 @@ def #{sym}=(obj) # def hair_colors=(obj) ...@@ -46,11 +46,12 @@ def #{sym}=(obj) # def hair_colors=(obj)
end # end end # end
" unless options[:instance_writer] == false } # # instance writer above is generated unless options[:instance_writer] == false " unless options[:instance_writer] == false } # # instance writer above is generated unless options[:instance_writer] == false
EOS EOS
self.send("#{sym}=", yield) if block_given?
end end
end end
def cattr_accessor(*syms) def cattr_accessor(*syms, &blk)
cattr_reader(*syms) cattr_reader(*syms)
cattr_writer(*syms) cattr_writer(*syms, &blk)
end end
end end
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册