diff options
Diffstat (limited to 'actionpack/lib')
43 files changed, 465 insertions, 438 deletions
diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index 887196b3d2..6db0941b52 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -23,7 +23,11 @@ module AbstractController def render(*args, &block) options = _normalize_render(*args, &block) self.response_body = render_to_body(options) - _process_format(rendered_format, options) if rendered_format + if options[:html] + _set_content_type Mime::HTML.to_s + else + _set_content_type _get_content_type(rendered_format) + end self.response_body end @@ -99,7 +103,14 @@ module AbstractController # Process the rendered format. # :api: private - def _process_format(format, options = {}) + def _process_format(format) + end + + def _get_content_type(rendered_format) # :nodoc: + rendered_format.to_s + end + + def _set_content_type(type) # :nodoc: end # Normalize args and options. @@ -107,7 +118,7 @@ module AbstractController def _normalize_render(*args, &block) options = _normalize_args(*args, &block) #TODO: remove defined? when we restore AP <=> AV dependency - if defined?(request) && request && request.variant.present? + if defined?(request) && request.variant.present? options[:variant] = request.variant end _normalize_options(options) diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 89fc4520d3..3d3af555c9 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -30,7 +30,6 @@ module ActionController autoload :Instrumentation autoload :MimeResponds autoload :ParamsWrapper - autoload :RackDelegation autoload :Redirecting autoload :Renderers autoload :Rendering diff --git a/actionpack/lib/action_controller/api.rb b/actionpack/lib/action_controller/api.rb index b4594bf302..1a46d49a49 100644 --- a/actionpack/lib/action_controller/api.rb +++ b/actionpack/lib/action_controller/api.rb @@ -115,7 +115,6 @@ module ActionController Rendering, Renderers::All, ConditionalGet, - RackDelegation, BasicImplicitRender, StrongParameters, diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 55734b9774..6c644862d5 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -213,7 +213,6 @@ module ActionController Renderers::All, ConditionalGet, EtagWithTemplateDigest, - RackDelegation, Caching, MimeResponds, ImplicitRender, @@ -252,7 +251,7 @@ module ActionController # Define some internal variables that should not be propagated to the view. PROTECTED_IVARS = AbstractController::Rendering::DEFAULT_PROTECTED_INSTANCE_VARIABLES + [ - :@_status, :@_headers, :@_params, :@_response, :@_request, + :@_params, :@_response, :@_request, :@_view_runtime, :@_stream, :@_url_options, :@_action_has_layout ] def _protected_ivars # :nodoc: diff --git a/actionpack/lib/action_controller/caching.rb b/actionpack/lib/action_controller/caching.rb index a4e4992cfe..0b8fa2ea09 100644 --- a/actionpack/lib/action_controller/caching.rb +++ b/actionpack/lib/action_controller/caching.rb @@ -1,6 +1,5 @@ require 'fileutils' require 'uri' -require 'set' module ActionController # \Caching is a cheap way of speeding up slow applications by keeping the result of @@ -46,7 +45,6 @@ module ActionController end end - include RackDelegation include AbstractController::Callbacks include ConfigMethods diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 914b0d4b30..030a1f3478 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -1,6 +1,8 @@ require 'active_support/core_ext/array/extract_options' require 'action_dispatch/middleware/stack' require 'active_support/deprecation' +require 'action_dispatch/http/request' +require 'action_dispatch/http/response' module ActionController # Extend ActionDispatch middleware stack to make it aware of options @@ -132,23 +134,30 @@ module ActionController @controller_name ||= name.demodulize.sub(/Controller$/, '').underscore end + def self.make_response!(request) + ActionDispatch::Response.new.tap do |res| + res.request = request + end + end + + def self.build_with_env(env = {}) #:nodoc: + new.tap { |c| + c.set_request! ActionDispatch::Request.new(env) + c.set_response! make_response!(c.request) + } + end + # Delegates to the class' <tt>controller_name</tt> def controller_name self.class.controller_name end - # The details below can be overridden to support a specific - # Request and Response object. The default ActionController::Base - # implementation includes RackDelegation, which makes a request - # and response object available. You might wish to control the - # environment and response manually for performance reasons. - - attr_internal :headers, :response, :request + attr_internal :response, :request delegate :session, :to => "@_request" + delegate :headers, :status=, :location=, :content_type=, + :status, :location, :content_type, :to => "@_response" def initialize - @_headers = {"Content-Type" => "text/html"} - @_status = 200 @_request = nil @_response = nil @_routes = nil @@ -163,63 +172,46 @@ module ActionController @_params = val end - # Basic implementations for content_type=, location=, and headers are - # provided to reduce the dependency on the RackDelegation module - # in Renderer and Redirector. - - def content_type=(type) - headers["Content-Type"] = type.to_s - end - - def content_type - headers["Content-Type"] - end - - def location - headers["Location"] - end - - def location=(url) - headers["Location"] = url - end + alias :response_code :status # :nodoc: # Basic url_for that can be overridden for more robust functionality def url_for(string) string end - def status - @_status - end - alias :response_code :status # :nodoc: - - def status=(status) - @_status = Rack::Utils.status_code(status) - end - def response_body=(body) body = [body] unless body.nil? || body.respond_to?(:each) + response.body = body super end # Tests if render or redirect has already happened. def performed? - response_body || (response && response.committed?) + response_body || response.committed? end - def dispatch(name, request) #:nodoc: + def dispatch(name, request, response) #:nodoc: set_request!(request) + set_response!(response) process(name) to_a end + def set_response!(response) # :nodoc: + @_response = response + end + def set_request!(request) #:nodoc: @_request = request @_request.controller_instance = self end def to_a #:nodoc: - response ? response.to_a : [status, headers, response_body] + response.to_a + end + + def reset_session + @_request.reset_session end class_attribute :middleware_stack @@ -247,15 +239,32 @@ module ActionController req = ActionDispatch::Request.new env action(req.path_parameters[:action]).call(env) end + class << self; deprecate :call; end # Returns a Rack endpoint for the given action name. def self.action(name) if middleware_stack.any? middleware_stack.build(name) do |env| - new.dispatch(name, ActionDispatch::Request.new(env)) + req = ActionDispatch::Request.new(env) + res = make_response! req + new.dispatch(name, req, res) end else - lambda { |env| new.dispatch(name, ActionDispatch::Request.new(env)) } + lambda { |env| + req = ActionDispatch::Request.new(env) + res = make_response! req + new.dispatch(name, req, res) + } + end + end + + # Direct dispatch to the controller. Instantiates the controller, then + # executes the action named +name+. + def self.dispatch(name, req, res) + if middleware_stack.any? + middleware_stack.build(name) { |env| new.dispatch(name, req, res) }.call req.env + else + new.dispatch(name, req, res) end end end diff --git a/actionpack/lib/action_controller/metal/conditional_get.rb b/actionpack/lib/action_controller/metal/conditional_get.rb index bb3ad9b850..89d589c486 100644 --- a/actionpack/lib/action_controller/metal/conditional_get.rb +++ b/actionpack/lib/action_controller/metal/conditional_get.rb @@ -4,7 +4,6 @@ module ActionController module ConditionalGet extend ActiveSupport::Concern - include RackDelegation include Head included do diff --git a/actionpack/lib/action_controller/metal/cookies.rb b/actionpack/lib/action_controller/metal/cookies.rb index d787f014cd..f8efb2b076 100644 --- a/actionpack/lib/action_controller/metal/cookies.rb +++ b/actionpack/lib/action_controller/metal/cookies.rb @@ -2,8 +2,6 @@ module ActionController #:nodoc: module Cookies extend ActiveSupport::Concern - include RackDelegation - included do helper_method :cookies end diff --git a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb index f9303efe6c..669cf55bca 100644 --- a/actionpack/lib/action_controller/metal/etag_with_template_digest.rb +++ b/actionpack/lib/action_controller/metal/etag_with_template_digest.rb @@ -25,7 +25,7 @@ module ActionController class_attribute :etag_with_template_digest self.etag_with_template_digest = true - ActiveSupport.on_load :action_view, yield: true do |action_view_base| + ActiveSupport.on_load :action_view, yield: true do etag do |options| determine_template_etag(options) if etag_with_template_digest end diff --git a/actionpack/lib/action_controller/metal/head.rb b/actionpack/lib/action_controller/metal/head.rb index f445094bdc..b2110bf946 100644 --- a/actionpack/lib/action_controller/metal/head.rb +++ b/actionpack/lib/action_controller/metal/head.rb @@ -28,7 +28,7 @@ module ActionController end status ||= :ok - + location = options.delete(:location) content_type = options.delete(:content_type) @@ -43,12 +43,9 @@ module ActionController if include_content?(self.response_code) self.content_type = content_type || (Mime[formats.first] if formats) - self.response.charset = false if self.response - else - headers.delete('Content-Type') - headers.delete('Content-Length') + self.response.charset = false end - + true end diff --git a/actionpack/lib/action_controller/metal/http_authentication.rb b/actionpack/lib/action_controller/metal/http_authentication.rb index bbb38cf8fc..15d4562abb 100644 --- a/actionpack/lib/action_controller/metal/http_authentication.rb +++ b/actionpack/lib/action_controller/metal/http_authentication.rb @@ -203,7 +203,7 @@ module ActionController password = password_procedure.call(credentials[:username]) return false unless password - method = request.env['rack.methodoverride.original_method'] || request.env['REQUEST_METHOD'] + method = request.get_header('rack.methodoverride.original_method') || request.get_header('REQUEST_METHOD') uri = credentials[:uri] [true, false].any? do |trailing_question_mark| @@ -260,8 +260,8 @@ module ActionController end def secret_token(request) - key_generator = request.env["action_dispatch.key_generator"] - http_auth_salt = request.env["action_dispatch.http_auth_salt"] + key_generator = request.key_generator + http_auth_salt = request.http_auth_salt key_generator.generate_key(http_auth_salt) end diff --git a/actionpack/lib/action_controller/metal/instrumentation.rb b/actionpack/lib/action_controller/metal/instrumentation.rb index a3e1a71b0a..3dbf34eb2a 100644 --- a/actionpack/lib/action_controller/metal/instrumentation.rb +++ b/actionpack/lib/action_controller/metal/instrumentation.rb @@ -11,7 +11,6 @@ module ActionController extend ActiveSupport::Concern include AbstractController::Logger - include ActionController::RackDelegation attr_internal :view_runtime diff --git a/actionpack/lib/action_controller/metal/live.rb b/actionpack/lib/action_controller/metal/live.rb index 58150cd9a9..69583f8ab4 100644 --- a/actionpack/lib/action_controller/metal/live.rb +++ b/actionpack/lib/action_controller/metal/live.rb @@ -33,6 +33,20 @@ module ActionController # the main thread. Make sure your actions are thread safe, and this shouldn't # be a problem (don't share state across threads, etc). module Live + extend ActiveSupport::Concern + + module ClassMethods + def make_response!(request) + if request.env["HTTP_VERSION"] == "HTTP/1.0" + super + else + Live::Response.new.tap do |res| + res.request = request + end + end + end + end + # This class provides the ability to write an SSE (Server Sent Event) # to an IO stream. The class is initialized with a stream and can be used # to either write a JSON string or an object which can be converted to JSON. @@ -311,12 +325,7 @@ module ActionController end def set_response!(request) - if request.env["HTTP_VERSION"] == "HTTP/1.0" - super - else - @_response = Live::Response.new - @_response.request = request - end + @_response = self.class.make_response! request end end end diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 1db68db20f..e62da0fa70 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -191,6 +191,7 @@ module ActionController #:nodoc: if format = collector.negotiate_format(request) _process_format(format) + _set_content_type _get_content_type format response = collector.response response ? response.call : render({}) else diff --git a/actionpack/lib/action_controller/metal/rack_delegation.rb b/actionpack/lib/action_controller/metal/rack_delegation.rb deleted file mode 100644 index ae9d89cc8c..0000000000 --- a/actionpack/lib/action_controller/metal/rack_delegation.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'action_dispatch/http/request' -require 'action_dispatch/http/response' - -module ActionController - module RackDelegation - extend ActiveSupport::Concern - - delegate :headers, :status=, :location=, :content_type=, - :status, :location, :content_type, :response_code, :to => "@_response" - - module ClassMethods - def build_with_env(env = {}) #:nodoc: - new.tap { |c| c.set_request! ActionDispatch::Request.new(env) } - end - end - - def set_request!(request) #:nodoc: - super - set_response!(request) - end - - def response_body=(body) - response.body = body if response - super - end - - def reset_session - @_request.reset_session - end - - private - - def set_response!(request) - @_response = ActionDispatch::Response.new - @_response.request = request - end - end -end diff --git a/actionpack/lib/action_controller/metal/redirecting.rb b/actionpack/lib/action_controller/metal/redirecting.rb index acaa8227c9..0febc905f1 100644 --- a/actionpack/lib/action_controller/metal/redirecting.rb +++ b/actionpack/lib/action_controller/metal/redirecting.rb @@ -11,7 +11,6 @@ module ActionController extend ActiveSupport::Concern include AbstractController::Logger - include ActionController::RackDelegation include ActionController::UrlFor # Redirects the browser to the target specified in +options+. This parameter can be any one of: diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index a3b0645dc0..c8934b367f 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -56,14 +56,12 @@ module ActionController nil end - def _process_format(format, options = {}) - super + def _get_content_type(rendered_format) + self.content_type || super + end - if options[:plain] - self.content_type = Mime::TEXT - else - self.content_type ||= format.to_s - end + def _set_content_type(format) + self.content_type = format end # Normalize arguments by catching blocks and setting them on :update. diff --git a/actionpack/lib/action_controller/metal/request_forgery_protection.rb b/actionpack/lib/action_controller/metal/request_forgery_protection.rb index d21a778d8d..e5f3cb8e8d 100644 --- a/actionpack/lib/action_controller/metal/request_forgery_protection.rb +++ b/actionpack/lib/action_controller/metal/request_forgery_protection.rb @@ -136,7 +136,7 @@ module ActionController #:nodoc: # This is the method that defines the application behavior when a request is found to be unverified. def handle_unverified_request request = @controller.request - request.session = NullSessionHash.new(request.env) + request.session = NullSessionHash.new(request) request.env['action_dispatch.request.flash_hash'] = nil request.env['rack.session.options'] = { skip: true } request.cookie_jar = NullCookieJar.build(request, {}) @@ -145,8 +145,8 @@ module ActionController #:nodoc: protected class NullSessionHash < Rack::Session::Abstract::SessionHash #:nodoc: - def initialize(env) - super(nil, env) + def initialize(req) + super(nil, req) @data = {} @loaded = true end diff --git a/actionpack/lib/action_controller/metal/strong_parameters.rb b/actionpack/lib/action_controller/metal/strong_parameters.rb index fc8e345d43..bf5c7003ff 100644 --- a/actionpack/lib/action_controller/metal/strong_parameters.rb +++ b/actionpack/lib/action_controller/metal/strong_parameters.rb @@ -240,19 +240,58 @@ module ActionController self end - # Ensures that a parameter is present. If it's present, returns - # the parameter at the given +key+, otherwise raises an - # <tt>ActionController::ParameterMissing</tt> error. + # This method accepts both a single key and an array of keys. + # + # When passed a single key, if it exists and its associated value is + # either present or the singleton +false+, returns said value: # # ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person) # # => {"name"=>"Francesco"} # + # Otherwise raises <tt>ActionController::ParameterMissing</tt>: + # + # ActionController::Parameters.new.require(:person) + # # ActionController::ParameterMissing: param is missing or the value is empty: person + # # ActionController::Parameters.new(person: nil).require(:person) - # # => ActionController::ParameterMissing: param is missing or the value is empty: person + # # ActionController::ParameterMissing: param is missing or the value is empty: person + # + # ActionController::Parameters.new(person: "\t").require(:person) + # # ActionController::ParameterMissing: param is missing or the value is empty: person # # ActionController::Parameters.new(person: {}).require(:person) - # # => ActionController::ParameterMissing: param is missing or the value is empty: person + # # ActionController::ParameterMissing: param is missing or the value is empty: person + # + # When given an array of keys, the method tries to require each one of them + # in order. If it succeeds, an array with the respective return values is + # returned: + # + # params = ActionController::Parameters.new(user: { ... }, profile: { ... }) + # user_params, profile_params = params.require(:user, :profile) + # + # Otherwise, the method reraises the first exception found: + # + # params = ActionController::Parameters.new(user: {}, profile: {}) + # user_params, profile_params = params.require(:user, :profile) + # # ActionController::ParameterMissing: param is missing or the value is empty: user + # + # Technically this method can be used to fetch terminal values: + # + # # CAREFUL + # params = ActionController::Parameters.new(person: { name: 'Finn' }) + # name = params.require(:person).require(:name) # CAREFUL + # + # but take into account that at some point those ones have to be permitted: + # + # def person_params + # params.require(:person).permit(:name).tap do |person_params| + # person_params.require(:name) # SAFER + # end + # end + # + # for example. def require(key) + return key.map { |k| require(k) } if key.is_a?(Array) value = self[key] if value.present? || value == false value diff --git a/actionpack/lib/action_controller/metal/testing.rb b/actionpack/lib/action_controller/metal/testing.rb index d01927b7cb..47d940f692 100644 --- a/actionpack/lib/action_controller/metal/testing.rb +++ b/actionpack/lib/action_controller/metal/testing.rb @@ -2,8 +2,6 @@ module ActionController module Testing extend ActiveSupport::Concern - include RackDelegation - # TODO : Rewrite tests using controller.headers= to use Rack env def headers=(new_headers) @_response ||= ActionDispatch::Response.new diff --git a/actionpack/lib/action_controller/test_case.rb b/actionpack/lib/action_controller/test_case.rb index e012fa617e..39cbc0cd70 100644 --- a/actionpack/lib/action_controller/test_case.rb +++ b/actionpack/lib/action_controller/test_case.rb @@ -36,11 +36,11 @@ module ActionController end def query_string=(string) - @env[Rack::QUERY_STRING] = string + set_header Rack::QUERY_STRING, string end - def request_parameters=(params) - @env["action_dispatch.request.request_parameters"] = params + def content_type=(type) + set_header 'CONTENT_TYPE', type end def assign_parameters(routes, controller_path, action, parameters, generated_path, query_string_keys) @@ -67,10 +67,12 @@ module ActionController end else if ENCODER.should_multipart?(non_path_parameters) - @env['CONTENT_TYPE'] = ENCODER.content_type + self.content_type = ENCODER.content_type data = ENCODER.build_multipart non_path_parameters else - @env['CONTENT_TYPE'] ||= 'application/x-www-form-urlencoded' + get_header('CONTENT_TYPE') do |k| + set_header k, 'application/x-www-form-urlencoded' + end # FIXME: setting `request_parametes` is normally handled by the # params parser middleware, and we should remove this roundtripping @@ -92,11 +94,13 @@ module ActionController end end - @env['CONTENT_LENGTH'] = data.length.to_s - @env['rack.input'] = StringIO.new(data) + set_header 'CONTENT_LENGTH', data.length.to_s + set_header 'rack.input', StringIO.new(data) end - @env["PATH_INFO"] ||= generated_path + get_header("PATH_INFO") do |k| + set_header k, generated_path + end path_parameters[:controller] = controller_path path_parameters[:action] = action @@ -170,8 +174,8 @@ module ActionController clear end - def fetch(*args, &block) - @data.fetch(*args, &block) + def fetch(key, *args, &block) + @data.fetch(key.to_s, *args, &block) end private @@ -450,7 +454,7 @@ module ActionController end if body.present? - @request.env['RAW_POST_DATA'] = body + @request.set_header 'RAW_POST_DATA', body end if http_method.present? @@ -472,15 +476,15 @@ module ActionController end self.cookies.update @request.cookies - @request.env['HTTP_COOKIE'] = cookies.to_header - @request.env['action_dispatch.cookies'] = nil + @request.set_header 'HTTP_COOKIE', cookies.to_header + @request.delete_header 'action_dispatch.cookies' @request = TestRequest.new scrub_env!(@request.env), @request.session @response = build_response @response_klass @response.request = @request @controller.recycle! - @request.env['REQUEST_METHOD'] = http_method + @request.set_header 'REQUEST_METHOD', http_method parameters = parameters.symbolize_keys @@ -494,24 +498,28 @@ module ActionController @request.flash.update(flash || {}) if xhr - @request.env['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' - @request.env['HTTP_ACCEPT'] ||= [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') + @request.set_header 'HTTP_X_REQUESTED_WITH', 'XMLHttpRequest' + @request.get_header('HTTP_ACCEPT') do |k| + @request.set_header k, [Mime::JS, Mime::HTML, Mime::XML, 'text/xml', Mime::ALL].join(', ') + end end @controller.request = @request @controller.response = @response - @request.env["SCRIPT_NAME"] ||= @controller.config.relative_url_root + @request.get_header("SCRIPT_NAME") do |k| + @request.set_header k, @controller.config.relative_url_root + end @controller.recycle! @controller.process(action) - @request.env.delete 'HTTP_COOKIE' + @request.delete_header 'HTTP_COOKIE' - if cookies = @request.env['action_dispatch.cookies'] + if @request.have_cookie_jar? unless @response.committed? - cookies.write(@response) - self.cookies.update(cookies.instance_variable_get(:@cookies)) + @request.cookie_jar.write(@response) + self.cookies.update(@request.cookie_jar.instance_variable_get(:@cookies)) end end @response.prepare! @@ -523,8 +531,8 @@ module ActionController end if xhr - @request.env.delete 'HTTP_X_REQUESTED_WITH' - @request.env.delete 'HTTP_ACCEPT' + @request.delete_header 'HTTP_X_REQUESTED_WITH' + @request.delete_header 'HTTP_ACCEPT' end @request.query_string = '' diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb index cc1cb3f0f0..08ebd2e8b2 100644 --- a/actionpack/lib/action_dispatch/http/cache.rb +++ b/actionpack/lib/action_dispatch/http/cache.rb @@ -8,13 +8,13 @@ module ActionDispatch HTTP_IF_NONE_MATCH = 'HTTP_IF_NONE_MATCH'.freeze def if_modified_since - if since = env[HTTP_IF_MODIFIED_SINCE] + if since = get_header(HTTP_IF_MODIFIED_SINCE) Time.rfc2822(since) rescue nil end end def if_none_match - env[HTTP_IF_NONE_MATCH] + get_header HTTP_IF_NONE_MATCH end def if_none_match_etags @@ -51,41 +51,45 @@ module ActionDispatch end module Response - attr_reader :cache_control, :etag - alias :etag? :etag + attr_reader :cache_control def last_modified - if last = headers[LAST_MODIFIED] + if last = get_header(LAST_MODIFIED) Time.httpdate(last) end end def last_modified? - headers.include?(LAST_MODIFIED) + have_header? LAST_MODIFIED end def last_modified=(utc_time) - headers[LAST_MODIFIED] = utc_time.httpdate + set_header LAST_MODIFIED, utc_time.httpdate end def date - if date_header = headers[DATE] + if date_header = get_header(DATE) Time.httpdate(date_header) end end def date? - headers.include?(DATE) + have_header? DATE end def date=(utc_time) - headers[DATE] = utc_time.httpdate + set_header DATE, utc_time.httpdate end def etag=(etag) key = ActiveSupport::Cache.expand_cache_key(etag) - @etag = self[ETAG] = %("#{Digest::MD5.hexdigest(key)}") + set_header ETAG, %("#{Digest::MD5.hexdigest(key)}") + end + + def etag + get_header ETAG end + alias :etag? :etag private @@ -96,7 +100,7 @@ module ActionDispatch SPECIAL_KEYS = Set.new(%w[extras no-cache max-age public must-revalidate]) def cache_control_segments - if cache_control = self[CACHE_CONTROL] + if cache_control = get_header(CACHE_CONTROL) cache_control.delete(' ').split(',') else [] @@ -123,12 +127,11 @@ module ActionDispatch def prepare_cache_control! @cache_control = cache_control_headers - @etag = self[ETAG] end def handle_conditional_get! if etag? || last_modified? || !@cache_control.empty? - set_conditional_cache_control! + set_conditional_cache_control!(@cache_control) end end @@ -138,24 +141,24 @@ module ActionDispatch PRIVATE = "private".freeze MUST_REVALIDATE = "must-revalidate".freeze - def set_conditional_cache_control! + def set_conditional_cache_control!(cache_control) control = {} cc_headers = cache_control_headers if extras = cc_headers.delete(:extras) - @cache_control[:extras] ||= [] - @cache_control[:extras] += extras - @cache_control[:extras].uniq! + cache_control[:extras] ||= [] + cache_control[:extras] += extras + cache_control[:extras].uniq! end control.merge! cc_headers - control.merge! @cache_control + control.merge! cache_control if control.empty? - self[CACHE_CONTROL] = DEFAULT_CACHE_CONTROL + set_header CACHE_CONTROL, DEFAULT_CACHE_CONTROL elsif control[:no_cache] - self[CACHE_CONTROL] = NO_CACHE + set_header CACHE_CONTROL, NO_CACHE if control[:extras] - self[CACHE_CONTROL] += ", #{control[:extras].join(', ')}" + set_header(CACHE_CONTROL, get_header(CACHE_CONTROL) + ", #{control[:extras].join(', ')}") end else extras = control[:extras] @@ -167,7 +170,7 @@ module ActionDispatch options << MUST_REVALIDATE if control[:must_revalidate] options.concat(extras) if extras - self[CACHE_CONTROL] = options.join(", ") + set_header CACHE_CONTROL, options.join(", ") end end end diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb index 3170389b36..e70e90018c 100644 --- a/actionpack/lib/action_dispatch/http/filter_parameters.rb +++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb @@ -50,13 +50,13 @@ module ActionDispatch protected def parameter_filter - parameter_filter_for @env.fetch("action_dispatch.parameter_filter") { + parameter_filter_for get_header("action_dispatch.parameter_filter") { return NULL_PARAM_FILTER } end def env_filter - user_key = @env.fetch("action_dispatch.parameter_filter") { + user_key = get_header("action_dispatch.parameter_filter") { return NULL_ENV_FILTER } parameter_filter_for(Array(user_key) + ENV_MATCH) diff --git a/actionpack/lib/action_dispatch/http/filter_redirect.rb b/actionpack/lib/action_dispatch/http/filter_redirect.rb index 94c1f2b41f..f4b806b8b5 100644 --- a/actionpack/lib/action_dispatch/http/filter_redirect.rb +++ b/actionpack/lib/action_dispatch/http/filter_redirect.rb @@ -16,7 +16,7 @@ module ActionDispatch def location_filters if request - request.env['action_dispatch.redirect_filter'] || [] + request.get_header('action_dispatch.redirect_filter') || [] else [] end diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb index bc5410dc38..fbdec6c132 100644 --- a/actionpack/lib/action_dispatch/http/headers.rb +++ b/actionpack/lib/action_dispatch/http/headers.rb @@ -30,27 +30,32 @@ module ActionDispatch HTTP_HEADER = /\A[A-Za-z0-9-]+\z/ include Enumerable - attr_reader :env - def initialize(env = {}) # :nodoc: - @env = env + def self.from_hash(hash) + new ActionDispatch::Request.new hash + end + + def initialize(request) # :nodoc: + @req = request end # Returns the value for the given key mapped to @env. def [](key) - @env[env_name(key)] + @req.get_header env_name(key) end # Sets the given value for the key mapped to @env. def []=(key, value) - @env[env_name(key)] = value + @req.set_header env_name(key), value end def key?(key) - @env.key? env_name(key) + @req.has_header? env_name(key) end alias :include? :key? + DEFAULT = Object.new # :nodoc: + # Returns the value for the given key mapped to @env. # # If the key is not found and an optional code block is not provided, @@ -58,18 +63,22 @@ module ActionDispatch # # If the code block is provided, then it will be run and # its result returned. - def fetch(key, *args, &block) - @env.fetch env_name(key), *args, &block + def fetch(key, default = DEFAULT) + @req.get_header(env_name(key)) do + return default unless default == DEFAULT + return yield if block_given? + raise NameError, key + end end def each(&block) - @env.each(&block) + @req.each_header(&block) end # Returns a new Http::Headers instance containing the contents of # <tt>headers_or_env</tt> and the original instance. def merge(headers_or_env) - headers = Http::Headers.new(env.dup) + headers = @req.dup.headers headers.merge!(headers_or_env) headers end @@ -79,11 +88,14 @@ module ActionDispatch # <tt>headers_or_env</tt>. def merge!(headers_or_env) headers_or_env.each do |key, value| - self[env_name(key)] = value + @req.set_header env_name(key), value end end + def env; @req.env.dup; end + private + # Converts a HTTP header name to an environment variable name if it is # not contained within the headers hash. def env_name(key) diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb index ff336b7354..e01d5ecc8f 100644 --- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb +++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb @@ -15,12 +15,13 @@ module ActionDispatch # For backward compatibility, the post \format is extracted from the # X-Post-Data-Format HTTP header if present. def content_mime_type - @env["action_dispatch.request.content_type"] ||= begin - if @env['CONTENT_TYPE'] =~ /^([^,\;]*)/ + get_header("action_dispatch.request.content_type") do |k| + v = if get_header('CONTENT_TYPE') =~ /^([^,\;]*)/ Mime::Type.lookup($1.strip.downcase) else nil end + set_header k, v end end @@ -30,14 +31,15 @@ module ActionDispatch # Returns the accepted MIME type for the request. def accepts - @env["action_dispatch.request.accepts"] ||= begin - header = @env['HTTP_ACCEPT'].to_s.strip + get_header("action_dispatch.request.accepts") do |k| + header = get_header('HTTP_ACCEPT').to_s.strip - if header.empty? + v = if header.empty? [content_mime_type] else Mime::Type.parse(header) end + set_header k, v end end @@ -52,14 +54,14 @@ module ActionDispatch end def formats - @env["action_dispatch.request.formats"] ||= begin + get_header("action_dispatch.request.formats") do |k| params_readable = begin parameters[:format] rescue ActionController::BadRequest false end - if params_readable + v = if params_readable Array(Mime[parameters[:format]]) elsif use_accept_header && valid_accept_header accepts @@ -68,6 +70,7 @@ module ActionDispatch else [Mime::HTML] end + set_header k, v end end @@ -102,7 +105,7 @@ module ActionDispatch # end def format=(extension) parameters[:format] = extension.to_s - @env["action_dispatch.request.formats"] = [Mime::Type.lookup_by_extension(parameters[:format])] + set_header "action_dispatch.request.formats", [Mime::Type.lookup_by_extension(parameters[:format])] end # Sets the \formats by string extensions. This differs from #format= by allowing you @@ -121,9 +124,9 @@ module ActionDispatch # end def formats=(extensions) parameters[:format] = extensions.first.to_s - @env["action_dispatch.request.formats"] = extensions.collect do |extension| + set_header "action_dispatch.request.formats", extensions.collect { |extension| Mime::Type.lookup_by_extension(extension) - end + } end # Receives an array of mimes and return the first user sent mime that diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb index 4defb7f858..3c9f8cd9e4 100644 --- a/actionpack/lib/action_dispatch/http/parameters.rb +++ b/actionpack/lib/action_dispatch/http/parameters.rb @@ -1,6 +1,3 @@ -require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/hash/indifferent_access' - module ActionDispatch module Http module Parameters @@ -8,20 +5,23 @@ module ActionDispatch # Returns both GET and POST \parameters in a single hash. def parameters - @env["action_dispatch.request.parameters"] ||= begin - params = begin - request_parameters.merge(query_parameters) - rescue EOFError - query_parameters.dup - end - params.merge!(path_parameters) - end + params = get_header("action_dispatch.request.parameters") + return params if params + + params = begin + request_parameters.merge(query_parameters) + rescue EOFError + query_parameters.dup + end + params.merge!(path_parameters) + set_header("action_dispatch.request.parameters", params) + params end alias :params :parameters def path_parameters=(parameters) #:nodoc: - @env.delete('action_dispatch.request.parameters') - @env[PARAMETERS_KEY] = parameters + delete_header('action_dispatch.request.parameters') + set_header PARAMETERS_KEY, parameters end # Returns a hash with the \parameters used to form the \path of the request. @@ -29,15 +29,7 @@ module ActionDispatch # # {'action' => 'my_action', 'controller' => 'my_controller'} def path_parameters - @env[PARAMETERS_KEY] ||= {} - end - - private - - # Convert nested Hash to HashWithIndifferentAccess. - # - def normalize_encode_params(params) - ActionDispatch::Request::Utils.normalize_encode_params params + get_header(PARAMETERS_KEY) || {} end end end diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb index de28cd0998..45600d0a61 100644 --- a/actionpack/lib/action_dispatch/http/request.rb +++ b/actionpack/lib/action_dispatch/http/request.rb @@ -20,8 +20,6 @@ module ActionDispatch include ActionDispatch::Http::FilterParameters include ActionDispatch::Http::URL - HTTP_X_REQUEST_ID = "HTTP_X_REQUEST_ID".freeze # :nodoc: - autoload :Session, 'action_dispatch/request/session' autoload :Utils, 'action_dispatch/request/utils' @@ -31,17 +29,20 @@ module ActionDispatch PATH_TRANSLATED REMOTE_HOST REMOTE_IDENT REMOTE_USER REMOTE_ADDR SERVER_NAME SERVER_PROTOCOL + ORIGINAL_SCRIPT_NAME HTTP_ACCEPT HTTP_ACCEPT_CHARSET HTTP_ACCEPT_ENCODING HTTP_ACCEPT_LANGUAGE HTTP_CACHE_CONTROL HTTP_FROM HTTP_NEGOTIATE HTTP_PRAGMA HTTP_CLIENT_IP HTTP_X_FORWARDED_FOR HTTP_VERSION + HTTP_X_REQUEST_ID HTTP_X_FORWARDED_HOST + SERVER_ADDR ].freeze ENV_METHODS.each do |env| class_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{env.sub(/^HTTP_/n, '').downcase} # def accept_charset - @env["#{env}".freeze] # @env["HTTP_ACCEPT_CHARSET".freeze] + get_header "#{env}".freeze # get_header "HTTP_ACCEPT_CHARSET".freeze end # end METHOD end @@ -67,8 +68,27 @@ module ActionDispatch end end + PASS_NOT_FOUND = Class.new { # :nodoc: + def self.action(_); self; end + def self.call(_); [404, {'X-Cascade' => 'pass'}, []]; end + } + + def controller_class + check_path_parameters! + params = path_parameters + + if params.key?(:controller) + controller_param = params[:controller].underscore + params[:action] ||= 'index' + const_name = "#{controller_param.camelize}Controller" + ActiveSupport::Dependencies.constantize(const_name) + else + PASS_NOT_FOUND + end + end + def key?(key) - @env.key?(key) + has_header? key end # List of HTTP request methods from the following RFCs: @@ -109,44 +129,44 @@ module ActionDispatch end def routes # :nodoc: - env["action_dispatch.routes".freeze] + get_header("action_dispatch.routes".freeze) end def routes=(routes) # :nodoc: - env["action_dispatch.routes".freeze] = routes - end - - def original_script_name # :nodoc: - env['ORIGINAL_SCRIPT_NAME'.freeze] + set_header("action_dispatch.routes".freeze, routes) end def engine_script_name(_routes) # :nodoc: - env[_routes.env_key] + get_header(_routes.env_key) end def engine_script_name=(name) # :nodoc: - env[routes.env_key] = name.dup + set_header(routes.env_key, name.dup) end def request_method=(request_method) #:nodoc: if check_method(request_method) - @request_method = env["REQUEST_METHOD"] = request_method + @request_method = set_header("REQUEST_METHOD", request_method) end end def controller_instance # :nodoc: - env['action_controller.instance'.freeze] + get_header('action_controller.instance'.freeze) end def controller_instance=(controller) # :nodoc: - env['action_controller.instance'.freeze] = controller + set_header('action_controller.instance'.freeze, controller) + end + + def http_auth_salt + get_header "action_dispatch.http_auth_salt" end def show_exceptions? # :nodoc: # We're treating `nil` as "unset", and we want the default setting to be # `true`. This logic should be extracted to `env_config` and calculated # once. - !(env['action_dispatch.show_exceptions'.freeze] == false) + !(get_header('action_dispatch.show_exceptions'.freeze) == false) end # Returns a symbol form of the #request_method @@ -158,7 +178,7 @@ module ActionDispatch # even if it was overridden by middleware. See #request_method for # more information. def method - @method ||= check_method(env["rack.methodoverride.original_method"] || env['REQUEST_METHOD']) + @method ||= check_method(get_header("rack.methodoverride.original_method") || get_header('REQUEST_METHOD')) end # Returns a symbol form of the #method @@ -170,7 +190,7 @@ module ActionDispatch # # request.headers["Content-Type"] # => "text/plain" def headers - @headers ||= Http::Headers.new(@env) + @headers ||= Http::Headers.new(self) end # Returns a +String+ with the last requested path including their params. @@ -181,7 +201,7 @@ module ActionDispatch # # get '/foo?bar' # request.original_fullpath # => '/foo?bar' def original_fullpath - @original_fullpath ||= (env["ORIGINAL_FULLPATH"] || fullpath) + @original_fullpath ||= (get_header("ORIGINAL_FULLPATH") || fullpath) end # Returns the +String+ full path including params of the last URL requested. @@ -220,7 +240,7 @@ module ActionDispatch # (case-insensitive), which may need to be manually added depending on the # choice of JavaScript libraries and frameworks. def xml_http_request? - @env['HTTP_X_REQUESTED_WITH'] =~ /XMLHttpRequest/i + get_header('HTTP_X_REQUESTED_WITH') =~ /XMLHttpRequest/i end alias :xhr? :xml_http_request? @@ -232,11 +252,11 @@ module ActionDispatch # Returns the IP address of client as a +String+, # usually set by the RemoteIp middleware. def remote_ip - @remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s + @remote_ip ||= (get_header("action_dispatch.remote_ip") || ip).to_s end def remote_ip=(remote_ip) - @env["action_dispatch.remote_ip".freeze] = remote_ip + set_header "action_dispatch.remote_ip".freeze, remote_ip end ACTION_DISPATCH_REQUEST_ID = "action_dispatch.request_id".freeze # :nodoc: @@ -248,43 +268,39 @@ module ActionDispatch # This unique ID is useful for tracing a request from end-to-end as part of logging or debugging. # This relies on the rack variable set by the ActionDispatch::RequestId middleware. def request_id - env[ACTION_DISPATCH_REQUEST_ID] + get_header ACTION_DISPATCH_REQUEST_ID end def request_id=(id) # :nodoc: - env[ACTION_DISPATCH_REQUEST_ID] = id + set_header ACTION_DISPATCH_REQUEST_ID, id end alias_method :uuid, :request_id - def x_request_id # :nodoc: - @env[HTTP_X_REQUEST_ID] - end - # Returns the lowercase name of the HTTP server software. def server_software - (@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil + (get_header('SERVER_SOFTWARE') && /^([a-zA-Z]+)/ =~ get_header('SERVER_SOFTWARE')) ? $1.downcase : nil end # Read the request \body. This is useful for web services that need to # work with raw requests directly. def raw_post - unless @env.include? 'RAW_POST_DATA' + unless has_header? 'RAW_POST_DATA' raw_post_body = body - @env['RAW_POST_DATA'] = raw_post_body.read(content_length) + set_header('RAW_POST_DATA', raw_post_body.read(content_length)) raw_post_body.rewind if raw_post_body.respond_to?(:rewind) end - @env['RAW_POST_DATA'] + get_header 'RAW_POST_DATA' 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'] + if raw_post = get_header('RAW_POST_DATA') raw_post.force_encoding(Encoding::BINARY) StringIO.new(raw_post) else - @env['rack.input'] + body_stream end end @@ -295,7 +311,7 @@ module ActionDispatch end def body_stream #:nodoc: - @env['rack.input'] + get_header('rack.input') end # TODO This should be broken apart into AD::Request::Session and probably @@ -306,20 +322,22 @@ module ActionDispatch else self.session = {} end - @env['action_dispatch.request.flash_hash'] = nil + set_header('action_dispatch.request.flash_hash', nil) end def session=(session) #:nodoc: - Session.set @env, session + Session.set self, session end def session_options=(options) - Session::Options.set @env, options + Session::Options.set self, options end # Override Rack's GET method to support indifferent access def GET - @env["action_dispatch.request.query_parameters"] ||= normalize_encode_params(super || {}) + get_header("action_dispatch.request.query_parameters") do |k| + set_header k, Request::Utils.normalize_encode_params(super || {}) + end rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e raise ActionController::BadRequest.new(:query, e) end @@ -327,7 +345,9 @@ module ActionDispatch # Override Rack's POST method to support indifferent access def POST - @env["action_dispatch.request.request_parameters"] ||= normalize_encode_params(super || {}) + get_header("action_dispatch.request.request_parameters") do + self.request_parameters = Request::Utils.normalize_encode_params(super || {}) + end rescue Rack::Utils::ParameterTypeError, Rack::Utils::InvalidParameterError => e raise ActionController::BadRequest.new(:request, e) end @@ -336,10 +356,10 @@ module ActionDispatch # Returns the authorization header regardless of whether it was specified directly or through one of the # proxy alternatives. def authorization - @env['HTTP_AUTHORIZATION'] || - @env['X-HTTP_AUTHORIZATION'] || - @env['X_HTTP_AUTHORIZATION'] || - @env['REDIRECT_X_HTTP_AUTHORIZATION'] + get_header('HTTP_AUTHORIZATION') || + get_header('X-HTTP_AUTHORIZATION') || + get_header('X_HTTP_AUTHORIZATION') || + get_header('REDIRECT_X_HTTP_AUTHORIZATION') end # True if the request came from localhost, 127.0.0.1. @@ -348,11 +368,12 @@ module ActionDispatch end def request_parameters=(params) - env["action_dispatch.request.request_parameters".freeze] = params + raise if params.nil? + set_header("action_dispatch.request.request_parameters".freeze, params) end def logger - env["action_dispatch.logger".freeze] + get_header("action_dispatch.logger".freeze) end private diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index fd92e89231..4aee489912 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -65,7 +65,7 @@ module ActionDispatch # :nodoc: CONTENT_TYPE = "Content-Type".freeze SET_COOKIE = "Set-Cookie".freeze LOCATION = "Location".freeze - NO_CONTENT_CODES = [204, 304] + NO_CONTENT_CODES = [100, 101, 102, 204, 205, 304] cattr_accessor(:default_charset) { "utf-8" } cattr_accessor(:default_headers) @@ -150,6 +150,11 @@ module ActionDispatch # :nodoc: yield self if block_given? end + def have_header?(key); headers.key? key; end + def get_header(key); headers[key]; end + def set_header(key, v); headers[key] = v; end + def delete_header(key); headers.delete key; end + def await_commit synchronize do @cv.wait_until { @committed } @@ -256,25 +261,9 @@ module ActionDispatch # :nodoc: parts end - def set_cookie(key, value) - ::Rack::Utils.set_cookie_header!(header, key, value) - end - - def delete_cookie(key, value={}) - ::Rack::Utils.delete_cookie_header!(header, key, value) - end - # The location header we'll be responding with. - def location - headers[LOCATION] - end alias_method :redirect_url, :location - # Sets the location header we'll be responding with. - def location=(url) - headers[LOCATION] = url - end - def close stream.close if stream.respond_to?(:close) end @@ -305,7 +294,7 @@ module ActionDispatch # :nodoc: # assert_equal 'AuthorOfNewPage', r.cookies['author'] def cookies cookies = {} - if header = self[SET_COOKIE] + if header = get_header(SET_COOKIE) header = header.split("\n") if header.respond_to?(:to_str) header.each do |cookie| if pair = cookie.split(';').first @@ -341,14 +330,14 @@ module ActionDispatch # :nodoc: end def assign_default_content_type_and_charset! - return if self[CONTENT_TYPE].present? + return if get_header(CONTENT_TYPE).present? @content_type ||= Mime::HTML type = @content_type.to_s.dup type << "; charset=#{charset}" if append_charset? - self[CONTENT_TYPE] = type + set_header CONTENT_TYPE, type end def append_charset? @@ -392,10 +381,9 @@ module ActionDispatch # :nodoc: end def rack_response(status, header) - header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join) - if NO_CONTENT_CODES.include?(@status) header.delete CONTENT_TYPE + header.delete 'Content-Length' [status, header, []] else [status, header, RackBody.new(self)] diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb index 6fcf49030b..e413954066 100644 --- a/actionpack/lib/action_dispatch/http/url.rb +++ b/actionpack/lib/action_dispatch/http/url.rb @@ -229,10 +229,10 @@ module ActionDispatch # req = Request.new 'HTTP_HOST' => 'example.com:8080' # req.raw_host_with_port # => "example.com:8080" def raw_host_with_port - if forwarded = env["HTTP_X_FORWARDED_HOST"].presence + if forwarded = x_forwarded_host.presence forwarded.split(/,\s?/).last else - env['HTTP_HOST'] || "#{env['SERVER_NAME'] || env['SERVER_ADDR']}:#{env['SERVER_PORT']}" + get_header('HTTP_HOST') || "#{server_name || server_addr}:#{get_header('SERVER_PORT')}" end end @@ -348,7 +348,7 @@ module ActionDispatch end def server_port - @env['SERVER_PORT'].to_i + get_header('SERVER_PORT').to_i end # Returns the \domain part of a \host, such as "rubyonrails.org" in "www.rubyonrails.org". You can specify diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb index cf4f654ed6..6d0387cf74 100644 --- a/actionpack/lib/action_dispatch/middleware/cookies.rb +++ b/actionpack/lib/action_dispatch/middleware/cookies.rb @@ -1,6 +1,4 @@ require 'active_support/core_ext/hash/keys' -require 'active_support/core_ext/module/attribute_accessors' -require 'active_support/core_ext/object/blank' require 'active_support/key_generator' require 'active_support/message_verifier' require 'active_support/json' @@ -8,48 +6,50 @@ require 'active_support/json' module ActionDispatch class Request < Rack::Request def cookie_jar - env['action_dispatch.cookies'.freeze] ||= Cookies::CookieJar.build(self, cookies) + get_header('action_dispatch.cookies'.freeze) do + self.cookie_jar = Cookies::CookieJar.build(self, cookies) + end end # :stopdoc: def have_cookie_jar? - env.key? 'action_dispatch.cookies'.freeze + has_header? 'action_dispatch.cookies'.freeze end def cookie_jar=(jar) - env['action_dispatch.cookies'.freeze] = jar + set_header 'action_dispatch.cookies'.freeze, jar end def key_generator - env[Cookies::GENERATOR_KEY] + get_header Cookies::GENERATOR_KEY end def signed_cookie_salt - env[Cookies::SIGNED_COOKIE_SALT] + get_header Cookies::SIGNED_COOKIE_SALT end def encrypted_cookie_salt - env[Cookies::ENCRYPTED_COOKIE_SALT] + get_header Cookies::ENCRYPTED_COOKIE_SALT end def encrypted_signed_cookie_salt - env[Cookies::ENCRYPTED_SIGNED_COOKIE_SALT] + get_header Cookies::ENCRYPTED_SIGNED_COOKIE_SALT end def secret_token - env[Cookies::SECRET_TOKEN] + get_header Cookies::SECRET_TOKEN end def secret_key_base - env[Cookies::SECRET_KEY_BASE] + get_header Cookies::SECRET_KEY_BASE end def cookies_serializer - env[Cookies::COOKIES_SERIALIZER] + get_header Cookies::COOKIES_SERIALIZER end def cookies_digest - env[Cookies::COOKIES_DIGEST] + get_header Cookies::COOKIES_DIGEST end # :startdoc: end diff --git a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb index 226a688fe2..66bb74b9c5 100644 --- a/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb @@ -55,18 +55,17 @@ module ActionDispatch response rescue Exception => exception raise exception unless request.show_exceptions? - render_exception(env, exception) + render_exception(request, exception) end private - def render_exception(env, exception) - backtrace_cleaner = env['action_dispatch.backtrace_cleaner'] + def render_exception(request, exception) + backtrace_cleaner = request.get_header('action_dispatch.backtrace_cleaner') wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) - log_error(env, wrapper) + log_error(request, wrapper) - if env['action_dispatch.show_detailed_exceptions'] - request = Request.new(env) + if request.get_header('action_dispatch.show_detailed_exceptions') traces = wrapper.traces trace_to_show = 'Application Trace' @@ -108,8 +107,8 @@ module ActionDispatch [status, {'Content-Type' => "#{format}; charset=#{Response.default_charset}", 'Content-Length' => body.bytesize.to_s}, [body]] end - def log_error(env, wrapper) - logger = logger(env) + def log_error(request, wrapper) + logger = logger(request) return unless logger exception = wrapper.exception @@ -125,8 +124,8 @@ module ActionDispatch end end - def logger(env) - env['action_dispatch.logger'] || stderr_logger + def logger(request) + request.logger || stderr_logger end def stderr_logger diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index 23da169b22..6041f84834 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -6,15 +6,17 @@ module ActionDispatch # read a notice you put there or <tt>flash["notice"] = "hello"</tt> # to put a new one. def flash - @env[Flash::KEY] ||= Flash::FlashHash.from_session_value(session["flash"]) + flash = flash_hash + return flash if flash + self.flash = Flash::FlashHash.from_session_value(session["flash"]) end def flash=(flash) - @env[Flash::KEY] = flash + set_header Flash::KEY, flash end def flash_hash # :nodoc: - @env[Flash::KEY] + get_header Flash::KEY end end @@ -274,7 +276,7 @@ module ActionDispatch req = ActionDispatch::Request.new env @app.call(env) ensure - session = Request::Session.find(env) || {} + session = Request::Session.find(req) || {} flash_hash = req.flash_hash if flash_hash && (flash_hash.present? || session.key?('flash')) diff --git a/actionpack/lib/action_dispatch/middleware/params_parser.rb b/actionpack/lib/action_dispatch/middleware/params_parser.rb index 402ad778fa..9cde9c9b98 100644 --- a/actionpack/lib/action_dispatch/middleware/params_parser.rb +++ b/actionpack/lib/action_dispatch/middleware/params_parser.rb @@ -37,7 +37,9 @@ module ActionDispatch def call(env) request = Request.new(env) - request.request_parameters = parse_formatted_parameters(request, @parsers) + parse_formatted_parameters(request, @parsers) do |params| + request.request_parameters = params + end @app.call(env) end @@ -48,7 +50,7 @@ module ActionDispatch strategy = parsers.fetch(request.content_mime_type) { return nil } - strategy.call(request.raw_post) + yield strategy.call(request.raw_post) rescue => e # JSON or Ruby code block errors logger(request).debug "Error occurred while parsing request parameters.\nContents:\n\n#{request.raw_post}" diff --git a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb index 7cde76b30e..0f27984550 100644 --- a/actionpack/lib/action_dispatch/middleware/public_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/public_exceptions.rb @@ -17,8 +17,8 @@ module ActionDispatch end def call(env) - status = env["PATH_INFO"][1..-1].to_i request = ActionDispatch::Request.new(env) + status = request.path_info[1..-1].to_i content_type = request.formats.first body = { :status => status, :error => Rack::Utils::HTTP_STATUS_CODES.fetch(status, Rack::Utils::HTTP_STATUS_CODES[500]) } diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 84df55fd5a..b924df789f 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -36,6 +36,11 @@ module ActionDispatch @default_options.delete(:sidbits) @default_options.delete(:secure_random) end + + private + def make_request(env) + ActionDispatch::Request.new env + end end module StaleSessionCheck @@ -65,8 +70,8 @@ module ActionDispatch end module SessionObject # :nodoc: - def prepare_session(env) - Request::Session.create(self, env, @default_options) + def prepare_session(req) + Request::Session.create(self, req, @default_options) end def loaded_session?(session) @@ -81,8 +86,7 @@ module ActionDispatch private - def set_cookie(env, session_id, cookie) - request = ActionDispatch::Request.new(env) + def set_cookie(request, session_id, cookie) request.cookie_jar[key] = cookie end end diff --git a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb index d8f9614904..e225f356df 100644 --- a/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb @@ -71,16 +71,16 @@ module ActionDispatch super(app, options.merge!(:cookie_only => true)) end - def destroy_session(env, session_id, options) + def destroy_session(req, session_id, options) new_sid = generate_sid unless options[:drop] # Reset hash and Assign the new session id - env["action_dispatch.request.unsigned_session_cookie"] = new_sid ? { "session_id" => new_sid } : {} + req.set_header("action_dispatch.request.unsigned_session_cookie", new_sid ? { "session_id" => new_sid } : {}) new_sid end - def load_session(env) + def load_session(req) stale_session_check! do - data = unpacked_cookie_data(env) + data = unpacked_cookie_data(req) data = persistent_session_id!(data) [data["session_id"], data] end @@ -88,20 +88,21 @@ module ActionDispatch private - def extract_session_id(env) + def extract_session_id(req) stale_session_check! do - unpacked_cookie_data(env)["session_id"] + unpacked_cookie_data(req)["session_id"] end end - def unpacked_cookie_data(env) - env["action_dispatch.request.unsigned_session_cookie"] ||= begin - stale_session_check! do - if data = get_cookie(env) + def unpacked_cookie_data(req) + req.get_header("action_dispatch.request.unsigned_session_cookie") do |k| + v = stale_session_check! do + if data = get_cookie(req) data.stringify_keys! end data || {} end + req.set_header k, v end end @@ -111,21 +112,20 @@ module ActionDispatch data end - def set_session(env, sid, session_data, options) + def set_session(req, sid, session_data, options) session_data["session_id"] = sid session_data end - def set_cookie(env, session_id, cookie) - cookie_jar(env)[@key] = cookie + def set_cookie(request, session_id, cookie) + cookie_jar(request)[@key] = cookie end - def get_cookie(env) - cookie_jar(env)[@key] + def get_cookie(req) + cookie_jar(req)[@key] end - def cookie_jar(env) - request = ActionDispatch::Request.new(env) + def cookie_jar(request) request.cookie_jar.signed_or_encrypted end end diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index 12d8dab8eb..64695f9738 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -31,7 +31,7 @@ module ActionDispatch @app.call(env) rescue Exception => exception if request.show_exceptions? - render_exception(env, exception) + render_exception(request, exception) else raise exception end @@ -39,14 +39,14 @@ module ActionDispatch private - def render_exception(env, exception) - backtrace_cleaner = env['action_dispatch.backtrace_cleaner'] + def render_exception(request, exception) + backtrace_cleaner = request.get_header 'action_dispatch.backtrace_cleaner' wrapper = ExceptionWrapper.new(backtrace_cleaner, exception) status = wrapper.status_code - env["action_dispatch.exception"] = wrapper.exception - env["action_dispatch.original_path"] = env["PATH_INFO"] - env["PATH_INFO"] = "/#{status}" - response = @exceptions_app.call(env) + request.set_header "action_dispatch.exception", wrapper.exception + request.set_header "action_dispatch.original_path", request.path_info + request.path_info = "/#{status}" + response = @exceptions_app.call(request.env) response[1]['X-Cascade'] == 'pass' ? pass_response(status) : response rescue Exception => failsafe_error $stderr.puts "Error during failsafe response: #{failsafe_error}\n #{failsafe_error.backtrace * "\n "}" diff --git a/actionpack/lib/action_dispatch/middleware/stack.rb b/actionpack/lib/action_dispatch/middleware/stack.rb index 0430ce3b9a..90e2ae6802 100644 --- a/actionpack/lib/action_dispatch/middleware/stack.rb +++ b/actionpack/lib/action_dispatch/middleware/stack.rb @@ -87,7 +87,7 @@ module ActionDispatch middlewares.freeze.reverse.inject(app) { |a, e| e.build(a) } end - protected + private def assert_index(index, where) index = get_class index @@ -96,8 +96,6 @@ module ActionDispatch i end - private - def get_class(klass) if klass.is_a?(String) || klass.is_a?(Symbol) classcache = ActiveSupport::Dependencies::Reference diff --git a/actionpack/lib/action_dispatch/request/session.rb b/actionpack/lib/action_dispatch/request/session.rb index bae3d909fa..b946ccb49f 100644 --- a/actionpack/lib/action_dispatch/request/session.rb +++ b/actionpack/lib/action_dispatch/request/session.rb @@ -11,31 +11,31 @@ module ActionDispatch Unspecified = Object.new # Creates a session hash, merging the properties of the previous session if any - def self.create(store, env, default_options) - session_was = find env - session = Request::Session.new(store, env) + def self.create(store, req, default_options) + session_was = find req + session = Request::Session.new(store, req) session.merge! session_was if session_was - set(env, session) - Options.set(env, Request::Session::Options.new(store, default_options)) + set(req, session) + Options.set(req, Request::Session::Options.new(store, default_options)) session end - def self.find(env) - env[ENV_SESSION_KEY] + def self.find(req) + req.get_header ENV_SESSION_KEY end - def self.set(env, session) - env[ENV_SESSION_KEY] = session + def self.set(req, session) + req.set_header ENV_SESSION_KEY, session end class Options #:nodoc: - def self.set(env, options) - env[ENV_SESSION_OPTIONS_KEY] = options + def self.set(req, options) + req.set_header ENV_SESSION_OPTIONS_KEY, options end - def self.find(env) - env[ENV_SESSION_OPTIONS_KEY] + def self.find(req) + req.get_header ENV_SESSION_OPTIONS_KEY end def initialize(by, default_options) @@ -47,9 +47,9 @@ module ActionDispatch @delegate[key] end - def id(env) + def id(req) @delegate.fetch(:id) { - @by.send(:extract_session_id, env) + @by.send(:extract_session_id, req) } end @@ -58,26 +58,26 @@ module ActionDispatch def values_at(*args); @delegate.values_at(*args); end end - def initialize(by, env) + def initialize(by, req) @by = by - @env = env + @req = req @delegate = {} @loaded = false @exists = nil # we haven't checked yet end def id - options.id(@env) + options.id(@req) end def options - Options.find @env + Options.find @req end def destroy clear options = self.options || {} - @by.send(:destroy_session, @env, options.id(@env), options) + @by.send(:destroy_session, @req, options.id(@req), options) # Load the new sid to be written with the response @loaded = false @@ -181,7 +181,7 @@ module ActionDispatch def exists? return @exists unless @exists.nil? - @exists = @by.send(:session_exists?, @env) + @exists = @by.send(:session_exists?, @req) end def loaded? @@ -209,7 +209,7 @@ module ActionDispatch end def load! - id, session = @by.load_session @env + id, session = @by.load_session @req options[:id] = id @delegate.replace(stringify_keys(session)) @loaded = true diff --git a/actionpack/lib/action_dispatch/routing/mapper.rb b/actionpack/lib/action_dispatch/routing/mapper.rb index 51bafb539f..1acfb2bfe8 100644 --- a/actionpack/lib/action_dispatch/routing/mapper.rb +++ b/actionpack/lib/action_dispatch/routing/mapper.rb @@ -283,12 +283,16 @@ module ActionDispatch end def app(blocks) - if to.respond_to?(:call) - Constraints.new(to, blocks, Constraints::CALL) - elsif blocks.any? - Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE) + if to.is_a?(Class) && to < ActionController::Metal + Routing::RouteSet::StaticDispatcher.new to else - dispatcher(defaults.key?(:controller)) + if to.respond_to?(:call) + Constraints.new(to, blocks, Constraints::CALL) + elsif blocks.any? + Constraints.new(dispatcher(defaults.key?(:controller)), blocks, Constraints::SERVE) + else + dispatcher(defaults.key?(:controller)) + end end end @@ -368,7 +372,7 @@ module ActionDispatch end def dispatcher(raise_on_name_error) - @set.dispatcher raise_on_name_error + Routing::RouteSet::Dispatcher.new raise_on_name_error end end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index 20926012b4..e4b8d5993e 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -1,6 +1,5 @@ require 'action_dispatch/journey' require 'forwardable' -require 'thread_safe' require 'active_support/concern' require 'active_support/core_ext/object/to_query' require 'active_support/core_ext/hash/slice' @@ -23,64 +22,43 @@ module ActionDispatch class Dispatcher < Routing::Endpoint def initialize(raise_on_name_error) @raise_on_name_error = raise_on_name_error - @controller_class_names = ThreadSafe::Cache.new end def dispatcher?; true; end def serve(req) - req.check_path_parameters! - params = req.path_parameters - - prepare_params!(params) - - controller = controller(params, @raise_on_name_error) do + params = req.path_parameters + controller = controller req + res = controller.make_response! req + dispatch(controller, params[:action], req, res) + rescue NameError => e + if @raise_on_name_error + raise ActionController::RoutingError, e.message, e.backtrace + else return [404, {'X-Cascade' => 'pass'}, []] end - - dispatch(controller, params[:action], req) end - def prepare_params!(params) - normalize_controller!(params) - merge_default_action!(params) - end + private - # If this is a default_controller (i.e. a controller specified by the user) - # we should raise an error in case it's not found, because it usually means - # a user error. However, if the controller was retrieved through a dynamic - # segment, as in :controller(/:action), we should simply return nil and - # delegate the control back to Rack cascade. Besides, if this is not a default - # controller, it means we should respect the @scope[:module] parameter. - def controller(params, raise_on_name_error=true) - controller_reference params.fetch(:controller) { yield } - rescue NameError => e - raise ActionController::RoutingError, e.message, e.backtrace if raise_on_name_error - yield + def controller(req) + req.controller_class end - protected - - attr_reader :controller_class_names - - def controller_reference(controller_param) - const_name = controller_class_names[controller_param] ||= "#{controller_param.camelize}Controller" - ActiveSupport::Dependencies.constantize(const_name) + def dispatch(controller, action, req, res) + controller.dispatch(action, req, res) end + end - private - - def dispatch(controller, action, req) - controller.action(action).call(req.env) + class StaticDispatcher < Dispatcher + def initialize(controller_class) + super(false) + @controller_class = controller_class end - def normalize_controller!(params) - params[:controller] = params[:controller].underscore if params.key?(:controller) - end + private - def merge_default_action!(params) - params[:action] ||= 'index' - end + def controller(_); @controller_class; end end # A NamedRouteCollection instance is a collection of named routes, and also @@ -202,9 +180,9 @@ module ActionDispatch private def optimized_helper(args) - params = parameterize_args(args) { |k| + params = parameterize_args(args) do raise_generation_error(args) - } + end @route.format params end @@ -316,7 +294,7 @@ module ActionDispatch attr_accessor :formatter, :set, :named_routes, :default_scope, :router attr_accessor :disable_clear_and_finalize, :resources_path_names - attr_accessor :default_url_options, :dispatcher_class + attr_accessor :default_url_options attr_reader :env_key alias :routes :set @@ -359,7 +337,6 @@ module ActionDispatch @set = Journey::Routes.new @router = Journey::Router.new @set @formatter = Journey::Formatter.new self - @dispatcher_class = Routing::RouteSet::Dispatcher end def relative_url_root @@ -374,6 +351,11 @@ module ActionDispatch ActionDispatch::Request end + def make_request(env) + request_class.new env + end + private :make_request + def draw(&block) clear! unless @disable_clear_and_finalize eval_block(block) @@ -417,10 +399,6 @@ module ActionDispatch @prepend.each { |blk| eval_block(blk) } end - def dispatcher(raise_on_name_error) - dispatcher_class.new(raise_on_name_error) - end - module MountedHelpers extend ActiveSupport::Concern include UrlFor @@ -731,7 +709,7 @@ module ActionDispatch end def call(env) - req = request_class.new(env) + req = make_request(env) req.path_info = Journey::Router::Utils.normalize_path(req.path_info) @router.serve(req) end @@ -747,7 +725,7 @@ module ActionDispatch raise ActionController::RoutingError, e.message end - req = request_class.new(env) + req = make_request(env) @router.recognize(req) do |route, params| params.merge!(extras) params.each do |key, value| @@ -760,14 +738,13 @@ module ActionDispatch req.path_parameters = old_params.merge params app = route.app if app.matches?(req) && app.dispatcher? - dispatcher = app.app - - dispatcher.controller(params, false) do + begin + req.controller_class + rescue NameError raise ActionController::RoutingError, "A route matches #{path.inspect}, but references missing controller: #{params[:controller].camelize}Controller" end - dispatcher.prepare_params!(params) - return params + return req.path_parameters end end diff --git a/actionpack/lib/action_dispatch/testing/integration.rb b/actionpack/lib/action_dispatch/testing/integration.rb index 0cdc6d4e77..4dfd4f3f71 100644 --- a/actionpack/lib/action_dispatch/testing/integration.rb +++ b/actionpack/lib/action_dispatch/testing/integration.rb @@ -359,10 +359,10 @@ module ActionDispatch # this modifies the passed request_env directly if headers.present? - Http::Headers.new(request_env).merge!(headers) + Http::Headers.from_hash(request_env).merge!(headers) end if env.present? - Http::Headers.new(request_env).merge!(env) + Http::Headers.from_hash(request_env).merge!(env) end session = Rack::Test::Session.new(_mock_session) |