aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/http
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack/lib/action_dispatch/http')
-rw-r--r--actionpack/lib/action_dispatch/http/cache.rb63
-rw-r--r--actionpack/lib/action_dispatch/http/filter_parameters.rb6
-rw-r--r--actionpack/lib/action_dispatch/http/headers.rb15
-rw-r--r--actionpack/lib/action_dispatch/http/mime_negotiation.rb23
-rw-r--r--actionpack/lib/action_dispatch/http/mime_type.rb210
-rw-r--r--actionpack/lib/action_dispatch/http/parameters.rb9
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb73
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb133
-rw-r--r--actionpack/lib/action_dispatch/http/upload.rb16
-rw-r--r--actionpack/lib/action_dispatch/http/url.rb51
10 files changed, 421 insertions, 178 deletions
diff --git a/actionpack/lib/action_dispatch/http/cache.rb b/actionpack/lib/action_dispatch/http/cache.rb
index bea62b94d2..a7f93b780e 100644
--- a/actionpack/lib/action_dispatch/http/cache.rb
+++ b/actionpack/lib/action_dispatch/http/cache.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/object/blank'
module ActionDispatch
module Http
@@ -60,6 +59,20 @@ module ActionDispatch
headers[LAST_MODIFIED] = utc_time.httpdate
end
+ def date
+ if date_header = headers['Date']
+ Time.httpdate(date_header)
+ end
+ end
+
+ def date?
+ headers.include?('Date')
+ end
+
+ def date=(utc_time)
+ headers['Date'] = utc_time.httpdate
+ end
+
def etag=(etag)
key = ActiveSupport::Cache.expand_cache_key(etag)
@etag = self[ETAG] = %("#{Digest::MD5.hexdigest(key)}")
@@ -70,17 +83,37 @@ module ActionDispatch
LAST_MODIFIED = "Last-Modified".freeze
ETAG = "ETag".freeze
CACHE_CONTROL = "Cache-Control".freeze
+ SPESHUL_KEYS = %w[extras no-cache max-age public must-revalidate]
- def prepare_cache_control!
- @cache_control = {}
- @etag = self[ETAG]
-
+ def cache_control_segments
if cache_control = self[CACHE_CONTROL]
- cache_control.split(/,\s*/).each do |segment|
- first, last = segment.split("=")
- @cache_control[first.to_sym] = last || true
+ cache_control.delete(' ').split(',')
+ else
+ []
+ end
+ end
+
+ def cache_control_headers
+ cache_control = {}
+
+ cache_control_segments.each do |segment|
+ directive, argument = segment.split('=', 2)
+
+ if SPESHUL_KEYS.include? directive
+ key = directive.tr('-', '_')
+ cache_control[key.to_sym] = argument || true
+ else
+ cache_control[:extras] ||= []
+ cache_control[:extras] << segment
end
end
+
+ cache_control
+ end
+
+ def prepare_cache_control!
+ @cache_control = cache_control_headers
+ @etag = self[ETAG]
end
def handle_conditional_get!
@@ -96,14 +129,24 @@ module ActionDispatch
MUST_REVALIDATE = "must-revalidate".freeze
def set_conditional_cache_control!
- return if self[CACHE_CONTROL].present?
+ control = {}
+ cc_headers = cache_control_headers
+ if extras = cc_headers.delete(:extras)
+ @cache_control[:extras] ||= []
+ @cache_control[:extras] += extras
+ @cache_control[:extras].uniq!
+ end
- control = @cache_control
+ control.merge! cc_headers
+ control.merge! @cache_control
if control.empty?
headers[CACHE_CONTROL] = DEFAULT_CACHE_CONTROL
elsif control[:no_cache]
headers[CACHE_CONTROL] = NO_CACHE
+ if control[:extras]
+ headers[CACHE_CONTROL] += ", #{control[:extras].join(', ')}"
+ end
else
extras = control[:extras]
max_age = control[:max_age]
diff --git a/actionpack/lib/action_dispatch/http/filter_parameters.rb b/actionpack/lib/action_dispatch/http/filter_parameters.rb
index 02a15ad599..47cf41cfa3 100644
--- a/actionpack/lib/action_dispatch/http/filter_parameters.rb
+++ b/actionpack/lib/action_dispatch/http/filter_parameters.rb
@@ -1,4 +1,3 @@
-require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/hash/keys'
require 'active_support/core_ext/object/duplicable'
@@ -10,8 +9,6 @@ module ActionDispatch
# value of the params hash and all subhashes is passed to it, the value
# or key can be replaced using String#replace or similar method.
#
- # Examples:
- #
# env["action_dispatch.parameter_filter"] = [:password]
# => replaces the value to all keys matching /password/i with "[FILTERED]"
#
@@ -22,7 +19,6 @@ module ActionDispatch
# v.reverse! if k =~ /secret/i
# end
# => reverses the value to all keys matching /secret/i
- #
module FilterParameters
extend ActiveSupport::Concern
@@ -50,7 +46,7 @@ module ActionDispatch
end
def env_filter
- parameter_filter_for(Array(@env["action_dispatch.parameter_filter"]) << /RAW_POST_DATA/)
+ parameter_filter_for(Array(@env["action_dispatch.parameter_filter"]) + [/RAW_POST_DATA/, "rack.request.form_vars"])
end
def parameter_filter_for(filters)
diff --git a/actionpack/lib/action_dispatch/http/headers.rb b/actionpack/lib/action_dispatch/http/headers.rb
index 040b51e040..a3bb25f75a 100644
--- a/actionpack/lib/action_dispatch/http/headers.rb
+++ b/actionpack/lib/action_dispatch/http/headers.rb
@@ -14,17 +14,18 @@ module ActionDispatch
end
def [](header_name)
- if include?(header_name)
- super
- else
- super(env_name(header_name))
- end
+ super env_name(header_name)
+ end
+
+ def fetch(header_name, default=nil, &block)
+ super env_name(header_name), default, &block
end
private
- # Converts a HTTP header name to an environment variable name.
+ # Converts a HTTP header name to an environment variable name if it is
+ # not contained within the headers hash.
def env_name(header_name)
- @@env_cache[header_name]
+ include?(header_name) ? header_name : @@env_cache[header_name]
end
end
end
diff --git a/actionpack/lib/action_dispatch/http/mime_negotiation.rb b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
index 5c48a60469..0f98e84788 100644
--- a/actionpack/lib/action_dispatch/http/mime_negotiation.rb
+++ b/actionpack/lib/action_dispatch/http/mime_negotiation.rb
@@ -1,3 +1,5 @@
+require 'active_support/core_ext/module/attribute_accessors'
+
module ActionDispatch
module Http
module MimeNegotiation
@@ -78,6 +80,27 @@ module ActionDispatch
@env["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
+ # to set multiple, ordered formats, which is useful when you want to have a fallback.
+ #
+ # In this example, the :iphone format will be used if it's available, otherwise it'll fallback
+ # to the :html format.
+ #
+ # class ApplicationController < ActionController::Base
+ # before_filter :adjust_format_for_iphone_with_html_fallback
+ #
+ # private
+ # def adjust_format_for_iphone_with_html_fallback
+ # request.formats = [ :iphone, :html ] if request.env["HTTP_USER_AGENT"][/iPhone/]
+ # end
+ # end
+ def formats=(extensions)
+ parameters[:format] = extensions.first.to_s
+ @env["action_dispatch.request.formats"] = extensions.collect do |extension|
+ Mime::Type.lookup_by_extension(extension)
+ end
+ end
+
# Receives an array of mimes and return the first user sent mime that
# matches the order array.
#
diff --git a/actionpack/lib/action_dispatch/http/mime_type.rb b/actionpack/lib/action_dispatch/http/mime_type.rb
index 2152351703..3c1f1458ea 100644
--- a/actionpack/lib/action_dispatch/http/mime_type.rb
+++ b/actionpack/lib/action_dispatch/http/mime_type.rb
@@ -1,11 +1,10 @@
require 'set'
require 'active_support/core_ext/class/attribute_accessors'
-require 'active_support/core_ext/object/blank'
module Mime
class Mimes < Array
def symbols
- @symbols ||= map {|m| m.to_sym }
+ @symbols ||= map { |m| m.to_sym }
end
%w(<< concat shift unshift push pop []= clear compact! collect!
@@ -24,9 +23,16 @@ module Mime
EXTENSION_LOOKUP = {}
LOOKUP = Hash.new { |h, k| h[k] = Type.new(k) unless k.blank? }
- def self.[](type)
- return type if type.is_a?(Type)
- Type.lookup_by_extension(type.to_s)
+ class << self
+ def [](type)
+ return type if type.is_a?(Type)
+ Type.lookup_by_extension(type)
+ end
+
+ def fetch(type)
+ return type if type.is_a?(Type)
+ EXTENSION_LOOKUP.fetch(type.to_s) { |k| yield k }
+ end
end
# Encapsulates the notion of a mime type. Can be used at render time, for example, with:
@@ -38,7 +44,7 @@ module Mime
# respond_to do |format|
# format.html
# format.ics { render :text => post.to_ics, :mime_type => Mime::Type["text/calendar"] }
- # format.xml { render :xml => @people.to_xml }
+ # format.xml { render :xml => @people }
# end
# end
# end
@@ -53,36 +59,94 @@ module Mime
cattr_reader :browser_generated_types
attr_reader :symbol
+ @register_callbacks = []
+
# A simple helper class used in parsing the accept header
class AcceptItem #:nodoc:
- attr_accessor :order, :name, :q
+ attr_accessor :index, :name, :q
+ alias :to_s :name
- def initialize(order, name, q=nil)
- @order = order
- @name = name.strip
- q ||= 0.0 if @name == Mime::ALL # default wildcard match to end of list
+ def initialize(index, name, q = nil)
+ @index = index
+ @name = name
+ q ||= 0.0 if @name == Mime::ALL.to_s # default wildcard match to end of list
@q = ((q || 1.0).to_f * 100).to_i
end
- def to_s
- @name
- end
-
def <=>(item)
- result = item.q <=> q
- result = order <=> item.order if result == 0
+ result = item.q <=> @q
+ result = @index <=> item.index if result == 0
result
end
def ==(item)
- name == (item.respond_to?(:name) ? item.name : item)
+ @name == item.to_s
end
end
- class << self
+ class AcceptList < Array #:nodoc:
+ def assort!
+ sort!
+
+ # Take care of the broken text/xml entry by renaming or deleting it
+ if text_xml_idx && app_xml_idx
+ app_xml.q = [text_xml.q, app_xml.q].max # set the q value to the max of the two
+ exchange_xml_items if app_xml_idx > text_xml_idx # make sure app_xml is ahead of text_xml in the list
+ delete_at(text_xml_idx) # delete text_xml from the list
+ elsif text_xml_idx
+ text_xml.name = Mime::XML.to_s
+ end
+
+ # Look for more specific XML-based types and sort them ahead of app/xml
+ if app_xml_idx
+ idx = app_xml_idx
+
+ while idx < length
+ type = self[idx]
+ break if type.q < app_xml.q
+
+ if type.name.ends_with? '+xml'
+ self[app_xml_idx], self[idx] = self[idx], app_xml
+ @app_xml_idx = idx
+ end
+ idx += 1
+ end
+ end
+
+ map! { |i| Mime::Type.lookup(i.name) }.uniq!
+ to_a
+ end
+
+ private
+ def text_xml_idx
+ @text_xml_idx ||= index('text/xml')
+ end
+
+ def app_xml_idx
+ @app_xml_idx ||= index(Mime::XML.to_s)
+ end
+
+ def text_xml
+ self[text_xml_idx]
+ end
+
+ def app_xml
+ self[app_xml_idx]
+ end
+
+ def exchange_xml_items
+ self[app_xml_idx], self[text_xml_idx] = text_xml, app_xml
+ @app_xml_idx, @text_xml_idx = text_xml_idx, app_xml_idx
+ end
+ end
+ class << self
TRAILING_STAR_REGEXP = /(text|application)\/\*/
- Q_SEPARATOR_REGEXP = /;\s*q=/
+ PARAMETER_SEPARATOR_REGEXP = /;\s*\w+="?\w+"?/
+
+ def register_callback(&block)
+ @register_callbacks << block
+ end
def lookup(string)
LOOKUP[string]
@@ -99,91 +163,51 @@ module Mime
end
def register(string, symbol, mime_type_synonyms = [], extension_synonyms = [], skip_lookup = false)
- Mime.const_set(symbol.to_s.upcase, Type.new(string, symbol, mime_type_synonyms))
+ Mime.const_set(symbol.upcase, Type.new(string, symbol, mime_type_synonyms))
- SET << Mime.const_get(symbol.to_s.upcase)
+ new_mime = Mime.const_get(symbol.upcase)
+ SET << new_mime
([string] + mime_type_synonyms).each { |str| LOOKUP[str] = SET.last } unless skip_lookup
([symbol] + extension_synonyms).each { |ext| EXTENSION_LOOKUP[ext.to_s] = SET.last }
+
+ @register_callbacks.each do |callback|
+ callback.call(new_mime)
+ end
end
def parse(accept_header)
if accept_header !~ /,/
- accept_header = accept_header.split(Q_SEPARATOR_REGEXP).first
- if accept_header =~ TRAILING_STAR_REGEXP
- parse_data_with_trailing_star($1)
- else
- [Mime::Type.lookup(accept_header)]
- end
+ accept_header = accept_header.split(PARAMETER_SEPARATOR_REGEXP).first
+ parse_trailing_star(accept_header) || [Mime::Type.lookup(accept_header)]
else
- # keep track of creation order to keep the subsequent sort stable
- list, index = [], 0
- accept_header.split(/,/).each do |header|
- params, q = header.split(Q_SEPARATOR_REGEXP)
+ list, index = AcceptList.new, 0
+ accept_header.split(',').each do |header|
+ params, q = header.split(PARAMETER_SEPARATOR_REGEXP)
if params.present?
params.strip!
- if params =~ TRAILING_STAR_REGEXP
- parse_data_with_trailing_star($1).each do |m|
- list << AcceptItem.new(index, m.to_s, q)
- index += 1
- end
- else
- list << AcceptItem.new(index, params, q)
- index += 1
- end
- end
- end
- list.sort!
-
- # Take care of the broken text/xml entry by renaming or deleting it
- text_xml = list.index("text/xml")
- app_xml = list.index(Mime::XML.to_s)
-
- if text_xml && app_xml
- # set the q value to the max of the two
- list[app_xml].q = [list[text_xml].q, list[app_xml].q].max
-
- # make sure app_xml is ahead of text_xml in the list
- if app_xml > text_xml
- list[app_xml], list[text_xml] = list[text_xml], list[app_xml]
- app_xml, text_xml = text_xml, app_xml
- end
+ params = parse_trailing_star(params) || [params]
- # delete text_xml from the list
- list.delete_at(text_xml)
-
- elsif text_xml
- list[text_xml].name = Mime::XML.to_s
- end
-
- # Look for more specific XML-based types and sort them ahead of app/xml
-
- if app_xml
- idx = app_xml
- app_xml_type = list[app_xml]
-
- while(idx < list.length)
- type = list[idx]
- break if type.q < app_xml_type.q
- if type.name =~ /\+xml$/
- list[app_xml], list[idx] = list[idx], list[app_xml]
- app_xml = idx
+ params.each do |m|
+ list << AcceptItem.new(index, m.to_s, q)
+ index += 1
end
- idx += 1
end
end
-
- list.map! { |i| Mime::Type.lookup(i.name) }.uniq!
- list
+ list.assort!
end
end
- # input: 'text'
- # returned value: [Mime::JSON, Mime::XML, Mime::ICS, Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT]
+ def parse_trailing_star(accept_header)
+ parse_data_with_trailing_star($1) if accept_header =~ TRAILING_STAR_REGEXP
+ end
+
+ # For an input of <tt>'text'</tt>, returns <tt>[Mime::JSON, Mime::XML, Mime::ICS,
+ # Mime::HTML, Mime::CSS, Mime::CSV, Mime::JS, Mime::YAML, Mime::TEXT]</tt>.
#
- # input: 'application'
- # returned value: [Mime::HTML, Mime::JS, Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::URL_ENCODED_FORM]
+ # For an input of <tt>'application'</tt>, returns <tt>[Mime::HTML, Mime::JS,
+ # Mime::XML, Mime::YAML, Mime::ATOM, Mime::JSON, Mime::RSS, Mime::URL_ENCODED_FORM]</tt>.
def parse_data_with_trailing_star(input)
Mime::SET.select { |m| m =~ input }
end
@@ -192,9 +216,9 @@ module Mime
#
# Usage:
#
- # Mime::Type.unregister(:mobile)
+ # Mime::Type.unregister(:mobile)
def unregister(symbol)
- symbol = symbol.to_s.upcase
+ symbol = symbol.upcase
mime = Mime.const_get(symbol)
Mime.instance_eval { remove_const(symbol) }
@@ -258,18 +282,18 @@ module Mime
@@html_types.include?(to_sym) || @string =~ /html/
end
- def respond_to?(method, include_private = false) #:nodoc:
- super || method.to_s =~ /(\w+)\?$/
- end
-
private
def method_missing(method, *args)
- if method.to_s =~ /(\w+)\?$/
- $1.downcase.to_sym == to_sym
+ if method.to_s.ends_with? '?'
+ method[0..-2].downcase.to_sym == to_sym
else
super
end
end
+
+ def respond_to_missing?(method, include_private = false) #:nodoc:
+ method.to_s.ends_with? '?'
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/http/parameters.rb b/actionpack/lib/action_dispatch/http/parameters.rb
index d9b63faf5e..9a7b5bc8c7 100644
--- a/actionpack/lib/action_dispatch/http/parameters.rb
+++ b/actionpack/lib/action_dispatch/http/parameters.rb
@@ -4,6 +4,11 @@ require 'active_support/core_ext/hash/indifferent_access'
module ActionDispatch
module Http
module Parameters
+ def initialize(env)
+ super
+ @symbolized_path_params = nil
+ end
+
# Returns both GET and POST \parameters in a single hash.
def parameters
@env["action_dispatch.request.parameters"] ||= begin
@@ -35,6 +40,10 @@ module ActionDispatch
@env["action_dispatch.request.path_parameters"] ||= {}
end
+ def reset_parameters #:nodoc:
+ @env.delete("action_dispatch.request.parameters")
+ end
+
private
# TODO: Validate that the characters are UTF-8. If they aren't,
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 0a0ebe7fad..b8ebeb408f 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -17,7 +17,10 @@ module ActionDispatch
include ActionDispatch::Http::Upload
include ActionDispatch::Http::URL
- LOCALHOST = [/^127\.0\.0\.\d{1,3}$/, "::1", /^0:0:0:0:0:0:0:1(%.*)?$/].freeze
+ autoload :Session, 'action_dispatch/request/session'
+
+ LOCALHOST = Regexp.union [/^127\.0\.0\.\d{1,3}$/, /^::1$/, /^0:0:0:0:0:0:0:1(%.*)?$/]
+
ENV_METHODS = %w[ AUTH_TYPE GATEWAY_INTERFACE
PATH_TRANSLATED REMOTE_HOST
REMOTE_IDENT REMOTE_USER REMOTE_ADDR
@@ -35,6 +38,17 @@ module ActionDispatch
METHOD
end
+ def initialize(env)
+ super
+ @method = nil
+ @request_method = nil
+ @remote_ip = nil
+ @original_fullpath = nil
+ @fullpath = nil
+ @ip = nil
+ @uuid = nil
+ end
+
def key?(key)
@env.key?(key)
end
@@ -97,6 +111,12 @@ module ActionDispatch
HTTP_METHOD_LOOKUP[request_method] == :post
end
+ # Is this a PATCH request?
+ # Equivalent to <tt>request.request_method == :patch</tt>.
+ def patch?
+ HTTP_METHOD_LOOKUP[request_method] == :patch
+ end
+
# Is this a PUT request?
# Equivalent to <tt>request.request_method_symbol == :put</tt>.
def put?
@@ -110,9 +130,9 @@ module ActionDispatch
end
# Is this a HEAD request?
- # Equivalent to <tt>request.method_symbol == :head</tt>.
+ # Equivalent to <tt>request.request_method_symbol == :head</tt>.
def head?
- HTTP_METHOD_LOOKUP[method] == :head
+ HTTP_METHOD_LOOKUP[request_method] == :head
end
# Provides access to the request's HTTP headers, for example:
@@ -207,32 +227,42 @@ module ActionDispatch
# TODO This should be broken apart into AD::Request::Session and probably
# be included by the session middleware.
def reset_session
- session.destroy if session && session.respond_to?(:destroy)
- self.session = {}
+ if session && session.respond_to?(:destroy)
+ session.destroy
+ else
+ self.session = {}
+ end
@env['action_dispatch.request.flash_hash'] = nil
end
def session=(session) #:nodoc:
- @env['rack.session'] = session
+ Session.set @env, session
end
def session_options=(options)
- @env['rack.session.options'] = options
+ Session::Options.set @env, options
end
# Override Rack's GET method to support indifferent access
def GET
- @env["action_dispatch.request.query_parameters"] ||= (normalize_parameters(super) || {})
+ begin
+ @env["action_dispatch.request.query_parameters"] ||= (normalize_parameters(super) || {})
+ rescue TypeError => e
+ raise ActionController::BadRequest, "Invalid query parameters: #{e.message}"
+ end
end
alias :query_parameters :GET
# Override Rack's POST method to support indifferent access
def POST
- @env["action_dispatch.request.request_parameters"] ||= (normalize_parameters(super) || {})
+ begin
+ @env["action_dispatch.request.request_parameters"] ||= (normalize_parameters(super) || {})
+ rescue TypeError => e
+ raise ActionController::BadRequest, "Invalid request parameters: #{e.message}"
+ end
end
alias :request_parameters :POST
-
# Returns the authorization header regardless of whether it was specified directly or through one of the
# proxy alternatives.
def authorization
@@ -244,7 +274,28 @@ module ActionDispatch
# True if the request came from localhost, 127.0.0.1.
def local?
- LOCALHOST.any? { |local_ip| local_ip === remote_addr && local_ip === remote_ip }
+ LOCALHOST =~ remote_addr && LOCALHOST =~ remote_ip
+ end
+
+ protected
+
+ # Remove nils from the params hash
+ def deep_munge(hash)
+ hash.each_value do |v|
+ case v
+ when Array
+ v.grep(Hash) { |x| deep_munge(x) }
+ v.compact!
+ when Hash
+ deep_munge(v)
+ end
+ end
+
+ hash
+ end
+
+ def parse_query(qs)
+ deep_munge(super)
end
private
diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb
index 84732085f0..11b7534ea4 100644
--- a/actionpack/lib/action_dispatch/http/response.rb
+++ b/actionpack/lib/action_dispatch/http/response.rb
@@ -1,7 +1,6 @@
require 'digest/md5'
-require 'active_support/core_ext/module/delegation'
-require 'active_support/core_ext/object/blank'
require 'active_support/core_ext/class/attribute_accessors'
+require 'monitor'
module ActionDispatch # :nodoc:
# Represents an HTTP response generated by a controller action. Use it to
@@ -29,7 +28,7 @@ module ActionDispatch # :nodoc:
# class DemoControllerTest < ActionDispatch::IntegrationTest
# def test_print_root_path_to_console
# get('/')
- # puts @response.body
+ # puts response.body
# end
# end
class Response
@@ -41,7 +40,7 @@ module ActionDispatch # :nodoc:
alias_method :headers, :header
delegate :[], :[]=, :to => :@header
- delegate :each, :to => :@body
+ delegate :each, :to => :@stream
# Sets the HTTP response's content MIME type. For example, in the controller
# you could write this:
@@ -51,22 +50,63 @@ module ActionDispatch # :nodoc:
# If a character set has been defined for this response (see charset=) then
# the character set information will also be included in the content type
# information.
- attr_accessor :charset, :content_type
+ attr_accessor :charset
+ attr_reader :content_type
CONTENT_TYPE = "Content-Type".freeze
SET_COOKIE = "Set-Cookie".freeze
LOCATION = "Location".freeze
-
+
cattr_accessor(:default_charset) { "utf-8" }
+ cattr_accessor(:default_headers)
include Rack::Response::Helpers
include ActionDispatch::Http::Cache::Response
+ include MonitorMixin
+
+ class Buffer # :nodoc:
+ def initialize(response, buf)
+ @response = response
+ @buf = buf
+ @closed = false
+ end
+
+ def write(string)
+ raise IOError, "closed stream" if closed?
+
+ @response.commit!
+ @buf.push string
+ end
+
+ def each(&block)
+ @buf.each(&block)
+ end
+
+ def close
+ @response.commit!
+ @closed = true
+ end
+
+ def closed?
+ @closed
+ end
+ end
+
+ attr_reader :stream
def initialize(status = 200, header = {}, body = [])
+ super()
+
+ header = merge_default_headers(header, self.class.default_headers)
+
self.body, self.header, self.status = body, header, status
@sending_file = false
- @blank = false
+ @blank = false
+ @cv = new_cond
+ @committed = false
+ @content_type = nil
+ @charset = nil
if content_type = self[CONTENT_TYPE]
type, charset = content_type.split(/;\s*charset=/)
@@ -79,10 +119,31 @@ module ActionDispatch # :nodoc:
yield self if block_given?
end
+ def await_commit
+ synchronize do
+ @cv.wait_until { @committed }
+ end
+ end
+
+ def commit!
+ synchronize do
+ @committed = true
+ @cv.broadcast
+ end
+ end
+
+ def committed?
+ @committed
+ end
+
def status=(status)
@status = Rack::Utils.status_code(status)
end
+ def content_type=(content_type)
+ @content_type = content_type.to_s
+ end
+
# The response code of the request
def response_code
@status
@@ -100,14 +161,14 @@ module ActionDispatch # :nodoc:
def respond_to?(method)
if method.to_sym == :to_path
- @body.respond_to?(:to_path)
+ stream.respond_to?(:to_path)
else
super
end
end
def to_path
- @body.to_path
+ stream.to_path
end
def body
@@ -121,11 +182,17 @@ module ActionDispatch # :nodoc:
def body=(body)
@blank = true if body == EMPTY
- @body = body.respond_to?(:each) ? body : [body]
+ if body.respond_to?(:to_path)
+ @stream = body
+ else
+ @stream = build_buffer self, munge_body_object(body)
+ end
end
def body_parts
- @body
+ parts = []
+ @stream.each { |x| parts << x }
+ parts
end
def set_cookie(key, value)
@@ -146,21 +213,11 @@ module ActionDispatch # :nodoc:
end
def close
- @body.close if @body.respond_to?(:close)
+ stream.close if stream.respond_to?(:close)
end
def to_a
- assign_default_content_type_and_charset!
- handle_conditional_get!
-
- @header[SET_COOKIE] = @header[SET_COOKIE].join("\n") if @header[SET_COOKIE].respond_to?(:join)
-
- if [204, 304].include?(@status)
- @header.delete CONTENT_TYPE
- [@status, @header, []]
- else
- [@status, @header, self]
- end
+ rack_response @status, @header.to_hash
end
alias prepare! to_a
alias to_ary to_a # For implicit splat on 1.9.2
@@ -184,7 +241,21 @@ module ActionDispatch # :nodoc:
private
- def assign_default_content_type_and_charset!
+ def merge_default_headers(original, default)
+ return original unless default.respond_to?(:merge)
+
+ default.merge(original)
+ end
+
+ def build_buffer(response, body)
+ Buffer.new response, body
+ end
+
+ def munge_body_object(body)
+ body.respond_to?(:each) ? body : [body]
+ end
+
+ def assign_default_content_type_and_charset!(headers)
return if headers[CONTENT_TYPE].present?
@content_type ||= Mime::HTML
@@ -195,5 +266,19 @@ module ActionDispatch # :nodoc:
headers[CONTENT_TYPE] = type
end
+
+ def rack_response(status, header)
+ assign_default_content_type_and_charset!(header)
+ handle_conditional_get!
+
+ header[SET_COOKIE] = header[SET_COOKIE].join("\n") if header[SET_COOKIE].respond_to?(:join)
+
+ if [204, 304].include?(@status)
+ header.delete CONTENT_TYPE
+ [status, header, []]
+ else
+ [status, header, self]
+ end
+ end
end
end
diff --git a/actionpack/lib/action_dispatch/http/upload.rb b/actionpack/lib/action_dispatch/http/upload.rb
index 5ab99d1061..ce8c2729e9 100644
--- a/actionpack/lib/action_dispatch/http/upload.rb
+++ b/actionpack/lib/action_dispatch/http/upload.rb
@@ -4,11 +4,12 @@ module ActionDispatch
attr_accessor :original_filename, :content_type, :tempfile, :headers
def initialize(hash)
+ @tempfile = hash[:tempfile]
+ raise(ArgumentError, ':tempfile is required') unless @tempfile
+
@original_filename = encode_filename(hash[:filename])
@content_type = hash[:type]
@headers = hash[:head]
- @tempfile = hash[:tempfile]
- raise(ArgumentError, ':tempfile is required') unless @tempfile
end
def read(*args)
@@ -16,18 +17,15 @@ module ActionDispatch
end
# Delegate these methods to the tempfile.
- [:open, :path, :rewind, :size].each do |method|
+ [:open, :path, :rewind, :size, :eof?].each do |method|
class_eval "def #{method}; @tempfile.#{method}; end"
end
-
+
private
+
def encode_filename(filename)
# Encode the filename in the utf8 encoding, unless it is nil
- if filename
- filename.force_encoding("UTF-8").encode!
- else
- filename
- end
+ filename.force_encoding("UTF-8").encode! if filename
end
end
diff --git a/actionpack/lib/action_dispatch/http/url.rb b/actionpack/lib/action_dispatch/http/url.rb
index 80ffbe575b..8aa02ec482 100644
--- a/actionpack/lib/action_dispatch/http/url.rb
+++ b/actionpack/lib/action_dispatch/http/url.rb
@@ -23,36 +23,43 @@ module ActionDispatch
end
def url_for(options = {})
+ path = ""
+ path << options.delete(:script_name).to_s.chomp("/")
+ path << options.delete(:path).to_s
+
+ params = options[:params] || {}
+ params.reject! {|k,v| v.to_param.nil? }
+
+ result = build_host_url(options)
+
+ result << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
+ result << "?#{params.to_query}" unless params.empty?
+ result << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
+ result
+ end
+
+ private
+
+ def build_host_url(options)
if options[:host].blank? && options[:only_path].blank?
raise ArgumentError, 'Missing host to link to! Please provide the :host parameter, set default_url_options[:host], or set :only_path to true'
end
- rewritten_url = ""
+ result = ""
unless options[:only_path]
unless options[:protocol] == false
- rewritten_url << (options[:protocol] || "http")
- rewritten_url << ":" unless rewritten_url.match(%r{:|//})
+ result << (options[:protocol] || "http")
+ result << ":" unless result.match(%r{:|//})
end
- rewritten_url << "//" unless rewritten_url.match("//")
- rewritten_url << rewrite_authentication(options)
- rewritten_url << host_or_subdomain_and_domain(options)
- rewritten_url << ":#{options.delete(:port)}" if options[:port]
+ result << "//" unless result.match("//")
+ result << rewrite_authentication(options)
+ result << host_or_subdomain_and_domain(options)
+ result << ":#{options.delete(:port)}" if options[:port]
end
-
- path = options.delete(:path) || ''
-
- params = options[:params] || {}
- params.reject! {|k,v| v.to_param.nil? }
-
- rewritten_url << (options[:trailing_slash] ? path.sub(/\?|\z/) { "/" + $& } : path)
- rewritten_url << "?#{params.to_query}" unless params.empty?
- rewritten_url << "##{Journey::Router::Utils.escape_fragment(options[:anchor].to_param.to_s)}" if options[:anchor]
- rewritten_url
+ result
end
- private
-
def named_host?(host)
host && IP_HOST_REGEXP !~ host
end
@@ -80,6 +87,12 @@ module ActionDispatch
end
end
+ def initialize(env)
+ super
+ @protocol = nil
+ @port = nil
+ end
+
# Returns the complete URL used for this request.
def url
protocol + host_with_port + fullpath