aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/CHANGELOG2
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/action_controller/record_identifier.rb4
-rw-r--r--actionpack/lib/action_controller/vendor/html-scanner/html/document.rb2
-rw-r--r--actionpack/lib/action_dispatch.rb2
-rw-r--r--actionpack/lib/action_dispatch/http/request.rb10
-rw-r--r--actionpack/lib/action_dispatch/middleware/cookies.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/request_id.rb39
-rw-r--r--actionpack/lib/action_dispatch/middleware/session/cache_store.rb50
-rw-r--r--actionpack/test/controller/mime_responds_test.rb4
-rw-r--r--actionpack/test/dispatch/request_id_test.rb65
-rw-r--r--actionpack/test/dispatch/session/cache_store_test.rb181
-rw-r--r--actionpack/test/template/html-scanner/tag_node_test.rb7
13 files changed, 363 insertions, 7 deletions
diff --git a/actionpack/CHANGELOG b/actionpack/CHANGELOG
index 29992a36b1..e7886facb9 100644
--- a/actionpack/CHANGELOG
+++ b/actionpack/CHANGELOG
@@ -1,5 +1,7 @@
*Rails 3.2.0 (unreleased)*
+* Added ActionDispatch::RequestId middleware that'll make a unique X-Request-Id header available to the response and enables the ActionDispatch::Request#uuid method. This makes it easy to trace requests from end-to-end in the stack and to identify individual requests in mixed logs like Syslog [DHH]
+
* Limit the number of options for select_year to 1000.
Pass the :max_years_allowed option to set your own limit.
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index 78b9a4d5a8..c2eac234b0 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -24,7 +24,7 @@ Gem::Specification.new do |s|
s.add_dependency('rack', '~> 1.3.5')
s.add_dependency('rack-test', '~> 0.6.1')
s.add_dependency('journey', '~> 1.0.0')
- s.add_dependency('sprockets', '~> 2.0.2')
+ s.add_dependency('sprockets', '~> 2.0.3')
s.add_dependency('erubis', '~> 2.7.0')
s.add_development_dependency('tzinfo', '~> 0.3.29')
diff --git a/actionpack/lib/action_controller/record_identifier.rb b/actionpack/lib/action_controller/record_identifier.rb
index 2036442cfe..9c38ff44d8 100644
--- a/actionpack/lib/action_controller/record_identifier.rb
+++ b/actionpack/lib/action_controller/record_identifier.rb
@@ -14,9 +14,9 @@ module ActionController
# <% end %> </div>
#
# # controller
- # def destroy
+ # def update
# post = Post.find(params[:id])
- # post.destroy
+ # post.update_attributes(params[:post])
#
# redirect_to(post) # Calls polymorphic_url(post) which in turn calls post_url(post)
# end
diff --git a/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb b/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
index 7fa3aead82..386820300a 100644
--- a/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
+++ b/actionpack/lib/action_controller/vendor/html-scanner/html/document.rb
@@ -4,7 +4,7 @@ require 'html/selector'
require 'html/sanitizer'
module HTML #:nodoc:
- # A top-level HTMl document. You give it a body of text, and it will parse that
+ # A top-level HTML document. You give it a body of text, and it will parse that
# text into a tree of nodes.
class Document #:nodoc:
diff --git a/actionpack/lib/action_dispatch.rb b/actionpack/lib/action_dispatch.rb
index 7f972fc281..1e92d14542 100644
--- a/actionpack/lib/action_dispatch.rb
+++ b/actionpack/lib/action_dispatch.rb
@@ -47,6 +47,7 @@ module ActionDispatch
end
autoload_under 'middleware' do
+ autoload :RequestId
autoload :BestStandardsSupport
autoload :Callbacks
autoload :Cookies
@@ -82,6 +83,7 @@ module ActionDispatch
autoload :AbstractStore, 'action_dispatch/middleware/session/abstract_store'
autoload :CookieStore, 'action_dispatch/middleware/session/cookie_store'
autoload :MemCacheStore, 'action_dispatch/middleware/session/mem_cache_store'
+ autoload :CacheStore, 'action_dispatch/middleware/session/cache_store'
end
autoload_under 'testing' do
diff --git a/actionpack/lib/action_dispatch/http/request.rb b/actionpack/lib/action_dispatch/http/request.rb
index 37d0a3e0b8..7a5237dcf3 100644
--- a/actionpack/lib/action_dispatch/http/request.rb
+++ b/actionpack/lib/action_dispatch/http/request.rb
@@ -177,6 +177,16 @@ module ActionDispatch
@remote_ip ||= (@env["action_dispatch.remote_ip"] || ip).to_s
end
+ # Returns the unique request id, which is based off either the X-Request-Id header that can
+ # be generated by a firewall, load balancer, or web server or by the RequestId middleware
+ # (which sets the action_dispatch.request_id environment variable).
+ #
+ # This unique ID is useful for tracing a request from end-to-end as part of logging or debugging.
+ # This relies on the rack variable set by the ActionDispatch::RequestId middleware.
+ def uuid
+ @uuid ||= env["action_dispatch.request_id"]
+ end
+
# Returns the lowercase name of the HTTP server software.
def server_software
(@env['SERVER_SOFTWARE'] && /^([a-zA-Z]+)/ =~ @env['SERVER_SOFTWARE']) ? $1.downcase : nil
diff --git a/actionpack/lib/action_dispatch/middleware/cookies.rb b/actionpack/lib/action_dispatch/middleware/cookies.rb
index 8c4615c0c1..a4ffd40a66 100644
--- a/actionpack/lib/action_dispatch/middleware/cookies.rb
+++ b/actionpack/lib/action_dispatch/middleware/cookies.rb
@@ -174,7 +174,7 @@ module ActionDispatch
options = { :value => value }
end
- value = @cookies[key.to_s] = value
+ @cookies[key.to_s] = value
handle_options(options)
diff --git a/actionpack/lib/action_dispatch/middleware/request_id.rb b/actionpack/lib/action_dispatch/middleware/request_id.rb
new file mode 100644
index 0000000000..bee446c8a5
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/request_id.rb
@@ -0,0 +1,39 @@
+require 'securerandom'
+require 'active_support/core_ext/string/access'
+require 'active_support/core_ext/object/blank'
+
+module ActionDispatch
+ # Makes a unique request id available to the action_dispatch.request_id env variable (which is then accessible through
+ # ActionDispatch::Request#uuid) and sends the same id to the client via the X-Request-Id header.
+ #
+ # The unique request id is either based off the X-Request-Id header in the request, which would typically be generated
+ # by a firewall, load balancer, or the web server, or, if this header is not available, a random uuid. If the
+ # header is accepted from the outside world, we sanitize it to a max of 255 chars and alphanumeric and dashes only.
+ #
+ # The unique request id can be used to trace a request end-to-end and would typically end up being part of log files
+ # from multiple pieces of the stack.
+ class RequestId
+ def initialize(app)
+ @app = app
+ end
+
+ def call(env)
+ env["action_dispatch.request_id"] = external_request_id(env) || internal_request_id
+ status, headers, body = @app.call(env)
+
+ headers["X-Request-Id"] = env["action_dispatch.request_id"]
+ [ status, headers, body ]
+ end
+
+ private
+ def external_request_id(env)
+ if request_id = env["HTTP_X_REQUEST_ID"].presence
+ request_id.gsub(/[^\w\-]/, "").first(255)
+ end
+ end
+
+ def internal_request_id
+ SecureRandom.hex(16)
+ end
+ end
+end
diff --git a/actionpack/lib/action_dispatch/middleware/session/cache_store.rb b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
new file mode 100644
index 0000000000..d3b6fd12fa
--- /dev/null
+++ b/actionpack/lib/action_dispatch/middleware/session/cache_store.rb
@@ -0,0 +1,50 @@
+require 'action_dispatch/middleware/session/abstract_store'
+require 'rack/session/memcache'
+
+module ActionDispatch
+ module Session
+ # Session store that uses an ActiveSupport::Cache::Store to store the sessions. This store is most useful
+ # if you don't store critical data in your sessions and you don't need them to live for extended periods
+ # of time.
+ class CacheStore < AbstractStore
+ # Create a new store. The cache to use can be passed in the <tt>:cache</tt> option. If it is
+ # not specified, <tt>Rails.cache</tt> will be used.
+ def initialize(app, options = {})
+ @cache = options[:cache] || Rails.cache
+ options[:expire_after] ||= @cache.options[:expires_in]
+ super
+ end
+
+ # Get a session from the cache.
+ def get_session(env, sid)
+ sid ||= generate_sid
+ session = @cache.read(cache_key(sid))
+ session ||= {}
+ [sid, session]
+ end
+
+ # Set a session in the cache.
+ def set_session(env, sid, session, options)
+ key = cache_key(sid)
+ if session
+ @cache.write(key, session, :expires_in => options[:expire_after])
+ else
+ @cache.delete(key)
+ end
+ sid
+ end
+
+ # Remove a session from the cache.
+ def destroy_session(env, sid, options)
+ @cache.delete(cache_key(sid))
+ generate_sid
+ end
+
+ private
+ # Turn the session id into a cache key.
+ def cache_key(sid)
+ "_session_id:#{sid}"
+ end
+ end
+ end
+end
diff --git a/actionpack/test/controller/mime_responds_test.rb b/actionpack/test/controller/mime_responds_test.rb
index 2aa73f1650..e91e11a8a7 100644
--- a/actionpack/test/controller/mime_responds_test.rb
+++ b/actionpack/test/controller/mime_responds_test.rb
@@ -656,7 +656,9 @@ class RespondWithControllerTest < ActionController::TestCase
@request.accept = "application/json"
get :using_hash_resource
assert_equal "application/json", @response.content_type
- assert_equal %Q[{"result":{"name":"david","id":13}}], @response.body
+ assert @response.body.include?("result")
+ assert @response.body.include?('"name":"david"')
+ assert @response.body.include?('"id":13')
end
def test_using_hash_resource_with_post
diff --git a/actionpack/test/dispatch/request_id_test.rb b/actionpack/test/dispatch/request_id_test.rb
new file mode 100644
index 0000000000..b6e8b6c3ad
--- /dev/null
+++ b/actionpack/test/dispatch/request_id_test.rb
@@ -0,0 +1,65 @@
+require 'abstract_unit'
+
+class RequestIdTest < ActiveSupport::TestCase
+ test "passing on the request id from the outside" do
+ assert_equal "external-uu-rid", stub_request('HTTP_X_REQUEST_ID' => 'external-uu-rid').uuid
+ end
+
+ test "ensure that only alphanumeric uurids are accepted" do
+ assert_equal "X-Hacked-HeaderStuff", stub_request('HTTP_X_REQUEST_ID' => '; X-Hacked-Header: Stuff').uuid
+ end
+
+ test "ensure that 255 char limit on the request id is being enforced" do
+ assert_equal "X" * 255, stub_request('HTTP_X_REQUEST_ID' => 'X' * 500).uuid
+ end
+
+ test "generating a request id when none is supplied" do
+ assert_match(/\w+/, stub_request.uuid)
+ end
+
+ private
+
+ def stub_request(env = {})
+ ActionDispatch::RequestId.new(lambda { |environment| [ 200, environment, [] ] }).call(env)
+ ActionDispatch::Request.new(env)
+ end
+end
+
+class RequestIdResponseTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ def index
+ head :ok
+ end
+ end
+
+ test "request id is passed all the way to the response" do
+ with_test_route_set do
+ get '/'
+ assert_match(/\w+/, @response.headers["X-Request-Id"])
+ end
+ end
+
+ test "request id given on request is passed all the way to the response" do
+ with_test_route_set do
+ get '/', {}, 'HTTP_X_REQUEST_ID' => 'X' * 500
+ assert_equal "X" * 255, @response.headers["X-Request-Id"]
+ end
+ end
+
+
+ private
+
+ def with_test_route_set
+ with_routing do |set|
+ set.draw do
+ match '/', :to => ::RequestIdResponseTest::TestController.action(:index)
+ end
+
+ @app = self.class.build_app(set) do |middleware|
+ middleware.use ActionDispatch::RequestId
+ end
+
+ yield
+ end
+ end
+end \ No newline at end of file
diff --git a/actionpack/test/dispatch/session/cache_store_test.rb b/actionpack/test/dispatch/session/cache_store_test.rb
new file mode 100644
index 0000000000..73e056de23
--- /dev/null
+++ b/actionpack/test/dispatch/session/cache_store_test.rb
@@ -0,0 +1,181 @@
+require 'abstract_unit'
+
+class CacheStoreTest < ActionDispatch::IntegrationTest
+ class TestController < ActionController::Base
+ def no_session_access
+ head :ok
+ end
+
+ def set_session_value
+ session[:foo] = "bar"
+ head :ok
+ end
+
+ def set_serialized_session_value
+ session[:foo] = SessionAutoloadTest::Foo.new
+ head :ok
+ end
+
+ def get_session_value
+ render :text => "foo: #{session[:foo].inspect}"
+ end
+
+ def get_session_id
+ render :text => "#{request.session_options[:id]}"
+ end
+
+ def call_reset_session
+ session[:bar]
+ reset_session
+ session[:bar] = "baz"
+ head :ok
+ end
+
+ def rescue_action(e) raise end
+ end
+
+ def test_setting_and_getting_session_value
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: "bar"', response.body
+ end
+ end
+
+ def test_getting_nil_session_value
+ with_test_route_set do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ end
+ end
+
+ def test_getting_session_value_after_session_reset
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ session_cookie = cookies.send(:hash_for)['_session_id']
+
+ get '/call_reset_session'
+ assert_response :success
+ assert_not_equal [], headers['Set-Cookie']
+
+ cookies << session_cookie # replace our new session_id with our old, pre-reset session_id
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body, "data for this session should have been obliterated from cache"
+ end
+ end
+
+ def test_getting_from_nonexistent_session
+ with_test_route_set do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ assert_nil cookies['_session_id'], "should only create session on write, not read"
+ end
+ end
+
+ def test_setting_session_value_after_session_reset
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ session_id = cookies['_session_id']
+
+ get '/call_reset_session'
+ assert_response :success
+ assert_not_equal [], headers['Set-Cookie']
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+
+ get '/get_session_id'
+ assert_response :success
+ assert_not_equal session_id, response.body
+ end
+ end
+
+ def test_getting_session_id
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ session_id = cookies['_session_id']
+
+ get '/get_session_id'
+ assert_response :success
+ assert_equal session_id, response.body, "should be able to read session id without accessing the session hash"
+ end
+ end
+
+ def test_deserializes_unloaded_class
+ with_test_route_set do
+ with_autoload_path "session_autoload_test" do
+ get '/set_serialized_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+ end
+ with_autoload_path "session_autoload_test" do
+ get '/get_session_id'
+ assert_response :success
+ end
+ with_autoload_path "session_autoload_test" do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: #<SessionAutoloadTest::Foo bar:"baz">', response.body, "should auto-load unloaded class"
+ end
+ end
+ end
+
+ def test_doesnt_write_session_cookie_if_session_id_is_already_exists
+ with_test_route_set do
+ get '/set_session_value'
+ assert_response :success
+ assert cookies['_session_id']
+
+ get '/get_session_value'
+ assert_response :success
+ assert_equal nil, headers['Set-Cookie'], "should not resend the cookie again if session_id cookie is already exists"
+ end
+ end
+
+ def test_prevents_session_fixation
+ with_test_route_set do
+ get '/get_session_value'
+ assert_response :success
+ assert_equal 'foo: nil', response.body
+ session_id = cookies['_session_id']
+
+ reset!
+
+ get '/set_session_value', :_session_id => session_id
+ assert_response :success
+ assert_not_equal session_id, cookies['_session_id']
+ end
+ end
+
+ private
+ def with_test_route_set
+ with_routing do |set|
+ set.draw do
+ match ':action', :to => ::CacheStoreTest::TestController
+ end
+
+ @app = self.class.build_app(set) do |middleware|
+ cache = ActiveSupport::Cache::MemoryStore.new
+ middleware.use ActionDispatch::Session::CacheStore, :key => '_session_id', :cache => cache
+ middleware.delete "ActionDispatch::ShowExceptions"
+ end
+
+ yield
+ end
+ end
+end
diff --git a/actionpack/test/template/html-scanner/tag_node_test.rb b/actionpack/test/template/html-scanner/tag_node_test.rb
index 0d87f1bd42..3b72243e7d 100644
--- a/actionpack/test/template/html-scanner/tag_node_test.rb
+++ b/actionpack/test/template/html-scanner/tag_node_test.rb
@@ -55,7 +55,12 @@ class TagNodeTest < Test::Unit::TestCase
def test_to_s
node = tag("<a b=c d='f' g=\"h 'i'\" />")
- assert_equal %(<a b="c" d="f" g="h 'i'" />), node.to_s
+ node = node.to_s
+ assert node.include?('a')
+ assert node.include?('b="c"')
+ assert node.include?('d="f"')
+ assert node.include?('g="h')
+ assert node.include?('i')
end
def test_tag