diff options
author | Pratik Naik <pratiknaik@gmail.com> | 2008-06-02 12:29:28 +0100 |
---|---|---|
committer | Pratik Naik <pratiknaik@gmail.com> | 2008-06-02 12:29:28 +0100 |
commit | 719aa13b0781d662673dba2225bc707fc4182b5e (patch) | |
tree | 057cabf17152a1ab0ccc749dd361eeaf754c38de /actionpack | |
parent | ad3c97cea6dd0714652db7d9b5b4d06b81e5c035 (diff) | |
parent | a980eb8c7734f14109d8c2a02a88dafdf682e0dc (diff) | |
download | rails-719aa13b0781d662673dba2225bc707fc4182b5e.tar.gz rails-719aa13b0781d662673dba2225bc707fc4182b5e.tar.bz2 rails-719aa13b0781d662673dba2225bc707fc4182b5e.zip |
Merge commit 'mainstream/master'
Diffstat (limited to 'actionpack')
-rw-r--r-- | actionpack/CHANGELOG | 7 | ||||
-rw-r--r-- | actionpack/Rakefile | 2 | ||||
-rwxr-xr-x | actionpack/lib/action_controller.rb | 1 | ||||
-rw-r--r-- | actionpack/lib/action_controller/cgi_ext/cookie.rb | 6 | ||||
-rw-r--r-- | actionpack/lib/action_controller/dispatcher.rb | 8 | ||||
-rw-r--r-- | actionpack/lib/action_controller/rack_process.rb | 321 | ||||
-rwxr-xr-x | actionpack/lib/action_controller/request_profiler.rb | 25 | ||||
-rw-r--r-- | actionpack/lib/action_pack/version.rb | 4 | ||||
-rw-r--r-- | actionpack/lib/action_view/compiled_templates.rb | 69 | ||||
-rwxr-xr-x | actionpack/test/controller/cgi_test.rb | 33 | ||||
-rw-r--r-- | actionpack/test/controller/rack_test.rb | 150 | ||||
-rw-r--r-- | actionpack/test/template/compiled_templates_test.rb | 192 |
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 |