aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib')
-rwxr-xr-xactionpack/lib/action_controller.rb1
-rw-r--r--actionpack/lib/action_controller/caching/actions.rb20
-rw-r--r--actionpack/lib/action_controller/dispatcher.rb10
-rw-r--r--actionpack/lib/action_controller/rack_process.rb325
-rw-r--r--actionpack/lib/action_controller/routing.rb2
-rw-r--r--actionpack/lib/action_pack/version.rb4
6 files changed, 354 insertions, 8 deletions
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 810a5fb9b5..3c4a339d50 100755
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -53,6 +53,7 @@ require 'action_controller/streaming'
require 'action_controller/session_management'
require 'action_controller/http_authentication'
require 'action_controller/components'
+require 'action_controller/rack_process'
require 'action_controller/record_identifier'
require 'action_controller/request_forgery_protection'
require 'action_controller/headers'
diff --git a/actionpack/lib/action_controller/caching/actions.rb b/actionpack/lib/action_controller/caching/actions.rb
index 1ef9e60a21..c4b0a97a33 100644
--- a/actionpack/lib/action_controller/caching/actions.rb
+++ b/actionpack/lib/action_controller/caching/actions.rb
@@ -40,6 +40,8 @@ module ActionController #:nodoc:
# controller.send(:list_url, c.params[:id]) }
# end
#
+ # If you pass :layout => false, it will only cache your action content. It is useful when your layout has dynamic information.
+ #
module Actions
def self.included(base) #:nodoc:
base.extend(ClassMethods)
@@ -54,7 +56,8 @@ module ActionController #:nodoc:
def caches_action(*actions)
return unless cache_configured?
options = actions.extract_options!
- around_filter(ActionCacheFilter.new(:cache_path => options.delete(:cache_path)), {:only => actions}.merge(options))
+ cache_filter = ActionCacheFilter.new(:layout => options.delete(:layout), :cache_path => options.delete(:cache_path))
+ around_filter(cache_filter, {:only => actions}.merge(options))
end
end
@@ -81,7 +84,9 @@ module ActionController #:nodoc:
if cache = controller.read_fragment(cache_path.path)
controller.rendered_action_cache = true
set_content_type!(controller, cache_path.extension)
- controller.send!(:render_for_text, cache)
+ options = { :text => cache }
+ options.merge!(:layout => true) if cache_layout?
+ controller.send!(:render, options)
false
else
controller.action_cache_path = cache_path
@@ -90,7 +95,8 @@ module ActionController #:nodoc:
def after(controller)
return if controller.rendered_action_cache || !caching_allowed(controller)
- controller.write_fragment(controller.action_cache_path.path, controller.response.body)
+ action_content = cache_layout? ? content_for_layout(controller) : controller.response.body
+ controller.write_fragment(controller.action_cache_path.path, action_content)
end
private
@@ -105,6 +111,14 @@ module ActionController #:nodoc:
def caching_allowed(controller)
controller.request.get? && controller.response.headers['Status'].to_i == 200
end
+
+ def cache_layout?
+ @options[:layout] == false
+ end
+
+ def content_for_layout(controller)
+ controller.response.layout && controller.response.template.instance_variable_get('@content_for_layout')
+ end
end
class ActionCachePath
diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb
index 6e1e7a261f..fe4f6b4a7e 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(@request)
+ dispatch
+ end
+
def reload_application
# Run prepare callbacks before every request in development mode
run_callbacks :prepare_dispatch
@@ -135,7 +141,7 @@ module ActionController
# be reloaded on the next request without restarting the server.
def cleanup_application
ActiveRecord::Base.reset_subclasses if defined?(ActiveRecord)
- Dependencies.clear
+ ActiveSupport::Dependencies.clear
ActiveRecord::Base.clear_reloadable_connections! if defined?(ActiveRecord)
end
diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb
new file mode 100644
index 0000000000..d5fb78c44d
--- /dev/null
+++ b/actionpack/lib/action_controller/rack_process.rb
@@ -0,0 +1,325 @@
+require 'action_controller/cgi_ext'
+require 'action_controller/session/cookie_store'
+
+module ActionController #:nodoc:
+ class RackRequest < AbstractRequest #:nodoc:
+ attr_accessor :env, :session_options
+ attr_reader :cgi
+
+ 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(request)
+ @request = request
+ @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
+
+ @request.cgi.output_cookies.each { |c| cookies << c.to_s } if @request.cgi.output_cookies
+
+ headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].flatten.compact
+ end
+
+ options.each { |k,v| headers[k] = v }
+ end
+
+ ""
+ end
+ end
+
+ class CGIWrapper < ::CGI
+ attr_reader :output_cookies
+
+ 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/routing.rb b/actionpack/lib/action_controller/routing.rb
index 6aa266513d..8846dcc504 100644
--- a/actionpack/lib/action_controller/routing.rb
+++ b/actionpack/lib/action_controller/routing.rb
@@ -369,7 +369,7 @@ module ActionController
Routes = RouteSet.new
- ::Inflector.module_eval do
+ ActiveSupport::Inflector.module_eval do
# Ensures that routes are reloaded when Rails inflections are updated.
def inflections_with_route_reloading(&block)
returning(inflections_without_route_reloading(&block)) {
diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb
index 70fc1ced8c..c67654d9a8 100644
--- a/actionpack/lib/action_pack/version.rb
+++ b/actionpack/lib/action_pack/version.rb
@@ -1,8 +1,8 @@
module ActionPack #:nodoc:
module VERSION #:nodoc:
MAJOR = 2
- MINOR = 0
- TINY = 991
+ MINOR = 1
+ TINY = 0
STRING = [MAJOR, MINOR, TINY].join('.')
end