aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
authorPratik Naik <pratiknaik@gmail.com>2008-06-02 12:29:28 +0100
committerPratik Naik <pratiknaik@gmail.com>2008-06-02 12:29:28 +0100
commit719aa13b0781d662673dba2225bc707fc4182b5e (patch)
tree057cabf17152a1ab0ccc749dd361eeaf754c38de /actionpack
parentad3c97cea6dd0714652db7d9b5b4d06b81e5c035 (diff)
parenta980eb8c7734f14109d8c2a02a88dafdf682e0dc (diff)
downloadrails-719aa13b0781d662673dba2225bc707fc4182b5e.tar.gz
rails-719aa13b0781d662673dba2225bc707fc4182b5e.tar.bz2
rails-719aa13b0781d662673dba2225bc707fc4182b5e.zip
Merge commit 'mainstream/master'
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/CHANGELOG7
-rw-r--r--actionpack/Rakefile2
-rwxr-xr-xactionpack/lib/action_controller.rb1
-rw-r--r--actionpack/lib/action_controller/cgi_ext/cookie.rb6
-rw-r--r--actionpack/lib/action_controller/dispatcher.rb8
-rw-r--r--actionpack/lib/action_controller/rack_process.rb321
-rwxr-xr-xactionpack/lib/action_controller/request_profiler.rb25
-rw-r--r--actionpack/lib/action_pack/version.rb4
-rw-r--r--actionpack/lib/action_view/compiled_templates.rb69
-rwxr-xr-xactionpack/test/controller/cgi_test.rb33
-rw-r--r--actionpack/test/controller/rack_test.rb150
-rw-r--r--actionpack/test/template/compiled_templates_test.rb192
12 files changed, 543 insertions, 275 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 5c4bfbf3c9..9622029362 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,6 +1,9 @@
-* InstanceTag#default_time_from_options overflows to DateTime [Geoff Buesing]
+* Added Rack processor [Ezra Zygmuntowicz, Josh Peek]
+
-*2.1.0 RC1 (May 11th, 2008)*
+*2.1.0 (May 31st, 2008)*
+
+* InstanceTag#default_time_from_options overflows to DateTime [Geoff Buesing]
* Fixed that forgery protection can be used without session tracking (Peter Jones) [#139]
diff --git a/actionpack/Rakefile b/actionpack/Rakefile
index 0147a5c1e8..b37f756c1e 100644
--- a/actionpack/Rakefile
+++ b/actionpack/Rakefile
@@ -76,7 +76,7 @@ spec = Gem::Specification.new do |s|
s.has_rdoc = true
s.requirements << 'none'
- s.add_dependency('activesupport', '= 2.0.991' + PKG_BUILD)
+ s.add_dependency('activesupport', '= 2.1.0' + PKG_BUILD)
s.require_path = 'lib'
s.autorequire = 'action_controller'
diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb
index 810a5fb9b5..3c4a339d50 100755
--- a/actionpack/lib/action_controller.rb
+++ b/actionpack/lib/action_controller.rb
@@ -53,6 +53,7 @@ require 'action_controller/streaming'
require 'action_controller/session_management'
require 'action_controller/http_authentication'
require 'action_controller/components'
+require 'action_controller/rack_process'
require 'action_controller/record_identifier'
require 'action_controller/request_forgery_protection'
require 'action_controller/headers'
diff --git a/actionpack/lib/action_controller/cgi_ext/cookie.rb b/actionpack/lib/action_controller/cgi_ext/cookie.rb
index ef033fb4f3..009ddd1c64 100644
--- a/actionpack/lib/action_controller/cgi_ext/cookie.rb
+++ b/actionpack/lib/action_controller/cgi_ext/cookie.rb
@@ -78,6 +78,12 @@ class CGI #:nodoc:
buf
end
+ # FIXME: work around broken 1.8.7 DelegateClass#respond_to?
+ def respond_to?(method, include_private = false)
+ return true if super(method)
+ return __getobj__.respond_to?(method, include_private)
+ end
+
# Parses a raw cookie string into a hash of <tt>cookie-name => cookie-object</tt>
# pairs.
#
diff --git a/actionpack/lib/action_controller/dispatcher.rb b/actionpack/lib/action_controller/dispatcher.rb
index 6e1e7a261f..b40f1ba9be 100644
--- a/actionpack/lib/action_controller/dispatcher.rb
+++ b/actionpack/lib/action_controller/dispatcher.rb
@@ -96,7 +96,7 @@ module ActionController
include ActiveSupport::Callbacks
define_callbacks :prepare_dispatch, :before_dispatch, :after_dispatch
- def initialize(output, request = nil, response = nil)
+ def initialize(output = $stdout, request = nil, response = nil)
@output, @request, @response = output, request, response
end
@@ -123,6 +123,12 @@ module ActionController
failsafe_rescue exception
end
+ def call(env)
+ @request = RackRequest.new(env)
+ @response = RackResponse.new
+ dispatch
+ end
+
def reload_application
# Run prepare callbacks before every request in development mode
run_callbacks :prepare_dispatch
diff --git a/actionpack/lib/action_controller/rack_process.rb b/actionpack/lib/action_controller/rack_process.rb
new file mode 100644
index 0000000000..16625519b6
--- /dev/null
+++ b/actionpack/lib/action_controller/rack_process.rb
@@ -0,0 +1,321 @@
+require 'action_controller/cgi_ext'
+require 'action_controller/session/cookie_store'
+
+module ActionController #:nodoc:
+ class RackRequest < AbstractRequest #:nodoc:
+ attr_accessor :env, :session_options
+
+ class SessionFixationAttempt < StandardError #:nodoc:
+ end
+
+ DEFAULT_SESSION_OPTIONS = {
+ :database_manager => CGI::Session::CookieStore, # store data in cookie
+ :prefix => "ruby_sess.", # prefix session file names
+ :session_path => "/", # available to all paths in app
+ :session_key => "_session_id",
+ :cookie_only => true
+ } unless const_defined?(:DEFAULT_SESSION_OPTIONS)
+
+ def initialize(env, session_options = DEFAULT_SESSION_OPTIONS)
+ @session_options = session_options
+ @env = env
+ @cgi = CGIWrapper.new(self)
+ super()
+ end
+
+ # The request body is an IO input stream. If the RAW_POST_DATA environment
+ # variable is already set, wrap it in a StringIO.
+ def body
+ if raw_post = env['RAW_POST_DATA']
+ StringIO.new(raw_post)
+ else
+ @env['rack.input']
+ end
+ end
+
+ def key?(key)
+ @env.key? key
+ end
+
+ def query_parameters
+ @query_parameters ||= self.class.parse_query_parameters(query_string)
+ end
+
+ def request_parameters
+ @request_parameters ||= parse_formatted_request_parameters
+ end
+
+ def cookies
+ return {} unless @env["HTTP_COOKIE"]
+
+ if @env["rack.request.cookie_string"] == @env["HTTP_COOKIE"]
+ @env["rack.request.cookie_hash"]
+ else
+ @env["rack.request.cookie_string"] = @env["HTTP_COOKIE"]
+ # According to RFC 2109:
+ # If multiple cookies satisfy the criteria above, they are ordered in
+ # the Cookie header such that those with more specific Path attributes
+ # precede those with less specific. Ordering with respect to other
+ # attributes (e.g., Domain) is unspecified.
+ @env["rack.request.cookie_hash"] =
+ parse_query(@env["rack.request.cookie_string"], ';,').inject({}) { |h, (k,v)|
+ h[k] = Array === v ? v.first : v
+ h
+ }
+ end
+ end
+
+ def host_with_port_without_standard_port_handling
+ if forwarded = @env["HTTP_X_FORWARDED_HOST"]
+ forwarded.split(/,\s?/).last
+ elsif http_host = @env['HTTP_HOST']
+ http_host
+ elsif server_name = @env['SERVER_NAME']
+ server_name
+ else
+ "#{env['SERVER_ADDR']}:#{env['SERVER_PORT']}"
+ end
+ end
+
+ def host
+ host_with_port_without_standard_port_handling.sub(/:\d+$/, '')
+ end
+
+ def port
+ if host_with_port_without_standard_port_handling =~ /:(\d+)$/
+ $1.to_i
+ else
+ standard_port
+ end
+ end
+
+ def remote_addr
+ @env['REMOTE_ADDR']
+ end
+
+ def session
+ unless defined?(@session)
+ if @session_options == false
+ @session = Hash.new
+ else
+ stale_session_check! do
+ if cookie_only? && query_parameters[session_options_with_string_keys['session_key']]
+ raise SessionFixationAttempt
+ end
+ case value = session_options_with_string_keys['new_session']
+ when true
+ @session = new_session
+ when false
+ begin
+ @session = CGI::Session.new(@cgi, session_options_with_string_keys)
+ # CGI::Session raises ArgumentError if 'new_session' == false
+ # and no session cookie or query param is present.
+ rescue ArgumentError
+ @session = Hash.new
+ end
+ when nil
+ @session = CGI::Session.new(@cgi, session_options_with_string_keys)
+ else
+ raise ArgumentError, "Invalid new_session option: #{value}"
+ end
+ @session['__valid_session']
+ end
+ end
+ end
+ @session
+ end
+
+ def reset_session
+ @session.delete if defined?(@session) && @session.is_a?(CGI::Session)
+ @session = new_session
+ end
+
+ private
+ # Delete an old session if it exists then create a new one.
+ def new_session
+ if @session_options == false
+ Hash.new
+ else
+ CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => false)).delete rescue nil
+ CGI::Session.new(@cgi, session_options_with_string_keys.merge("new_session" => true))
+ end
+ end
+
+ def cookie_only?
+ session_options_with_string_keys['cookie_only']
+ end
+
+ def stale_session_check!
+ yield
+ rescue ArgumentError => argument_error
+ if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
+ begin
+ # Note that the regexp does not allow $1 to end with a ':'
+ $1.constantize
+ rescue LoadError, NameError => const_error
+ raise ActionController::SessionRestoreError, <<-end_msg
+Session contains objects whose class definition isn\'t available.
+Remember to require the classes for all objects kept in the session.
+(Original exception: #{const_error.message} [#{const_error.class}])
+end_msg
+ end
+
+ retry
+ else
+ raise
+ end
+ end
+
+ def session_options_with_string_keys
+ @session_options_with_string_keys ||= DEFAULT_SESSION_OPTIONS.merge(@session_options).stringify_keys
+ end
+
+ # From Rack::Utils
+ def parse_query(qs, d = '&;')
+ params = {}
+ (qs || '').split(/[#{d}] */n).inject(params) { |h,p|
+ k, v = unescape(p).split('=',2)
+ if cur = params[k]
+ if cur.class == Array
+ params[k] << v
+ else
+ params[k] = [cur, v]
+ end
+ else
+ params[k] = v
+ end
+ }
+
+ return params
+ end
+
+ def unescape(s)
+ s.tr('+', ' ').gsub(/((?:%[0-9a-fA-F]{2})+)/n){
+ [$1.delete('%')].pack('H*')
+ }
+ end
+ end
+
+ class RackResponse < AbstractResponse #:nodoc:
+ attr_accessor :status
+
+ def initialize
+ @writer = lambda { |x| @body << x }
+ @block = nil
+ super()
+ end
+
+ def out(output = $stdout, &block)
+ @block = block
+ normalize_headers(@headers)
+ if [204, 304].include?(@status.to_i)
+ @headers.delete "Content-Type"
+ [status.to_i, @headers.to_hash, []]
+ else
+ [status.to_i, @headers.to_hash, self]
+ end
+ end
+ alias to_a out
+
+ def each(&callback)
+ if @body.respond_to?(:call)
+ @writer = lambda { |x| callback.call(x) }
+ @body.call(self, self)
+ else
+ @body.each(&callback)
+ end
+
+ @writer = callback
+ @block.call(self) if @block
+ end
+
+ def write(str)
+ @writer.call str.to_s
+ str
+ end
+
+ def close
+ @body.close if @body.respond_to?(:close)
+ end
+
+ def empty?
+ @block == nil && @body.empty?
+ end
+
+ private
+ def normalize_headers(options = "text/html")
+ if options.is_a?(String)
+ headers['Content-Type'] = options unless headers['Content-Type']
+ else
+ headers['Content-Length'] = options.delete('Content-Length').to_s if options['Content-Length']
+
+ headers['Content-Type'] = options.delete('type') || "text/html"
+ headers['Content-Type'] += "; charset=" + options.delete('charset') if options['charset']
+
+ headers['Content-Language'] = options.delete('language') if options['language']
+ headers['Expires'] = options.delete('expires') if options['expires']
+
+ @status = options.delete('Status') if options['Status']
+ @status ||= 200
+ # Convert 'cookie' header to 'Set-Cookie' headers.
+ # Because Set-Cookie header can appear more the once in the response body,
+ # we store it in a line break seperated string that will be translated to
+ # multiple Set-Cookie header by the handler.
+ if cookie = options.delete('cookie')
+ cookies = []
+
+ case cookie
+ when Array then cookie.each { |c| cookies << c.to_s }
+ when Hash then cookie.each { |_, c| cookies << c.to_s }
+ else cookies << cookie.to_s
+ end
+
+ @output_cookies.each { |c| cookies << c.to_s } if @output_cookies
+
+ headers['Set-Cookie'] = [headers['Set-Cookie'], cookies].compact.join("\n")
+ end
+
+ options.each { |k,v| headers[k] = v }
+ end
+
+ ""
+ end
+ end
+
+ class CGIWrapper < ::CGI
+ def initialize(request, *args)
+ @request = request
+ @args = *args
+ @input = request.body
+
+ super *args
+ end
+
+ def params
+ @params ||= @request.params
+ end
+
+ def cookies
+ @request.cookies
+ end
+
+ def query_string
+ @request.query_string
+ end
+
+ # Used to wrap the normal args variable used inside CGI.
+ def args
+ @args
+ end
+
+ # Used to wrap the normal env_table variable used inside CGI.
+ def env_table
+ @request.env
+ end
+
+ # Used to wrap the normal stdinput variable used inside CGI.
+ def stdinput
+ @input
+ end
+ end
+end
diff --git a/actionpack/lib/action_controller/request_profiler.rb b/actionpack/lib/action_controller/request_profiler.rb
index 8a18d194bd..a6471d0c08 100755
--- a/actionpack/lib/action_controller/request_profiler.rb
+++ b/actionpack/lib/action_controller/request_profiler.rb
@@ -17,13 +17,13 @@ module ActionController
reset!
end
- def benchmark(n)
+ def benchmark(n, profiling = false)
@quiet = true
print ' '
result = Benchmark.realtime do
n.times do |i|
- run
+ run(profiling)
print_progress(i)
end
end
@@ -43,8 +43,15 @@ module ActionController
script = File.read(script_path)
source = <<-end_source
- def run
- #{script}
+ def run(profiling = false)
+ if profiling
+ RubyProf.resume do
+ #{script}
+ end
+ else
+ #{script}
+ end
+
old_request_count = request_count
reset!
self.request_count = old_request_count
@@ -91,21 +98,22 @@ module ActionController
def profile(sandbox)
load_ruby_prof
- results = RubyProf.profile { benchmark(sandbox) }
+ benchmark(sandbox, true)
+ results = RubyProf.stop
show_profile_results results
results
end
- def benchmark(sandbox)
+ def benchmark(sandbox, profiling = false)
sandbox.request_count = 0
- elapsed = sandbox.benchmark(options[:n]).to_f
+ elapsed = sandbox.benchmark(options[:n], profiling).to_f
count = sandbox.request_count.to_i
puts '%.2f sec, %d requests, %d req/sec' % [elapsed, count, count / elapsed]
end
def warmup(sandbox)
- Benchmark.realtime { sandbox.run }
+ Benchmark.realtime { sandbox.run(false) }
end
def default_options
@@ -136,6 +144,7 @@ module ActionController
protected
def load_ruby_prof
begin
+ gem 'ruby-prof', '>= 0.6.1'
require 'ruby-prof'
if mode = options[:measure]
RubyProf.measure_mode = RubyProf.const_get(mode.upcase)
diff --git a/actionpack/lib/action_pack/version.rb b/actionpack/lib/action_pack/version.rb
index 70fc1ced8c..c67654d9a8 100644
--- a/actionpack/lib/action_pack/version.rb
+++ b/actionpack/lib/action_pack/version.rb
@@ -1,8 +1,8 @@
module ActionPack #:nodoc:
module VERSION #:nodoc:
MAJOR = 2
- MINOR = 0
- TINY = 991
+ MINOR = 1
+ TINY = 0
STRING = [MAJOR, MINOR, TINY].join('.')
end
diff --git a/actionpack/lib/action_view/compiled_templates.rb b/actionpack/lib/action_view/compiled_templates.rb
deleted file mode 100644
index 5a286432e3..0000000000
--- a/actionpack/lib/action_view/compiled_templates.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-module ActionView
-
- # CompiledTemplates modules hold methods that have been compiled.
- # Templates are compiled into these methods so that they do not need to be
- # read and parsed for each request.
- #
- # Each template may be compiled into one or more methods. Each method accepts a given
- # set of parameters which is used to implement local assigns passing.
- #
- # To use a compiled template module, create a new instance and include it into the class
- # in which you want the template to be rendered.
- class CompiledTemplates < Module
- attr_reader :method_names
-
- def initialize
- @method_names = Hash.new do |hash, key|
- hash[key] = "__compiled_method_#{(hash.length + 1)}"
- end
- @mtimes = {}
- end
-
- # Return the full key for the given identifier and argument names
- def full_key(identifier, arg_names)
- [identifier, arg_names]
- end
-
- # Return the selector for this method or nil if it has not been compiled
- def selector(identifier, arg_names)
- key = full_key(identifier, arg_names)
- method_names.key?(key) ? method_names[key] : nil
- end
- alias :compiled? :selector
-
- # Return the time at which the method for the given identifier and argument names was compiled.
- def mtime(identifier, arg_names)
- @mtimes[full_key(identifier, arg_names)]
- end
-
- # Compile the provided source code for the given argument names and with the given initial line number.
- # The identifier should be unique to this source.
- #
- # The file_name, if provided will appear in backtraces. If not provided, the file_name defaults
- # to the identifier.
- #
- # This method will return the selector for the compiled version of this method.
- def compile_source(identifier, arg_names, source, initial_line_number = 0, file_name = nil)
- file_name ||= identifier
- name = method_names[full_key(identifier, arg_names)]
- arg_desc = arg_names.empty? ? '' : "(#{arg_names * ', '})"
- fake_file_name = "#{file_name}#{arg_desc}" # Include the arguments for this version (for now)
-
- method_def = wrap_source(name, arg_names, source)
-
- begin
- module_eval(method_def, fake_file_name, initial_line_number)
- @mtimes[full_key(identifier, arg_names)] = Time.now
- rescue Exception => e # errors from compiled source
- e.blame_file! identifier
- raise
- end
- name
- end
-
- # Wrap the provided source in a def ... end block.
- def wrap_source(name, arg_names, source)
- "def #{name}(#{arg_names * ', '})\n#{source}\nend"
- end
- end
-end
diff --git a/actionpack/test/controller/cgi_test.rb b/actionpack/test/controller/cgi_test.rb
index 87f72fda77..f0f3a4b826 100755
--- a/actionpack/test/controller/cgi_test.rb
+++ b/actionpack/test/controller/cgi_test.rb
@@ -114,3 +114,36 @@ class CgiRequestNeedsRewoundTest < BaseCgiTest
assert_equal 0, request.body.pos
end
end
+
+class CgiResponseTest < BaseCgiTest
+ def setup
+ super
+ @fake_cgi.expects(:header).returns("HTTP/1.0 200 OK\nContent-Type: text/html\n")
+ @response = ActionController::CgiResponse.new(@fake_cgi)
+ @output = StringIO.new('')
+ end
+
+ def test_simple_output
+ @response.body = "Hello, World!"
+
+ @response.out(@output)
+ assert_equal "HTTP/1.0 200 OK\nContent-Type: text/html\nHello, World!", @output.string
+ end
+
+ def test_head_request
+ @fake_cgi.env_table['REQUEST_METHOD'] = 'HEAD'
+ @response.body = "Hello, World!"
+
+ @response.out(@output)
+ assert_equal "HTTP/1.0 200 OK\nContent-Type: text/html\n", @output.string
+ end
+
+ def test_streaming_block
+ @response.body = Proc.new do |response, output|
+ 5.times { |n| output.write(n) }
+ end
+
+ @response.out(@output)
+ assert_equal "HTTP/1.0 200 OK\nContent-Type: text/html\n01234", @output.string
+ end
+end
diff --git a/actionpack/test/controller/rack_test.rb b/actionpack/test/controller/rack_test.rb
new file mode 100644
index 0000000000..cd4151783e
--- /dev/null
+++ b/actionpack/test/controller/rack_test.rb
@@ -0,0 +1,150 @@
+require 'abstract_unit'
+require 'action_controller/rack_process'
+
+class BaseRackTest < Test::Unit::TestCase
+ def setup
+ @env = {"HTTP_MAX_FORWARDS"=>"10", "SERVER_NAME"=>"glu.ttono.us:8007", "FCGI_ROLE"=>"RESPONDER", "HTTP_X_FORWARDED_HOST"=>"glu.ttono.us", "HTTP_ACCEPT_ENCODING"=>"gzip, deflate", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", "PATH_INFO"=>"", "HTTP_ACCEPT_LANGUAGE"=>"en", "HTTP_HOST"=>"glu.ttono.us:8007", "SERVER_PROTOCOL"=>"HTTP/1.1", "REDIRECT_URI"=>"/dispatch.fcgi", "SCRIPT_NAME"=>"/dispatch.fcgi", "SERVER_ADDR"=>"207.7.108.53", "REMOTE_ADDR"=>"207.7.108.53", "SERVER_SOFTWARE"=>"lighttpd/1.4.5", "HTTP_COOKIE"=>"_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes", "HTTP_X_FORWARDED_SERVER"=>"glu.ttono.us", "REQUEST_URI"=>"/admin", "DOCUMENT_ROOT"=>"/home/kevinc/sites/typo/public", "SERVER_PORT"=>"8007", "QUERY_STRING"=>"", "REMOTE_PORT"=>"63137", "GATEWAY_INTERFACE"=>"CGI/1.1", "HTTP_X_FORWARDED_FOR"=>"65.88.180.234", "HTTP_ACCEPT"=>"*/*", "SCRIPT_FILENAME"=>"/home/kevinc/sites/typo/public/dispatch.fcgi", "REDIRECT_STATUS"=>"200", "REQUEST_METHOD"=>"GET"}
+ # some Nokia phone browsers omit the space after the semicolon separator.
+ # some developers have grown accustomed to using comma in cookie values.
+ @alt_cookie_fmt_request_hash = {"HTTP_COOKIE"=>"_session_id=c84ace847,96670c052c6ceb2451fb0f2;is_admin=yes"}
+ @request = ActionController::RackRequest.new(@env)
+ end
+
+ def default_test; end
+end
+
+
+class RackRequestTest < BaseRackTest
+ def test_proxy_request
+ assert_equal 'glu.ttono.us', @request.host_with_port
+ end
+
+ def test_http_host
+ @env.delete "HTTP_X_FORWARDED_HOST"
+ @env['HTTP_HOST'] = "rubyonrails.org:8080"
+ assert_equal "rubyonrails.org:8080", @request.host_with_port
+
+ @env['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org"
+ assert_equal "www.secondhost.org", @request.host
+ end
+
+ def test_http_host_with_default_port_overrides_server_port
+ @env.delete "HTTP_X_FORWARDED_HOST"
+ @env['HTTP_HOST'] = "rubyonrails.org"
+ assert_equal "rubyonrails.org", @request.host_with_port
+ end
+
+ def test_host_with_port_defaults_to_server_name_if_no_host_headers
+ @env.delete "HTTP_X_FORWARDED_HOST"
+ @env.delete "HTTP_HOST"
+ assert_equal "glu.ttono.us:8007", @request.host_with_port
+ end
+
+ def test_host_with_port_falls_back_to_server_addr_if_necessary
+ @env.delete "HTTP_X_FORWARDED_HOST"
+ @env.delete "HTTP_HOST"
+ @env.delete "SERVER_NAME"
+ assert_equal "207.7.108.53:8007", @request.host_with_port
+ end
+
+ def test_host_with_port_if_http_standard_port_is_specified
+ @env['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:80"
+ assert_equal "glu.ttono.us", @request.host_with_port
+ end
+
+ def test_host_with_port_if_https_standard_port_is_specified
+ @env['HTTP_X_FORWARDED_PROTO'] = "https"
+ @env['HTTP_X_FORWARDED_HOST'] = "glu.ttono.us:443"
+ assert_equal "glu.ttono.us", @request.host_with_port
+ end
+
+ def test_host_if_ipv6_reference
+ @env.delete "HTTP_X_FORWARDED_HOST"
+ @env['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]"
+ assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
+ end
+
+ def test_host_if_ipv6_reference_with_port
+ @env.delete "HTTP_X_FORWARDED_HOST"
+ @env['HTTP_HOST'] = "[2001:1234:5678:9abc:def0::dead:beef]:8008"
+ assert_equal "[2001:1234:5678:9abc:def0::dead:beef]", @request.host
+ end
+
+ def test_cookie_syntax_resilience
+ cookies = CGI::Cookie::parse(@env["HTTP_COOKIE"]);
+ assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], cookies["_session_id"], cookies.inspect
+ assert_equal ["yes"], cookies["is_admin"], cookies.inspect
+
+ alt_cookies = CGI::Cookie::parse(@alt_cookie_fmt_request_hash["HTTP_COOKIE"]);
+ assert_equal ["c84ace847,96670c052c6ceb2451fb0f2"], alt_cookies["_session_id"], alt_cookies.inspect
+ assert_equal ["yes"], alt_cookies["is_admin"], alt_cookies.inspect
+ end
+end
+
+
+class RackRequestParamsParsingTest < BaseRackTest
+ def test_doesnt_break_when_content_type_has_charset
+ data = 'flamenco=love'
+ @request.env['CONTENT_LENGTH'] = data.length
+ @request.env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
+ @request.env['RAW_POST_DATA'] = data
+ assert_equal({"flamenco"=> "love"}, @request.request_parameters)
+ end
+
+ def test_doesnt_interpret_request_uri_as_query_string_when_missing
+ @request.env['REQUEST_URI'] = 'foo'
+ assert_equal({}, @request.query_parameters)
+ end
+end
+
+
+class RackRequestNeedsRewoundTest < BaseRackTest
+ def test_body_should_be_rewound
+ data = 'foo'
+ @env['rack.input'] = StringIO.new(data)
+ @env['CONTENT_LENGTH'] = data.length
+ @env['CONTENT_TYPE'] = 'application/x-www-form-urlencoded; charset=utf-8'
+
+ # Read the request body by parsing params.
+ request = ActionController::RackRequest.new(@env)
+ request.request_parameters
+
+ # Should have rewound the body.
+ assert_equal 0, request.body.pos
+ end
+end
+
+
+class RackResponseTest < BaseRackTest
+ def setup
+ super
+ @response = ActionController::RackResponse.new
+ @output = StringIO.new('')
+ end
+
+ def test_simple_output
+ @response.body = "Hello, World!"
+
+ status, headers, body = @response.out(@output)
+ assert_equal 200, status
+ assert_equal({"Content-Type" => "text/html", "Cache-Control" => "no-cache", "Set-Cookie" => ""}, headers)
+
+ parts = []
+ body.each { |part| parts << part }
+ assert_equal ["Hello, World!"], parts
+ end
+
+ def test_streaming_block
+ @response.body = Proc.new do |response, output|
+ 5.times { |n| output.write(n) }
+ end
+
+ status, headers, body = @response.out(@output)
+ assert_equal 200, status
+ assert_equal({"Content-Type" => "text/html", "Cache-Control" => "no-cache", "Set-Cookie" => ""}, headers)
+
+ parts = []
+ body.each { |part| parts << part }
+ assert_equal ["0", "1", "2", "3", "4"], parts
+ end
+end
diff --git a/actionpack/test/template/compiled_templates_test.rb b/actionpack/test/template/compiled_templates_test.rb
deleted file mode 100644
index 73e7ec1d76..0000000000
--- a/actionpack/test/template/compiled_templates_test.rb
+++ /dev/null
@@ -1,192 +0,0 @@
-require 'abstract_unit'
-require 'action_view/helpers/date_helper'
-require 'action_view/compiled_templates'
-
-class CompiledTemplateTests < Test::Unit::TestCase
- def setup
- @ct = ActionView::CompiledTemplates.new
- @v = Class.new
- @v.send :include, @ct
- @a = './test_compile_template_a.rhtml'
- @b = './test_compile_template_b.rhtml'
- @s = './test_compile_template_link.rhtml'
- end
- def teardown
- [@a, @b, @s].each do |f|
- FileUtils.rm(f) if File.exist?(f) || File.symlink?(f)
- end
- end
- attr_reader :ct, :v
-
- def test_name_allocation
- hi_world = ct.method_names['hi world']
- hi_sexy = ct.method_names['hi sexy']
- wish_upon_a_star = ct.method_names['I love seeing decent error messages']
-
- assert_equal hi_world, ct.method_names['hi world']
- assert_equal hi_sexy, ct.method_names['hi sexy']
- assert_equal wish_upon_a_star, ct.method_names['I love seeing decent error messages']
- assert_equal 3, [hi_world, hi_sexy, wish_upon_a_star].uniq.length
- end
-
- def test_wrap_source
- assert_equal(
- "def aliased_assignment(value)\nself.value = value\nend",
- @ct.wrap_source(:aliased_assignment, [:value], 'self.value = value')
- )
-
- assert_equal(
- "def simple()\nnil\nend",
- @ct.wrap_source(:simple, [], 'nil')
- )
- end
-
- def test_compile_source_single_method
- selector = ct.compile_source('doubling method', [:a], 'a + a')
- assert_equal 2, @v.new.send(selector, 1)
- assert_equal 4, @v.new.send(selector, 2)
- assert_equal -4, @v.new.send(selector, -2)
- assert_equal 0, @v.new.send(selector, 0)
- selector
- end
-
- def test_compile_source_two_method
- sel1 = test_compile_source_single_method # compile the method in the other test
- sel2 = ct.compile_source('doubling method', [:a, :b], 'a + b + a + b')
- assert_not_equal sel1, sel2
-
- assert_equal 2, @v.new.send(sel1, 1)
- assert_equal 4, @v.new.send(sel1, 2)
-
- assert_equal 6, @v.new.send(sel2, 1, 2)
- assert_equal 32, @v.new.send(sel2, 15, 1)
- end
-
- def test_mtime
- t1 = Time.now
-
- test_compile_source_single_method
- mtime = ct.mtime('doubling method', [:a])
-
- assert mtime < Time.now
- assert mtime > t1
- end
-
- uses_mocha 'test_compile_time' do
-
- def test_compile_time
- t = Time.now
-
- File.open(@a, "w"){|f| f.puts @a}
- File.open(@b, "w"){|f| f.puts @b}
- # windows doesn't support symlinks (even under cygwin)
- windows = (RUBY_PLATFORM =~ /win32/)
- `ln -s #{@a} #{@s}` unless windows
-
- v = ActionView::Base.new
- v.base_path = '.'
- v.cache_template_loading = false
-
- ta = ActionView::Template.new(v, @a, false, {})
- tb = ActionView::Template.new(v, @b, false, {})
- ts = ActionView::Template.new(v, @s, false, {})
-
- @handler_class = ActionView::Template.handler_class_for_extension(:rhtml)
- @handler = @handler_class.new(v)
-
- # All templates were created at t+1
- File::Stat.any_instance.expects(:mtime).times(windows ? 2 : 3).returns(t + 1.second)
-
- # private methods template_changed_since? and compile_template?
- # should report true for all since they have not been compiled
- assert @handler.send(:template_changed_since?, @a, t)
- assert @handler.send(:template_changed_since?, @b, t)
- assert @handler.send(:template_changed_since?, @s, t) unless windows
-
- assert @handler.send(:compile_template?, ta)
- assert @handler.send(:compile_template?, tb)
- assert @handler.send(:compile_template?, ts) unless windows
-
- # All templates are rendered at t+2
- Time.expects(:now).times(windows ? 2 : 3).returns(t + 2.seconds)
- v.send(:render_template, ta)
- v.send(:render_template, tb)
- v.send(:render_template, ts) unless windows
- a_n = v.method_names[@a]
- b_n = v.method_names[@b]
- s_n = v.method_names[@s] unless windows
- # all of the files have changed since last compile
- assert @handler.compile_time[a_n] > t
- assert @handler.compile_time[b_n] > t
- assert @handler.compile_time[s_n] > t unless windows
-
- # private methods template_changed_since? and compile_template?
- # should report false for all since none have changed since compile
- File::Stat.any_instance.expects(:mtime).times(windows ? 6 : 12).returns(t + 1.second)
- assert !@handler.send(:template_changed_since?, @a, @handler.compile_time[a_n])
- assert !@handler.send(:template_changed_since?, @b, @handler.compile_time[b_n])
- assert !@handler.send(:template_changed_since?, @s, @handler.compile_time[s_n]) unless windows
- assert !@handler.send(:compile_template?, ta)
- assert !@handler.send(:compile_template?, tb)
- assert !@handler.send(:compile_template?, ts) unless windows
- v.send(:render_template, ta)
- v.send(:render_template, tb)
- v.send(:render_template, ts) unless windows
- # none of the files have changed since last compile
- assert @handler.compile_time[a_n] < t + 3.seconds
- assert @handler.compile_time[b_n] < t + 3.seconds
- assert @handler.compile_time[s_n] < t + 3.seconds unless windows
-
- `rm #{@s}; ln -s #{@b} #{@s}` unless windows
- # private methods template_changed_since? and compile_template?
- # should report true for symlink since it has changed since compile
-
- # t + 3.seconds is for the symlink
- File::Stat.any_instance.expects(:mtime).times(windows ? 6 : 9).returns(
- *(windows ? [ t + 1.second, t + 1.second ] :
- [ t + 1.second, t + 1.second, t + 3.second ]) * 3)
- assert !@handler.send(:template_changed_since?, @a, @handler.compile_time[a_n])
- assert !@handler.send(:template_changed_since?, @b, @handler.compile_time[b_n])
- assert @handler.send(:template_changed_since?, @s, @handler.compile_time[s_n]) unless windows
- assert !@handler.send(:compile_template?, ta)
- assert !@handler.send(:compile_template?, tb)
- assert @handler.send(:compile_template?, ts) unless windows
-
- # Only the symlink template gets rendered at t+3
- Time.stubs(:now).returns(t + 3.seconds) unless windows
- v.send(:render_template, ta)
- v.send(:render_template, tb)
- v.send(:render_template, ts) unless windows
- # the symlink has changed since last compile
- assert @handler.compile_time[a_n] < t + 3.seconds
- assert @handler.compile_time[b_n] < t + 3.seconds
- assert_equal @handler.compile_time[s_n], t + 3.seconds unless windows
-
- FileUtils.touch @b
- # private methods template_changed_since? and compile_template?
- # should report true for symlink and file at end of symlink
- # since it has changed since last compile
- #
- # t+4 is for @b and also for the file that @s points to, which is @b
- File::Stat.any_instance.expects(:mtime).times(windows ? 6 : 12).returns(
- *(windows ? [ t + 1.second, t + 4.seconds ] :
- [ t + 1.second, t + 4.seconds, t + 3.second, t + 4.seconds ]) * 3)
- assert !@handler.send(:template_changed_since?, @a, @handler.compile_time[a_n])
- assert @handler.send(:template_changed_since?, @b, @handler.compile_time[b_n])
- assert @handler.send(:template_changed_since?, @s, @handler.compile_time[s_n]) unless windows
- assert !@handler.send(:compile_template?, ta)
- assert @handler.send(:compile_template?, tb)
- assert @handler.send(:compile_template?, ts) unless windows
-
- Time.expects(:now).times(windows ? 1 : 2).returns(t + 5.seconds)
- v.send(:render_template, ta)
- v.send(:render_template, tb)
- v.send(:render_template, ts) unless windows
- # the file at the end of the symlink has changed since last compile
- # both the symlink and the file at the end of it should be recompiled
- assert @handler.compile_time[a_n] < t + 5.seconds
- assert_equal @handler.compile_time[b_n], t + 5.seconds
- assert_equal @handler.compile_time[s_n], t + 5.seconds unless windows
- end
- end
-end