aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller
diff options
context:
space:
mode:
authorHongli Lai (Phusion) <hongli@phusion.nl>2008-12-09 01:38:17 +0100
committerHongli Lai (Phusion) <hongli@phusion.nl>2008-12-09 01:38:17 +0100
commit13c6c3cfc59ff0b400b294dce15f32752b0fb5f5 (patch)
tree052ac9c8f4adb91a4b32e3b2a97b1bc6bdace2e6 /actionpack/lib/action_controller
parentccb96f2297e8783165cba764e9b5d51e1a15ff87 (diff)
parent4e60eebae05aeec65e4894e3901c9d61c9b32910 (diff)
downloadrails-13c6c3cfc59ff0b400b294dce15f32752b0fb5f5.tar.gz
rails-13c6c3cfc59ff0b400b294dce15f32752b0fb5f5.tar.bz2
rails-13c6c3cfc59ff0b400b294dce15f32752b0fb5f5.zip
Merge commit 'origin/master' into savepoints
Diffstat (limited to 'actionpack/lib/action_controller')
-rw-r--r--actionpack/lib/action_controller/caching/fragments.rb26
-rw-r--r--actionpack/lib/action_controller/caching/pages.rb26
-rw-r--r--actionpack/lib/action_controller/cgi_process.rb216
-rw-r--r--actionpack/lib/action_controller/dispatcher.rb83
-rw-r--r--actionpack/lib/action_controller/failsafe.rb52
-rw-r--r--actionpack/lib/action_controller/integration.rb156
-rw-r--r--actionpack/lib/action_controller/rack_process.rb11
-rw-r--r--actionpack/lib/action_controller/rescue.rb66
-rw-r--r--actionpack/lib/action_controller/resources.rb7
-rw-r--r--actionpack/lib/action_controller/routing.rb6
-rw-r--r--actionpack/lib/action_controller/session/active_record_store.rb10
-rw-r--r--actionpack/lib/action_controller/templates/rescues/diagnostics.erb4
-rw-r--r--actionpack/lib/action_controller/templates/rescues/template_error.erb4
13 files changed, 312 insertions, 355 deletions
diff --git a/actionpack/lib/action_controller/caching/fragments.rb b/actionpack/lib/action_controller/caching/fragments.rb
index c41b1a12cf..95cba0e411 100644
--- a/actionpack/lib/action_controller/caching/fragments.rb
+++ b/actionpack/lib/action_controller/caching/fragments.rb
@@ -83,15 +83,23 @@ module ActionController #:nodoc:
end
end
- # Name can take one of three forms:
- # * String: This would normally take the form of a path like "pages/45/notes"
- # * Hash: Is treated as an implicit call to url_for, like { :controller => "pages", :action => "notes", :id => 45 }
- # * Regexp: Will destroy all the matched fragments, example:
- # %r{pages/\d*/notes}
- # Ensure you do not specify start and finish in the regex (^$) because
- # the actual filename matched looks like ./cache/filename/path.cache
- # Regexp expiration is only supported on caches that can iterate over
- # all keys (unlike memcached).
+ # Removes fragments from the cache.
+ #
+ # +key+ can take one of three forms:
+ # * String - This would normally take the form of a path, like
+ # <tt>"pages/45/notes"</tt>.
+ # * Hash - Treated as an implicit call to +url_for+, like
+ # <tt>{:controller => "pages", :action => "notes", :id => 45}</tt>
+ # * Regexp - Will remove any fragment that matches, so
+ # <tt>%r{pages/\d*/notes}</tt> might remove all notes. Make sure you
+ # don't use anchors in the regex (<tt>^</tt> or <tt>$</tt>) because
+ # the actual filename matched looks like
+ # <tt>./cache/filename/path.cache</tt>. Note: Regexp expiration is
+ # only supported on caches that can iterate over all keys (unlike
+ # memcached).
+ #
+ # +options+ is passed through to the cache store's <tt>delete</tt>
+ # method (or <tt>delete_matched</tt>, for Regexp keys.)
def expire_fragment(key, options = nil)
return unless cache_configured?
diff --git a/actionpack/lib/action_controller/caching/pages.rb b/actionpack/lib/action_controller/caching/pages.rb
index a70ed72f03..22e4fbec43 100644
--- a/actionpack/lib/action_controller/caching/pages.rb
+++ b/actionpack/lib/action_controller/caching/pages.rb
@@ -33,28 +33,26 @@ module ActionController #:nodoc:
#
# Additionally, you can expire caches using Sweepers that act on changes in the model to determine when a cache is supposed to be
# expired.
- #
- # == Setting the cache directory
- #
- # The cache directory should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>.
- # For Rails, this directory has already been set to Rails.public_path (which is usually set to <tt>RAILS_ROOT + "/public"</tt>). Changing
- # this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your
- # web server to look in the new location for cached files.
- #
- # == Setting the cache extension
- #
- # Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. In these cases, the page caching mechanism will add one in
- # order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is <tt>.html</tt>.
- # If you want something else, like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. In cases where a request already has an
- # extension, such as <tt>.xml</tt> or <tt>.rss</tt>, page caching will not add an extension. This allows it to work well with RESTful apps.
module Pages
def self.included(base) #:nodoc:
base.extend(ClassMethods)
base.class_eval do
@@page_cache_directory = defined?(Rails.public_path) ? Rails.public_path : ""
+ ##
+ # :singleton-method:
+ # The cache directory should be the document root for the web server and is set using <tt>Base.page_cache_directory = "/document/root"</tt>.
+ # For Rails, this directory has already been set to Rails.public_path (which is usually set to <tt>RAILS_ROOT + "/public"</tt>). Changing
+ # this setting can be useful to avoid naming conflicts with files in <tt>public/</tt>, but doing so will likely require configuring your
+ # web server to look in the new location for cached files.
cattr_accessor :page_cache_directory
@@page_cache_extension = '.html'
+ ##
+ # :singleton-method:
+ # Most Rails requests do not have an extension, such as <tt>/weblog/new</tt>. In these cases, the page caching mechanism will add one in
+ # order to make it easy for the cached files to be picked up properly by the web server. By default, this cache extension is <tt>.html</tt>.
+ # If you want something else, like <tt>.php</tt> or <tt>.shtml</tt>, just set Base.page_cache_extension. In cases where a request already has an
+ # extension, such as <tt>.xml</tt> or <tt>.rss</tt>, page caching will not add an extension. This allows it to work well with RESTful apps.
cattr_accessor :page_cache_extension
end
end
diff --git a/actionpack/lib/action_controller/cgi_process.rb b/actionpack/lib/action_controller/cgi_process.rb
index 45b51a7488..5d6988e1b1 100644
--- a/actionpack/lib/action_controller/cgi_process.rb
+++ b/actionpack/lib/action_controller/cgi_process.rb
@@ -1,184 +1,72 @@
require 'action_controller/cgi_ext'
module ActionController #:nodoc:
- class Base
- # Process a request extracted from a CGI object and return a response. Pass false as <tt>session_options</tt> to disable
- # sessions (large performance increase if sessions are not needed). The <tt>session_options</tt> are the same as for CGI::Session:
- #
- # * <tt>:database_manager</tt> - standard options are CGI::Session::FileStore, CGI::Session::MemoryStore, and CGI::Session::PStore
- # (default). Additionally, there is CGI::Session::DRbStore and CGI::Session::ActiveRecordStore. Read more about these in
- # lib/action_controller/session.
- # * <tt>:session_key</tt> - the parameter name used for the session id. Defaults to '_session_id'.
- # * <tt>:session_id</tt> - the session id to use. If not provided, then it is retrieved from the +session_key+ cookie, or
- # automatically generated for a new session.
- # * <tt>:new_session</tt> - if true, force creation of a new session. If not set, a new session is only created if none currently
- # exists. If false, a new session is never created, and if none currently exists and the +session_id+ option is not set,
- # an ArgumentError is raised.
- # * <tt>:session_expires</tt> - the time the current session expires, as a Time object. If not set, the session will continue
- # indefinitely.
- # * <tt>:session_domain</tt> - the hostname domain for which this session is valid. If not set, defaults to the hostname of the
- # server.
- # * <tt>:session_secure</tt> - if +true+, this session will only work over HTTPS.
- # * <tt>:session_path</tt> - the path for which this session applies. Defaults to the directory of the CGI script.
- # * <tt>:cookie_only</tt> - if +true+ (the default), session IDs will only be accepted from cookies and not from
- # the query string or POST parameters. This protects against session fixation attacks.
- def self.process_cgi(cgi = CGI.new, session_options = {})
- new.process_cgi(cgi, session_options)
- end
-
- def process_cgi(cgi, session_options = {}) #:nodoc:
- process(CgiRequest.new(cgi, session_options), CgiResponse.new(cgi)).out
- end
- end
-
- class CgiRequest < AbstractRequest #:nodoc:
- attr_accessor :cgi, :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,
- :session_http_only=> true
- }
-
- def initialize(cgi, session_options = {})
- @cgi = cgi
- @session_options = session_options
- @env = @cgi.__send__(:env_table)
- super()
- end
-
- def query_string
- qs = @cgi.query_string if @cgi.respond_to?(:query_string)
- if !qs.blank?
- qs
- else
- super
- end
- end
-
- def body_stream #:nodoc:
- @cgi.stdinput
- end
-
- def cookies
- @cgi.cookies.freeze
- 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
+ class CGIHandler
+ module ProperStream
+ def each
+ while line = gets
+ yield line
end
end
- @session
- end
- def reset_session
- @session.delete if defined?(@session) && @session.is_a?(CGI::Session)
- @session = new_session
- end
-
- def method_missing(method_id, *arguments)
- @cgi.__send__(method_id, *arguments) rescue super
- end
-
- private
- # Delete an old session if it exists then create a new one.
- def new_session
- if @session_options == false
- Hash.new
+ def read(*args)
+ if args.empty?
+ super || ""
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))
+ super
end
end
+ end
- def cookie_only?
- session_options_with_string_keys['cookie_only']
- end
+ def self.dispatch_cgi(app, cgi, out = $stdout)
+ env = cgi.__send__(:env_table)
+ env.delete "HTTP_CONTENT_LENGTH"
- 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
+ cgi.stdinput.extend ProperStream
- retry
- else
- raise
- end
- end
+ env["SCRIPT_NAME"] = "" if env["SCRIPT_NAME"] == "/"
- def session_options_with_string_keys
- @session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
- end
- end
+ env.update({
+ "rack.version" => [0,1],
+ "rack.input" => cgi.stdinput,
+ "rack.errors" => $stderr,
+ "rack.multithread" => false,
+ "rack.multiprocess" => true,
+ "rack.run_once" => false,
+ "rack.url_scheme" => ["yes", "on", "1"].include?(env["HTTPS"]) ? "https" : "http"
+ })
- class CgiResponse < AbstractResponse #:nodoc:
- def initialize(cgi)
- @cgi = cgi
- super()
- end
-
- def out(output = $stdout)
- output.binmode if output.respond_to?(:binmode)
- output.sync = false if output.respond_to?(:sync=)
+ env["QUERY_STRING"] ||= ""
+ env["HTTP_VERSION"] ||= env["SERVER_PROTOCOL"]
+ env["REQUEST_PATH"] ||= "/"
+ env.delete "PATH_INFO" if env["PATH_INFO"] == ""
+ status, headers, body = app.call(env)
begin
- output.write(@cgi.header(@headers))
-
- if @cgi.__send__(:env_table)['REQUEST_METHOD'] == 'HEAD'
- return
- elsif @body.respond_to?(:call)
- # Flush the output now in case the @body Proc uses
- # #syswrite.
- output.flush if output.respond_to?(:flush)
- @body.call(self, output)
- else
- output.write(@body)
- end
-
- output.flush if output.respond_to?(:flush)
- rescue Errno::EPIPE, Errno::ECONNRESET
- # lost connection to parent process, ignore output
+ out.binmode if out.respond_to?(:binmode)
+ out.sync = false if out.respond_to?(:sync=)
+
+ headers['Status'] = status.to_s
+ out.write(cgi.header(headers))
+
+ body.each { |part|
+ out.write part
+ out.flush if out.respond_to?(:flush)
+ }
+ ensure
+ body.close if body.respond_to?(:close)
end
end
end
+
+ class CgiRequest #:nodoc:
+ DEFAULT_SESSION_OPTIONS = {
+ :database_manager => CGI::Session::CookieStore,
+ :prefix => "ruby_sess.",
+ :session_path => "/",
+ :session_key => "_session_id",
+ :cookie_only => true,
+ :session_http_only => true
+ }
+ end
end
diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb
index 47199af2b4..203f6b1683 100644
--- a/actionpack/lib/action_controller/dispatcher.rb
+++ b/actionpack/lib/action_controller/dispatcher.rb
@@ -24,8 +24,7 @@ module ActionController
end
end
- # Backward-compatible class method takes CGI-specific args. Deprecated
- # in favor of Dispatcher.new(output, request, response).dispatch.
+ # DEPRECATE: Remove CGI support
def dispatch(cgi = nil, session_options = CgiRequest::DEFAULT_SESSION_OPTIONS, output = $stdout)
new(output).dispatch_cgi(cgi, session_options)
end
@@ -43,60 +42,19 @@ module ActionController
callback = ActiveSupport::Callbacks::Callback.new(:prepare_dispatch, block, :identifier => identifier)
@prepare_dispatch_callbacks.replace_or_append!(callback)
end
-
- # If the block raises, send status code as a last-ditch response.
- def failsafe_response(fallback_output, status, originating_exception = nil)
- yield
- rescue Exception => exception
- begin
- log_failsafe_exception(status, originating_exception || exception)
- body = failsafe_response_body(status)
- fallback_output.write "Status: #{status}\r\nContent-Type: text/html\r\n\r\n#{body}"
- nil
- rescue Exception => failsafe_error # Logger or IO errors
- $stderr.puts "Error during failsafe response: #{failsafe_error}"
- $stderr.puts "(originally #{originating_exception})" if originating_exception
- end
- end
-
- private
- def failsafe_response_body(status)
- error_path = "#{error_file_path}/#{status.to_s[0..2]}.html"
-
- if File.exist?(error_path)
- File.read(error_path)
- else
- "<html><body><h1>#{status}</h1></body></html>"
- end
- end
-
- def log_failsafe_exception(status, exception)
- message = "/!\\ FAILSAFE /!\\ #{Time.now}\n Status: #{status}\n"
- message << " #{exception}\n #{exception.backtrace.join("\n ")}" if exception
- failsafe_logger.fatal message
- end
-
- def failsafe_logger
- if defined?(::RAILS_DEFAULT_LOGGER) && !::RAILS_DEFAULT_LOGGER.nil?
- ::RAILS_DEFAULT_LOGGER
- else
- Logger.new($stderr)
- end
- end
end
cattr_accessor :middleware
self.middleware = MiddlewareStack.new
-
- cattr_accessor :error_file_path
- self.error_file_path = Rails.public_path if defined?(Rails.public_path)
+ self.middleware.use "ActionController::Failsafe"
include ActiveSupport::Callbacks
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
+ # DEPRECATE: Remove arguments
def initialize(output = $stdout, request = nil, response = nil)
@output, @request, @response = output, request, response
- @app = @@middleware.build(lambda { |env| self._call(env) })
+ @app = @@middleware.build(lambda { |env| self.dup._call(env) })
end
def dispatch_unlocked
@@ -120,14 +78,9 @@ module ActionController
end
end
+ # DEPRECATE: Remove CGI support
def dispatch_cgi(cgi, session_options)
- if cgi ||= self.class.failsafe_response(@output, '400 Bad Request') { CGI.new }
- @request = CgiRequest.new(cgi, session_options)
- @response = CgiResponse.new(cgi)
- dispatch
- end
- rescue Exception => exception
- failsafe_rescue exception
+ CGIHandler.dispatch_cgi(self, cgi, @output)
end
def call(env)
@@ -160,34 +113,24 @@ module ActionController
Base.logger.flush
end
- def mark_as_test_request!
- @test_request = true
- self
- end
-
- def test_request?
- @test_request
- end
-
def checkin_connections
# Don't return connection (and peform implicit rollback) if this request is a part of integration test
- return if test_request?
+ # TODO: This callback should have direct access to env
+ return if @request.key?("action_controller.test")
ActiveRecord::Base.clear_active_connections!
end
protected
def handle_request
@controller = Routing::Routes.recognize(@request)
- @controller.process(@request, @response).out(@output)
+ @controller.process(@request, @response).out
end
def failsafe_rescue(exception)
- self.class.failsafe_response(@output, '500 Internal Server Error', exception) do
- if @controller ||= (::ApplicationController rescue Base)
- @controller.process_with_exception(@request, @response, exception).out(@output)
- else
- raise exception
- end
+ if @controller ||= (::ApplicationController rescue Base)
+ @controller.process_with_exception(@request, @response, exception).out
+ else
+ raise exception
end
end
end
diff --git a/actionpack/lib/action_controller/failsafe.rb b/actionpack/lib/action_controller/failsafe.rb
new file mode 100644
index 0000000000..bb6ef39470
--- /dev/null
+++ b/actionpack/lib/action_controller/failsafe.rb
@@ -0,0 +1,52 @@
+module ActionController
+ class Failsafe
+ cattr_accessor :error_file_path
+ self.error_file_path = Rails.public_path if defined?(Rails.public_path)
+
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ @app.call(env)
+ rescue Exception => exception
+ # Reraise exception in test environment
+ if env["action_controller.test"]
+ raise exception
+ else
+ failsafe_response(exception)
+ end
+ end
+
+ private
+ def failsafe_response(exception)
+ log_failsafe_exception(exception)
+ [500, {'Content-Type' => 'text/html'}, failsafe_response_body]
+ rescue Exception => failsafe_error # Logger or IO errors
+ $stderr.puts "Error during failsafe response: #{failsafe_error}"
+ end
+
+ def failsafe_response_body
+ error_path = "#{self.class.error_file_path}/500.html"
+ if File.exist?(error_path)
+ File.read(error_path)
+ else
+ "<html><body><h1>500 Internal Server Error</h1></body></html>"
+ end
+ end
+
+ def log_failsafe_exception(exception)
+ message = "/!\\ FAILSAFE /!\\ #{Time.now}\n Status: 500 Internal Server Error\n"
+ message << " #{exception}\n #{exception.backtrace.join("\n ")}" if exception
+ failsafe_logger.fatal(message)
+ end
+
+ def failsafe_logger
+ if defined?(::RAILS_DEFAULT_LOGGER) && !::RAILS_DEFAULT_LOGGER.nil?
+ ::RAILS_DEFAULT_LOGGER
+ else
+ Logger.new($stderr)
+ end
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/integration.rb b/actionpack/lib/action_controller/integration.rb
index 65e3eed678..eeabe5b845 100644
--- a/actionpack/lib/action_controller/integration.rb
+++ b/actionpack/lib/action_controller/integration.rb
@@ -9,13 +9,17 @@ module ActionController
# multiple sessions and run them side-by-side, you can also mimic (to some
# limited extent) multiple simultaneous users interacting with your system.
#
- # Typically, you will instantiate a new session using IntegrationTest#open_session,
- # rather than instantiating Integration::Session directly.
+ # Typically, you will instantiate a new session using
+ # IntegrationTest#open_session, rather than instantiating
+ # Integration::Session directly.
class Session
include Test::Unit::Assertions
include ActionController::TestCase::Assertions
include ActionController::TestProcess
+ # Rack application to use
+ attr_accessor :application
+
# The integer HTTP status code of the last request.
attr_reader :status
@@ -57,7 +61,8 @@ module ActionController
end
# Create and initialize a new Session instance.
- def initialize
+ def initialize(app)
+ @application = app
reset!
end
@@ -76,11 +81,13 @@ module ActionController
self.host = "www.example.com"
self.remote_addr = "127.0.0.1"
- self.accept = "text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"
+ self.accept = "text/xml,application/xml,application/xhtml+xml," +
+ "text/html;q=0.9,text/plain;q=0.8,image/png," +
+ "*/*;q=0.5"
unless defined? @named_routes_configured
# install the named routes in this session instance.
- klass = class<<self; self; end
+ klass = class << self; self; end
Routing::Routes.install_helpers(klass)
# the helpers are made protected by default--we make them public for
@@ -94,7 +101,7 @@ module ActionController
#
# session.https!
# session.https!(false)
- def https!(flag=true)
+ def https!(flag = true)
@https = flag
end
@@ -164,17 +171,21 @@ module ActionController
# Performs a GET request with the given parameters.
#
- # - +path+: The URI (as a String) on which you want to perform a GET request.
- # - +parameters+: The HTTP parameters that you want to pass. This may be +nil+,
+ # - +path+: The URI (as a String) on which you want to perform a GET
+ # request.
+ # - +parameters+: The HTTP parameters that you want to pass. This may
+ # be +nil+,
# a Hash, or a String that is appropriately encoded
- # (<tt>application/x-www-form-urlencoded</tt> or <tt>multipart/form-data</tt>).
+ # (<tt>application/x-www-form-urlencoded</tt> or
+ # <tt>multipart/form-data</tt>).
# - +headers+: Additional HTTP headers to pass, as a Hash. The keys will
# automatically be upcased, with the prefix 'HTTP_' added if needed.
#
- # This method returns an AbstractResponse object, which one can use to inspect
- # the details of the response. Furthermore, if this method was called from an
- # ActionController::IntegrationTest object, then that object's <tt>@response</tt>
- # instance variable will point to the same response object.
+ # This method returns an AbstractResponse object, which one can use to
+ # inspect the details of the response. Furthermore, if this method was
+ # called from an ActionController::IntegrationTest object, then that
+ # object's <tt>@response</tt> instance variable will point to the same
+ # response object.
#
# You can also perform POST, PUT, DELETE, and HEAD requests with +post+,
# +put+, +delete+, and +head+.
@@ -182,22 +193,26 @@ module ActionController
process :get, path, parameters, headers
end
- # Performs a POST request with the given parameters. See get() for more details.
+ # Performs a POST request with the given parameters. See get() for more
+ # details.
def post(path, parameters = nil, headers = nil)
process :post, path, parameters, headers
end
- # Performs a PUT request with the given parameters. See get() for more details.
+ # Performs a PUT request with the given parameters. See get() for more
+ # details.
def put(path, parameters = nil, headers = nil)
process :put, path, parameters, headers
end
- # Performs a DELETE request with the given parameters. See get() for more details.
+ # Performs a DELETE request with the given parameters. See get() for
+ # more details.
def delete(path, parameters = nil, headers = nil)
process :delete, path, parameters, headers
end
- # Performs a HEAD request with the given parameters. See get() for more details.
+ # Performs a HEAD request with the given parameters. See get() for more
+ # details.
def head(path, parameters = nil, headers = nil)
process :head, path, parameters, headers
end
@@ -212,7 +227,8 @@ module ActionController
def xml_http_request(request_method, path, parameters = nil, headers = nil)
headers ||= {}
headers['X-Requested-With'] = 'XMLHttpRequest'
- headers['Accept'] ||= 'text/javascript, text/html, application/xml, text/xml, */*'
+ headers['Accept'] ||= 'text/javascript, text/html, application/xml, ' +
+ 'text/xml, */*'
process(request_method, path, parameters, headers)
end
@@ -221,7 +237,9 @@ module ActionController
# Returns the URL for the given options, according to the rules specified
# in the application's routes.
def url_for(options)
- controller ? controller.url_for(options) : generic_url_rewriter.rewrite(options)
+ controller ?
+ controller.url_for(options) :
+ generic_url_rewriter.rewrite(options)
end
private
@@ -247,17 +265,34 @@ module ActionController
data = nil
end
+ env["QUERY_STRING"] ||= ""
+
+ data = data.is_a?(IO) ? data : StringIO.new(data || '')
+
env.update(
- "REQUEST_METHOD" => method.to_s.upcase,
+ "REQUEST_METHOD" => method.to_s.upcase,
+ "SERVER_NAME" => host,
+ "SERVER_PORT" => (https? ? "443" : "80"),
+ "HTTPS" => https? ? "on" : "off",
+ "rack.url_scheme" => https? ? "https" : "http",
+ "SCRIPT_NAME" => "",
+
"REQUEST_URI" => path,
"HTTP_HOST" => host,
"REMOTE_ADDR" => remote_addr,
- "SERVER_PORT" => (https? ? "443" : "80"),
"CONTENT_TYPE" => "application/x-www-form-urlencoded",
"CONTENT_LENGTH" => data ? data.length.to_s : nil,
"HTTP_COOKIE" => encode_cookies,
- "HTTPS" => https? ? "on" : "off",
- "HTTP_ACCEPT" => accept
+ "HTTP_ACCEPT" => accept,
+
+ "rack.version" => [0,1],
+ "rack.input" => data,
+ "rack.errors" => StringIO.new,
+ "rack.multithread" => true,
+ "rack.multiprocess" => true,
+ "rack.run_once" => false,
+
+ "action_controller.test" => true
)
(headers || {}).each do |key, value|
@@ -272,48 +307,43 @@ module ActionController
ActionController::Base.clear_last_instantiation!
- env['rack.input'] = data.is_a?(IO) ? data : StringIO.new(data || '')
- @status, @headers, result_body = ActionController::Dispatcher.new.mark_as_test_request!.call(env)
+ app = Rack::Lint.new(@application)
+
+ status, headers, body = app.call(env)
@request_count += 1
- @controller = ActionController::Base.last_instantiation
- @request = @controller.request
- @response = @controller.response
+ if @controller = ActionController::Base.last_instantiation
+ @request = @controller.request
+ @response = @controller.response
- # Decorate the response with the standard behavior of the TestResponse
- # so that things like assert_response can be used in integration
- # tests.
- @response.extend(TestResponseBehavior)
+ # Decorate the response with the standard behavior of the
+ # TestResponse so that things like assert_response can be
+ # used in integration tests.
+ @response.extend(TestResponseBehavior)
+ end
@html_document = nil
- # Inject status back in for backwords compatibility with CGI
- @headers['Status'] = @status
-
- @status, @status_message = @status.split(/ /)
- @status = @status.to_i
+ @status = status.to_i
+ @status_message = StatusCodes::STATUS_CODES[@status]
- cgi_headers = Hash.new { |h,k| h[k] = [] }
- @headers.each do |key, value|
- cgi_headers[key.downcase] << value
- end
- cgi_headers['set-cookie'] = cgi_headers['set-cookie'].first
- @headers = cgi_headers
+ @headers = Rack::Utils::HeaderHash.new(headers)
- @response.headers['cookie'] ||= []
- (@headers['set-cookie'] || []).each do |cookie|
+ (@headers['Set-Cookie'] || []).each do |cookie|
name, value = cookie.match(/^([^=]*)=([^;]*);/)[1,2]
@cookies[name] = value
-
- # Fake CGI cookie header
- # DEPRECATE: Use response.headers["Set-Cookie"] instead
- @response.headers['cookie'] << CGI::Cookie::new("name" => name, "value" => value)
end
- return status
+ @body = ""
+ body.each { |part| @body << part }
+
+ return @status
rescue MultiPartNeededException
boundary = "----------XnJLe9ZIbbGUYtzPQJ16u1"
- status = process(method, path, multipart_body(parameters, boundary), (headers || {}).merge({"CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}"}))
+ status = process(method, path,
+ multipart_body(parameters, boundary),
+ (headers || {}).merge(
+ {"CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}"}))
return status
end
@@ -335,7 +365,7 @@ module ActionController
"SERVER_PORT" => https? ? "443" : "80",
"HTTPS" => https? ? "on" : "off"
}
- ActionController::UrlRewriter.new(ActionController::RackRequest.new(env), {})
+ UrlRewriter.new(RackRequest.new(env), {})
end
def name_with_prefix(prefix, name)
@@ -349,9 +379,13 @@ module ActionController
raise MultiPartNeededException
elsif Hash === parameters
return nil if parameters.empty?
- parameters.map { |k,v| requestify(v, name_with_prefix(prefix, k)) }.join("&")
+ parameters.map { |k,v|
+ requestify(v, name_with_prefix(prefix, k))
+ }.join("&")
elsif Array === parameters
- parameters.map { |v| requestify(v, name_with_prefix(prefix, "")) }.join("&")
+ parameters.map { |v|
+ requestify(v, name_with_prefix(prefix, ""))
+ }.join("&")
elsif prefix.nil?
parameters
else
@@ -458,7 +492,8 @@ EOF
# can use this method to open multiple sessions that ought to be tested
# simultaneously.
def open_session
- session = Integration::Session.new
+ application = ActionController::Dispatcher.new
+ session = Integration::Session.new(application)
# delegate the fixture accessors back to the test instance
extras = Module.new { attr_accessor :delegate, :test_result }
@@ -466,12 +501,16 @@ EOF
self.class.fixture_table_names.each do |table_name|
name = table_name.tr(".", "_")
next unless respond_to?(name)
- extras.__send__(:define_method, name) { |*args| delegate.send(name, *args) }
+ extras.__send__(:define_method, name) { |*args|
+ delegate.send(name, *args)
+ }
end
end
# delegate add_assertion to the test case
- extras.__send__(:define_method, :add_assertion) { test_result.add_assertion }
+ extras.__send__(:define_method, :add_assertion) {
+ test_result.add_assertion
+ }
session.extend(extras)
session.delegate = self
session.test_result = @_result
@@ -599,7 +638,8 @@ EOF
# would potentially have to set their values for both Test::Unit::TestCase
# ActionController::IntegrationTest, since by the time the value is set on
# TestCase, IntegrationTest has already been defined and cannot inherit
- # changes to those variables. So, we make those two attributes copy-on-write.
+ # changes to those variables. So, we make those two attributes
+ # copy-on-write.
class << self
def use_transactional_fixtures=(flag) #:nodoc:
diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb
index 58d7b53e31..568f893c6c 100644
--- a/actionpack/lib/action_controller/rack_process.rb
+++ b/actionpack/lib/action_controller/rack_process.rb
@@ -55,14 +55,7 @@ module ActionController #:nodoc:
end
def cookies
- return {} unless @env["HTTP_COOKIE"]
-
- unless @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
- @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
- @env["rack.request.cookie_hash"] = CGI::Cookie::parse(@env["rack.request.cookie_string"])
- end
-
- @env["rack.request.cookie_hash"]
+ Rack::Request.new(@env).cookies
end
def server_port
@@ -164,7 +157,7 @@ end_msg
@status || super
end
- def out(output = $stdout, &block)
+ def out(&block)
# Nasty hack because CGI sessions are closed after the normal
# prepare! statement
set_cookies!
diff --git a/actionpack/lib/action_controller/rescue.rb b/actionpack/lib/action_controller/rescue.rb
index 9c24c3def4..d7b0e96c93 100644
--- a/actionpack/lib/action_controller/rescue.rb
+++ b/actionpack/lib/action_controller/rescue.rb
@@ -1,13 +1,19 @@
module ActionController #:nodoc:
- # Actions that fail to perform as expected throw exceptions. These exceptions can either be rescued for the public view
- # (with a nice user-friendly explanation) or for the developers view (with tons of debugging information). The developers view
- # is already implemented by the Action Controller, but the public view should be tailored to your specific application.
- #
- # The default behavior for public exceptions is to render a static html file with the name of the error code thrown. If no such
- # file exists, an empty response is sent with the correct status code.
+ # Actions that fail to perform as expected throw exceptions. These
+ # exceptions can either be rescued for the public view (with a nice
+ # user-friendly explanation) or for the developers view (with tons of
+ # debugging information). The developers view is already implemented by
+ # the Action Controller, but the public view should be tailored to your
+ # specific application.
#
- # You can override what constitutes a local request by overriding the <tt>local_request?</tt> method in your own controller.
- # Custom rescue behavior is achieved by overriding the <tt>rescue_action_in_public</tt> and <tt>rescue_action_locally</tt> methods.
+ # The default behavior for public exceptions is to render a static html
+ # file with the name of the error code thrown. If no such file exists, an
+ # empty response is sent with the correct status code.
+ #
+ # You can override what constitutes a local request by overriding the
+ # <tt>local_request?</tt> method in your own controller. Custom rescue
+ # behavior is achieved by overriding the <tt>rescue_action_in_public</tt>
+ # and <tt>rescue_action_locally</tt> methods.
module Rescue
LOCALHOST = '127.0.0.1'.freeze
@@ -32,6 +38,9 @@ module ActionController #:nodoc:
'ActionView::TemplateError' => 'template_error'
}
+ RESCUES_TEMPLATE_PATH = ActionView::PathSet::Path.new(
+ "#{File.dirname(__FILE__)}/templates", true)
+
def self.included(base) #:nodoc:
base.cattr_accessor :rescue_responses
base.rescue_responses = Hash.new(DEFAULT_RESCUE_RESPONSE)
@@ -56,12 +65,15 @@ module ActionController #:nodoc:
end
protected
- # Exception handler called when the performance of an action raises an exception.
+ # Exception handler called when the performance of an action raises
+ # an exception.
def rescue_action(exception)
- rescue_with_handler(exception) || rescue_action_without_handler(exception)
+ rescue_with_handler(exception) ||
+ rescue_action_without_handler(exception)
end
- # Overwrite to implement custom logging of errors. By default logs as fatal.
+ # Overwrite to implement custom logging of errors. By default
+ # logs as fatal.
def log_error(exception) #:doc:
ActiveSupport::Deprecation.silence do
if ActionView::TemplateError === exception
@@ -75,16 +87,19 @@ module ActionController #:nodoc:
end
end
- # Overwrite to implement public exception handling (for requests answering false to <tt>local_request?</tt>). By
- # default will call render_optional_error_file. Override this method to provide more user friendly error messages.
+ # Overwrite to implement public exception handling (for requests
+ # answering false to <tt>local_request?</tt>). By default will call
+ # render_optional_error_file. Override this method to provide more
+ # user friendly error messages.
def rescue_action_in_public(exception) #:doc:
render_optional_error_file response_code_for_rescue(exception)
end
-
- # Attempts to render a static error page based on the <tt>status_code</tt> thrown,
- # or just return headers if no such file exists. For example, if a 500 error is
- # being handled Rails will first attempt to render the file at <tt>public/500.html</tt>.
- # If the file doesn't exist, the body of the response will be left empty.
+
+ # Attempts to render a static error page based on the
+ # <tt>status_code</tt> thrown, or just return headers if no such file
+ # exists. For example, if a 500 error is being handled Rails will first
+ # attempt to render the file at <tt>public/500.html</tt>. If the file
+ # doesn't exist, the body of the response will be left empty.
def render_optional_error_file(status_code)
status = interpret_status(status_code)
path = "#{Rails.public_path}/#{status[0,3]}.html"
@@ -106,11 +121,13 @@ module ActionController #:nodoc:
# a controller action.
def rescue_action_locally(exception)
@template.instance_variable_set("@exception", exception)
- @template.instance_variable_set("@rescues_path", File.dirname(rescues_path("stub")))
- @template.instance_variable_set("@contents", @template.render(:file => template_path_for_local_rescue(exception)))
+ @template.instance_variable_set("@rescues_path", RESCUES_TEMPLATE_PATH)
+ @template.instance_variable_set("@contents",
+ @template.render(:file => template_path_for_local_rescue(exception)))
response.content_type = Mime::HTML
- render_for_file(rescues_path("layout"), response_code_for_rescue(exception))
+ render_for_file(rescues_path("layout"),
+ response_code_for_rescue(exception))
end
def rescue_action_without_handler(exception)
@@ -138,7 +155,7 @@ module ActionController #:nodoc:
end
def rescues_path(template_name)
- "#{File.dirname(__FILE__)}/templates/rescues/#{template_name}.erb"
+ RESCUES_TEMPLATE_PATH["rescues/#{template_name}.erb"]
end
def template_path_for_local_rescue(exception)
@@ -150,8 +167,9 @@ module ActionController #:nodoc:
end
def clean_backtrace(exception)
- defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
- Rails.backtrace_cleaner.clean(exception.backtrace) : exception.backtrace
+ defined?(Rails) && Rails.respond_to?(:backtrace_cleaner) ?
+ Rails.backtrace_cleaner.clean(exception.backtrace) :
+ exception.backtrace
end
end
end
diff --git a/actionpack/lib/action_controller/resources.rb b/actionpack/lib/action_controller/resources.rb
index b6cfe2dd68..e8988aa737 100644
--- a/actionpack/lib/action_controller/resources.rb
+++ b/actionpack/lib/action_controller/resources.rb
@@ -283,7 +283,12 @@ module ActionController
# * <tt>:new</tt> - Same as <tt>:collection</tt>, but for actions that operate on the new \resource action.
# * <tt>:controller</tt> - Specify the controller name for the routes.
# * <tt>:singular</tt> - Specify the singular name used in the member routes.
- # * <tt>:requirements</tt> - Set custom routing parameter requirements.
+ # * <tt>:requirements</tt> - Set custom routing parameter requirements; this is a hash of either
+ # regular expressions (which must match for the route to match) or extra parameters. For example:
+ #
+ # map.resource :profile, :path_prefix => ':name', :requirements => { :name => /[a-zA-Z]+/, :extra => 'value' }
+ #
+ # will only match if the first part is alphabetic, and will pass the parameter :extra to the controller.
# * <tt>:conditions</tt> - Specify custom routing recognition conditions. \Resources sets the <tt>:method</tt> value for the method-specific routes.
# * <tt>:as</tt> - Specify a different \resource name to use in the URL path. For example:
# # products_path == '/productos'
diff --git a/actionpack/lib/action_controller/routing.rb b/actionpack/lib/action_controller/routing.rb
index efd474097e..da9b56fdf9 100644
--- a/actionpack/lib/action_controller/routing.rb
+++ b/actionpack/lib/action_controller/routing.rb
@@ -83,9 +83,11 @@ module ActionController
# This sets up +blog+ as the default controller if no other is specified.
# This means visiting '/' would invoke the blog controller.
#
- # More formally, you can define defaults in a route with the <tt>:defaults</tt> key.
+ # More formally, you can include arbitrary parameters in the route, thus:
#
- # map.connect ':controller/:action/:id', :action => 'show', :defaults => { :page => 'Dashboard' }
+ # map.connect ':controller/:action/:id', :action => 'show', :page => 'Dashboard'
+ #
+ # This will pass the :page parameter to all incoming requests that match this route.
#
# Note: The default routes, as provided by the Rails generator, make all actions in every
# controller accessible via GET requests. You should consider removing them or commenting
diff --git a/actionpack/lib/action_controller/session/active_record_store.rb b/actionpack/lib/action_controller/session/active_record_store.rb
index 1e8eb57acb..fadf2a6b32 100644
--- a/actionpack/lib/action_controller/session/active_record_store.rb
+++ b/actionpack/lib/action_controller/session/active_record_store.rb
@@ -56,6 +56,8 @@ class CGI
class ActiveRecordStore
# The default Active Record class.
class Session < ActiveRecord::Base
+ ##
+ # :singleton-method:
# Customizable data column name. Defaults to 'data'.
cattr_accessor :data_column_name
self.data_column_name = 'data'
@@ -166,17 +168,25 @@ class CGI
# binary session data in a +text+ column. For higher performance,
# store in a +blob+ column instead and forgo the Base64 encoding.
class SqlBypass
+ ##
+ # :singleton-method:
# Use the ActiveRecord::Base.connection by default.
cattr_accessor :connection
+ ##
+ # :singleton-method:
# The table name defaults to 'sessions'.
cattr_accessor :table_name
@@table_name = 'sessions'
+ ##
+ # :singleton-method:
# The session id field defaults to 'session_id'.
cattr_accessor :session_id_column
@@session_id_column = 'session_id'
+ ##
+ # :singleton-method:
# The data field defaults to 'data'.
cattr_accessor :data_column
@@data_column = 'data'
diff --git a/actionpack/lib/action_controller/templates/rescues/diagnostics.erb b/actionpack/lib/action_controller/templates/rescues/diagnostics.erb
index b5483eccae..669da1b26e 100644
--- a/actionpack/lib/action_controller/templates/rescues/diagnostics.erb
+++ b/actionpack/lib/action_controller/templates/rescues/diagnostics.erb
@@ -6,6 +6,6 @@
</h1>
<pre><%=h @exception.clean_message %></pre>
-<%= render(:file => @rescues_path + "/_trace.erb") %>
+<%= render :file => @rescues_path["rescues/_trace.erb"] %>
-<%= render(:file => @rescues_path + "/_request_and_response.erb") %>
+<%= render :file => @rescues_path["rescues/_request_and_response.erb"] %>
diff --git a/actionpack/lib/action_controller/templates/rescues/template_error.erb b/actionpack/lib/action_controller/templates/rescues/template_error.erb
index 76fa3df89d..2e34e03bd5 100644
--- a/actionpack/lib/action_controller/templates/rescues/template_error.erb
+++ b/actionpack/lib/action_controller/templates/rescues/template_error.erb
@@ -15,7 +15,7 @@
<% @real_exception = @exception
@exception = @exception.original_exception || @exception %>
-<%= render(:file => @rescues_path + "/_trace.erb") %>
+<%= render :file => @rescues_path["rescues/_trace.erb"] %>
<% @exception = @real_exception %>
-<%= render(:file => @rescues_path + "/_request_and_response.erb") %>
+<%= render :file => @rescues_path["rescues/_request_and_response.erb"] %>