aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_controller')
-rw-r--r--actionpack/lib/action_controller/cgi_ext/cookie.rb6
-rw-r--r--actionpack/lib/action_controller/dispatcher.rb8
-rw-r--r--actionpack/lib/action_controller/rack_process.rb321
-rwxr-xr-xactionpack/lib/action_controller/request_profiler.rb25
4 files changed, 351 insertions, 9 deletions
diff --git a/actionpack/lib/action_controller/cgi_ext/cookie.rb b/actionpack/lib/action_controller/cgi_ext/cookie.rb
index ef033fb4f3..009ddd1c64 100644
--- a/actionpack/lib/action_controller/cgi_ext/cookie.rb
+++ b/actionpack/lib/action_controller/cgi_ext/cookie.rb
@@ -78,6 +78,12 @@ class CGI #:nodoc:
buf
end
+ # FIXME: work around broken 1.8.7 DelegateClass#respond_to?
+ def respond_to?(method, include_private = false)
+ return true if super(method)
+ return __getobj__.respond_to?(method, include_private)
+ end
+
# Parses a raw cookie string into a hash of <tt>cookie-name => cookie-object</tt>
# pairs.
#
diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb
index 6e1e7a261f..b40f1ba9be 100644
--- a/actionpack/lib/action_controller/dispatcher.rb
+++ b/actionpack/lib/action_controller/dispatcher.rb
@@ -96,7 +96,7 @@ module ActionController
include ActiveSupport::Callbacks
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
- def initialize(output, request = nil, response = nil)
+ def initialize(output = $stdout, request = nil, response = nil)
@output, @request, @response = output, request, response
end
@@ -123,6 +123,12 @@ module ActionController
failsafe_rescue exception
end
+ def call(env)
+ @request = RackRequest.new(env)
+ @response = RackResponse.new
+ dispatch
+ end
+
def reload_application
# Run prepare callbacks before every request in development mode
run_callbacks :prepare_dispatch
diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb
new file mode 100644
index 0000000000..16625519b6
--- /dev/null
+++ b/actionpack/lib/action_controller/rack_process.rb
@@ -0,0 +1,321 @@
+require 'action_controller/cgi_ext'
+require 'action_controller/session/cookie_store'
+
+module ActionController #:nodoc:
+ class RackRequest < AbstractRequest #:nodoc:
+ attr_accessor :env, :session_options
+
+ class SessionFixationAttempt < StandardError #:nodoc:
+ end
+
+ DEFAULT_SESSION_OPTIONS = {
+ :database_manager => CGI::Session::CookieStore, # store data in cookie
+ :prefix => "ruby_sess.", # prefix session file names
+ :session_path => "/", # available to all paths in app
+ :session_key => "_session_id",
+ :cookie_only => true
+ } unless const_defined?(:DEFAULT_SESSION_OPTIONS)
+
+ def initialize(env, session_options = DEFAULT_SESSION_OPTIONS)
+ @session_options = session_options
+ @env = env
+ @cgi = CGIWrapper.new(self)
+ super()
+ end
+
+ # The request body is an IO input stream. If the RAW_POST_DATA environment
+ # variable is already set, wrap it in a StringIO.
+ def body
+ if raw_post = env['RAW_POST_DATA']
+ StringIO.new(raw_post)
+ else
+ @env['rack.input']
+ end
+ end
+
+ def key?(key)
+ @env.key? key
+ end
+
+ def query_parameters
+ @query_parameters ||= self.class.parse_query_parameters(query_string)
+ end
+
+ def request_parameters
+ @request_parameters ||= parse_formatted_request_parameters
+ end
+
+ def cookies
+ return {} unless @env["HTTP_COOKIE"]
+
+ if @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
+ @env["rack.request.cookie_hash"]
+ else
+ @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
+ # According to RFC 2109:
+ # If multiple cookies satisfy the criteria above, they are ordered in
+ # the Cookie header such that those with more specific Path attributes
+ # precede those with less specific. Ordering with respect to other
+ # attributes (e.g., Domain) is unspecified.
+ @env["rack.request.cookie_hash"] =
+ parse_query(@env["rack.request.cookie_string"], ';,').inject({}) { |h, (k,v)|
+ h[k] = Array === v ? v.first : v
+ h
+ }
+ end
+ end
+
+ def host_with_port_without_standard_port_handling
+ if forwarded = @env["HTTP_X_FORWARDED_HOST"]
+ forwarded.split(/,\s?/).last
+ elsif http_host = @env['HTTP_HOST']
+ http_host
+ elsif server_name = @env['SERVER_NAME']
+ server_name
+ else
+ "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
+ end
+ end
+
+ def host
+ host_with_port_without_standard_port_handling.sub(/:\d+$/, '')
+ end
+
+ def port
+ if host_with_port_without_standard_port_handling =~ /:(\d+)$/
+ $1.to_i
+ else
+ standard_port
+ end
+ end
+
+ def remote_addr
+ @env['REMOTE_ADDR']
+ end
+
+ def session
+ unless defined?(@session)
+ if @session_options == false
+ @session = Hash.new
+ else
+ stale_session_check! do
+ if cookie_only? && query_parameters[session_options_with_string_keys['session_key']]
+ raise SessionFixationAttempt
+ end
+ case value = session_options_with_string_keys['new_session']
+ when true
+ @session = new_session
+ when false
+ begin
+ @session = CGI::Session.new(@cgi, session_options_with_string_keys)
+ # CGI::Session raises ArgumentError if 'new_session' == false
+ # and no session cookie or query param is present.
+ rescue ArgumentError
+ @session = Hash.new
+ end
+ when nil
+ @session = CGI::Session.new(@cgi, session_options_with_string_keys)
+ else
+ raise ArgumentError, "Invalid new_session option: #{value}"
+ end
+ @session['__valid_session']
+ end
+ end
+ end
+ @session
+ end
+
+ def reset_session
+ @session.delete if defined?(@session) && @session.is_a?(CGI::Session)
+ @session = new_session
+ end
+
+ private
+ # Delete an old session if it exists then create a new one.
+ def new_session
+ if @session_options == false
+ Hash.new
+ else
+ CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
+ CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
+ end
+ end
+
+ def cookie_only?
+ session_options_with_string_keys['cookie_only']
+ end
+
+ def stale_session_check!
+ yield
+ rescue ArgumentError => argument_error
+ if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
+ begin
+ # Note that the regexp does not allow $1 to end with a ':'
+ $1.constantize
+ rescue LoadError, NameError => const_error
+ raise ActionController::SessionRestoreError, <<-end_msg
+Session contains objects whose class definition isn\'t available.
+Remember to require the classes for all objects kept in the session.
+(Original exception: #{const_error.message} [#{const_error.class}])
+end_msg
+ end
+
+ retry
+ else
+ raise
+ end
+ end
+
+ def session_options_with_string_keys
+ @session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
+ end
+
+ # From Rack::Utils
+ def parse_query(qs, d = '&;')
+ params = {}
+ (qs || '').split(/[#{d}] */n).inject(params) { |h,p|
+ k, v = unescape(p).split('=',2)
+ if cur = params[k]
+ if cur.class == Array
+ params[k] << v
+ else
+ params[k] = [cur, v]
+ end
+ else
+ params[k] = v
+ end
+ }
+
+ return params
+ end
+
+ def unescape(s)
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
+ [$1.delete('%')].pack('H*')
+ }
+ end
+ end
+
+ class RackResponse < AbstractResponse #:nodoc:
+ attr_accessor :status
+
+ def initialize
+ @writer = lambda { |x| @body << x }
+ @block = nil
+ super()
+ end
+
+ def out(output = $stdout, &block)
+ @block = block
+ normalize_headers(@headers)
+ if [204, 304].include?(@status.to_i)
+ @headers.delete "Content-Type"
+ [status.to_i, @headers.to_hash, []]
+ else
+ [status.to_i, @headers.to_hash, self]
+ end
+ end
+ alias to_a out
+
+ def each(&callback)
+ if @body.respond_to?(:call)
+ @writer = lambda { |x| callback.call(x) }
+ @body.call(self, self)
+ else
+ @body.each(&callback)
+ end
+
+ @writer = callback
+ @block.call(self) if @block
+ end
+
+ def write(str)
+ @writer.call str.to_s
+ str
+ end
+
+ def close
+ @body.close if @body.respond_to?(:close)
+ end
+
+ def empty?
+ @block == nil && @body.empty?
+ end
+
+ private
+ def normalize_headers(options = "text/html")
+ if options.is_a?(String)
+ headers['Content-Type'] = options unless headers['Content-Type']
+ else
+ headers['Content-Length'] = options.delete('Content-Length').to_s if options['Content-Length']
+
+ headers['Content-Type'] = options.delete('type') || "text/html"
+ headers['Content-Type'] += "; charset=" + options.delete('charset') if options['charset']
+
+ headers['Content-Language'] = options.delete('language') if options['language']
+ headers['Expires'] = options.delete('expires') if options['expires']
+
+ @status = options.delete('Status') if options['Status']
+ @status ||= 200
+ # Convert 'cookie' header to 'Set-Cookie' headers.
+ # Because Set-Cookie header can appear more the once in the response body,
+ # we store it in a line break seperated string that will be translated to
+ # multiple Set-Cookie header by the handler.
+ if cookie = options.delete('cookie')
+ cookies = []
+
+ case cookie
+ when Array then cookie.each { |c| cookies << c.to_s }
+ when Hash then cookie.each { |_, c| cookies << c.to_s }
+ else cookies << cookie.to_s
+ end
+
+ @output_cookies.each { |c| cookies << c.to_s } if @output_cookies
+
+ headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].compact.join("\n")
+ end
+
+ options.each { |k,v| headers[k] = v }
+ end
+
+ ""
+ end
+ end
+
+ class CGIWrapper < ::CGI
+ def initialize(request, *args)
+ @request = request
+ @args = *args
+ @input = request.body
+
+ super *args
+ end
+
+ def params
+ @params ||= @request.params
+ end
+
+ def cookies
+ @request.cookies
+ end
+
+ def query_string
+ @request.query_string
+ end
+
+ # Used to wrap the normal args variable used inside CGI.
+ def args
+ @args
+ end
+
+ # Used to wrap the normal env_table variable used inside CGI.
+ def env_table
+ @request.env
+ end
+
+ # Used to wrap the normal stdinput variable used inside CGI.
+ def stdinput
+ @input
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/request_profiler.rb b/actionpack/lib/action_controller/request_profiler.rb
index 8a18d194bd..a6471d0c08 100755
--- a/actionpack/lib/action_controller/request_profiler.rb
+++ b/actionpack/lib/action_controller/request_profiler.rb
@@ -17,13 +17,13 @@ module ActionController
reset!
end
- def benchmark(n)
+ def benchmark(n, profiling = false)
@quiet = true
print ' '
result = Benchmark.realtime do
n.times do |i|
- run
+ run(profiling)
print_progress(i)
end
end
@@ -43,8 +43,15 @@ module ActionController
script = File.read(script_path)
source = <<-end_source
- def run
- #{script}
+ def run(profiling = false)
+ if profiling
+ RubyProf.resume do
+ #{script}
+ end
+ else
+ #{script}
+ end
+
old_request_count = request_count
reset!
self.request_count = old_request_count
@@ -91,21 +98,22 @@ module ActionController
def profile(sandbox)
load_ruby_prof
- results = RubyProf.profile { benchmark(sandbox) }
+ benchmark(sandbox, true)
+ results = RubyProf.stop
show_profile_results results
results
end
- def benchmark(sandbox)
+ def benchmark(sandbox, profiling = false)
sandbox.request_count = 0
- elapsed = sandbox.benchmark(options[:n]).to_f
+ elapsed = sandbox.benchmark(options[:n], profiling).to_f
count = sandbox.request_count.to_i
puts '%.2f sec, %d requests, %d req/sec' % [elapsed, count, count / elapsed]
end
def warmup(sandbox)
- Benchmark.realtime { sandbox.run }
+ Benchmark.realtime { sandbox.run(false) }
end
def default_options
@@ -136,6 +144,7 @@ module ActionController
protected
def load_ruby_prof
begin
+ gem 'ruby-prof', '>= 0.6.1'
require 'ruby-prof'
if mode = options[:measure]
RubyProf.measure_mode = RubyProf.const_get(mode.upcase)