redis-rack-2.0.2/0000755000004100000410000000000013100057465013570 5ustar www-datawww-dataredis-rack-2.0.2/Rakefile0000644000004100000410000000014713100057465015237 0ustar www-datawww-datarequire 'bundler/setup' require 'rake' require 'bundler/gem_tasks' require 'redis-store/testing/tasks' redis-rack-2.0.2/Gemfile0000644000004100000410000000004613100057465015063 0ustar www-datawww-datasource 'https://rubygems.org' gemspec redis-rack-2.0.2/MIT-LICENSE0000644000004100000410000000204513100057465015225 0ustar www-datawww-dataCopyright (c) 2009 - 2013 Luca Guidi Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. redis-rack-2.0.2/.ruby-version0000644000004100000410000000000613100057465016231 0ustar www-datawww-data2.2.2 redis-rack-2.0.2/.travis.yml0000644000004100000410000000024613100057465015703 0ustar www-datawww-datalanguage: ruby script: 'bundle exec rake' rvm: - 2.2.3 - 2.3.1 - ruby-head - jruby-head matrix: allow_failures: - rvm: jruby-head - rvm: ruby-head redis-rack-2.0.2/lib/0000755000004100000410000000000013100057465014336 5ustar www-datawww-dataredis-rack-2.0.2/lib/redis-rack.rb0000644000004100000410000000012013100057465016700 0ustar www-datawww-datarequire 'redis-store' require 'redis/rack/version' require 'rack/session/redis' redis-rack-2.0.2/lib/redis/0000755000004100000410000000000013100057465015444 5ustar www-datawww-dataredis-rack-2.0.2/lib/redis/rack/0000755000004100000410000000000013100057465016364 5ustar www-datawww-dataredis-rack-2.0.2/lib/redis/rack/version.rb0000644000004100000410000000007313100057465020376 0ustar www-datawww-dataclass Redis module Rack VERSION = '2.0.2' end end redis-rack-2.0.2/lib/rack/0000755000004100000410000000000013100057465015256 5ustar www-datawww-dataredis-rack-2.0.2/lib/rack/session/0000755000004100000410000000000013100057465016741 5ustar www-datawww-dataredis-rack-2.0.2/lib/rack/session/redis.rb0000644000004100000410000000544013100057465020377 0ustar www-datawww-datarequire 'rack/session/abstract/id' require 'redis-store' require 'thread' module Rack module Session class Redis < Abstract::ID attr_reader :mutex, :pool DEFAULT_OPTIONS = Abstract::ID::DEFAULT_OPTIONS.merge \ :redis_server => 'redis://127.0.0.1:6379/0/rack:session' def initialize(app, options = {}) super @mutex = Mutex.new @pool = if @default_options[:pool] raise "pool must be an instance of ConnectionPool" unless @default_options[:pool].is_a?(ConnectionPool) @pooled = true @default_options[:pool] elsif [:pool_size, :pool_timeout].any? { |key| @default_options.has_key?(key) } pool_options = {} pool_options[:size] = options[:pool_size] if options[:pool_size] pool_options[:timeout] = options[:pool_timeout] if options[:pool_timeout] @pooled = true ::ConnectionPool.new(pool_options) { ::Redis::Store::Factory.create(@default_options[:redis_server]) } else @default_options.has_key?(:redis_store) ? @default_options[:redis_store] : ::Redis::Store::Factory.create(@default_options[:redis_server]) end end def generate_unique_sid(session) loop do sid = generate_sid first = with do |c| [*c.setnx(sid, session, @default_options)].first end break sid if [1, true].include?(first) end end def get_session(env, sid) if env['rack.session.options'][:skip] [generate_sid, {}] else with_lock(env, [nil, {}]) do unless sid and session = with { |c| c.get(sid) } session = {} sid = generate_unique_sid(session) end [sid, session] end end end def set_session(env, session_id, new_session, options) with_lock(env, false) do with { |c| c.set session_id, new_session, options } session_id end end def destroy_session(env, session_id, options) with_lock(env) do with { |c| c.del(session_id) } generate_sid unless options[:drop] end end def with_lock(env, default=nil) @mutex.lock if env['rack.multithread'] yield rescue Errno::ECONNREFUSED if $VERBOSE warn "#{self} is unable to find Redis server." warn $!.inspect end default ensure @mutex.unlock if @mutex.locked? end def with(&block) if @pooled @pool.with(&block) else block.call(@pool) end end end end end redis-rack-2.0.2/test/0000755000004100000410000000000013100057465014547 5ustar www-datawww-dataredis-rack-2.0.2/test/test_helper.rb0000644000004100000410000000016513100057465017414 0ustar www-datawww-datarequire 'bundler/setup' require 'minitest/autorun' require 'mocha/setup' require 'rack' require 'rack/session/redis' redis-rack-2.0.2/test/rack/0000755000004100000410000000000013100057465015467 5ustar www-datawww-dataredis-rack-2.0.2/test/rack/session/0000755000004100000410000000000013100057465017152 5ustar www-datawww-dataredis-rack-2.0.2/test/rack/session/redis_test.rb0000644000004100000410000003047013100057465021650 0ustar www-datawww-datarequire 'test_helper' require 'rack/mock' require 'thread' require 'connection_pool' describe Rack::Session::Redis do session_key = Rack::Session::Redis::DEFAULT_OPTIONS[:key] session_match = /#{session_key}=([0-9a-fA-F]+);/ incrementor = lambda do |env| env["rack.session"]["counter"] ||= 0 env["rack.session"]["counter"] += 1 Rack::Response.new(env["rack.session"].inspect).to_a end drop_session = proc do |env| env['rack.session.options'][:drop] = true incrementor.call(env) end renew_session = proc do |env| env['rack.session.options'][:renew] = true incrementor.call(env) end defer_session = proc do |env| env['rack.session.options'][:defer] = true incrementor.call(env) end skip_session = proc do |env| env['rack.session.options'][:skip] = true incrementor.call(env) end # # test Redis connection # Rack::Session::Redis.new(incrementor) # # it "faults on no connection" do # lambda{ # Rack::Session::Redis.new(incrementor, :redis_server => 'nosuchserver') # }.must_raise(Exception) # end it "can create it's own pool" do session_store = Rack::Session::Redis.new(incrementor, pool_size: 5, pool_timeout: 10) session_store.pool.class.must_equal ::ConnectionPool session_store.pool.instance_variable_get(:@size).must_equal 5 end it "can create it's own pool using default Redis server" do session_store = Rack::Session::Redis.new(incrementor, pool_size: 5, pool_timeout: 10) session_store.with { |connection| connection.to_s.must_match(/127\.0\.0\.1:6379 against DB 0 with namespace rack:session$/) } end it "can create it's own pool using provided Redis server" do session_store = Rack::Session::Redis.new(incrementor, redis_server: 'redis://127.0.0.1:6380/1', pool_size: 5, pool_timeout: 10) session_store.with { |connection| connection.to_s.must_match(/127\.0\.0\.1:6380 against DB 1$/) } end it "can use a supplied pool" do session_store = Rack::Session::Redis.new(incrementor, pool: ::ConnectionPool.new(size: 1, timeout: 1) { ::Redis::Store::Factory.create("redis://127.0.0.1:6380/1")}) session_store.pool.class.must_equal ::ConnectionPool session_store.pool.instance_variable_get(:@size).must_equal 1 end it "uses the specified Redis store when provided" do store = ::Redis::Store::Factory.create('redis://127.0.0.1:6380/1') pool = Rack::Session::Redis.new(incrementor, :redis_store => store) pool.pool.to_s.must_match(/127\.0\.0\.1:6380 against DB 1$/) pool.pool.must_equal(store) end it "uses the default Redis server and namespace when not provided" do pool = Rack::Session::Redis.new(incrementor) pool.pool.to_s.must_match(/127\.0\.0\.1:6379 against DB 0 with namespace rack:session$/) end it "uses the specified namespace when provided" do pool = Rack::Session::Redis.new(incrementor, :redis_server => {:namespace => 'test:rack:session'}) pool.pool.to_s.must_match(/namespace test:rack:session$/) end it "uses the specified Redis server when provided" do pool = Rack::Session::Redis.new(incrementor, :redis_server => 'redis://127.0.0.1:6380/1') pool.pool.to_s.must_match(/127\.0\.0\.1:6380 against DB 1$/) end it "creates a new cookie" do with_pool_management(incrementor) do |pool| res = Rack::MockRequest.new(pool).get("/") res["Set-Cookie"].must_include("#{session_key}=") res.body.must_equal('{"counter"=>1}') end end it "determines session from a cookie" do with_pool_management(incrementor) do |pool| req = Rack::MockRequest.new(pool) res = req.get("/") cookie = res["Set-Cookie"] req.get("/", "HTTP_COOKIE" => cookie). body.must_equal('{"counter"=>2}') req.get("/", "HTTP_COOKIE" => cookie). body.must_equal('{"counter"=>3}') end end it "determines session only from a cookie by default" do with_pool_management(incrementor) do |pool| req = Rack::MockRequest.new(pool) res = req.get("/") sid = res["Set-Cookie"][session_match, 1] req.get("/?rack.session=#{sid}"). body.must_equal('{"counter"=>1}') req.get("/?rack.session=#{sid}"). body.must_equal('{"counter"=>1}') end end it "determines session from params" do with_pool_management(incrementor, :cookie_only => false) do |pool| req = Rack::MockRequest.new(pool) res = req.get("/") sid = res["Set-Cookie"][session_match, 1] req.get("/?rack.session=#{sid}"). body.must_equal('{"counter"=>2}') req.get("/?rack.session=#{sid}"). body.must_equal('{"counter"=>3}') end end it "survives nonexistant cookies" do bad_cookie = "rack.session=blarghfasel" with_pool_management(incrementor) do |pool| res = Rack::MockRequest.new(pool). get("/", "HTTP_COOKIE" => bad_cookie) res.body.must_equal('{"counter"=>1}') cookie = res["Set-Cookie"][session_match] cookie.wont_match(/#{bad_cookie}/) end end it "maintains freshness" do with_pool_management(incrementor, :expire_after => 3) do |pool| res = Rack::MockRequest.new(pool).get('/') res.body.must_include('"counter"=>1') cookie = res["Set-Cookie"] sid = cookie[session_match, 1] res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie) res["Set-Cookie"][session_match, 1].must_equal(sid) res.body.must_include('"counter"=>2') puts 'Sleeping to expire session' if $DEBUG sleep 4 res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie) res["Set-Cookie"][session_match, 1].wont_equal(sid) res.body.must_include('"counter"=>1') end end it "does not send the same session id if it did not change" do with_pool_management(incrementor) do |pool| req = Rack::MockRequest.new(pool) res0 = req.get("/") cookie = res0["Set-Cookie"] res0.body.must_equal('{"counter"=>1}') res1 = req.get("/", "HTTP_COOKIE" => cookie) res1["Set-Cookie"].must_be_nil res1.body.must_equal('{"counter"=>2}') res2 = req.get("/", "HTTP_COOKIE" => cookie) res2["Set-Cookie"].must_be_nil res2.body.must_equal('{"counter"=>3}') end end it "deletes cookies with :drop option" do with_pool_management(incrementor) do |pool| req = Rack::MockRequest.new(pool) drop = Rack::Utils::Context.new(pool, drop_session) dreq = Rack::MockRequest.new(drop) res1 = req.get("/") session = (cookie = res1["Set-Cookie"])[session_match] res1.body.must_equal('{"counter"=>1}') res2 = dreq.get("/", "HTTP_COOKIE" => cookie) res2["Set-Cookie"].must_be_nil res2.body.must_equal('{"counter"=>2}') res3 = req.get("/", "HTTP_COOKIE" => cookie) res3["Set-Cookie"][session_match].wont_equal(session) res3.body.must_equal('{"counter"=>1}') end end it "provides new session id with :renew option" do with_pool_management(incrementor) do |pool| req = Rack::MockRequest.new(pool) renew = Rack::Utils::Context.new(pool, renew_session) rreq = Rack::MockRequest.new(renew) res1 = req.get("/") session = (cookie = res1["Set-Cookie"])[session_match] res1.body.must_equal('{"counter"=>1}') res2 = rreq.get("/", "HTTP_COOKIE" => cookie) new_cookie = res2["Set-Cookie"] new_session = new_cookie[session_match] new_session.wont_equal(session) res2.body.must_equal('{"counter"=>2}') res3 = req.get("/", "HTTP_COOKIE" => new_cookie) res3.body.must_equal('{"counter"=>3}') # Old cookie was deleted res4 = req.get("/", "HTTP_COOKIE" => cookie) res4.body.must_equal('{"counter"=>1}') end end it "omits cookie with :defer option" do with_pool_management(incrementor) do |pool| defer = Rack::Utils::Context.new(pool, defer_session) dreq = Rack::MockRequest.new(defer) res0 = dreq.get("/") res0["Set-Cookie"].must_be_nil res0.body.must_equal('{"counter"=>1}') end end it "does not hit with :skip option" do with_pool_management(incrementor) do |pool| skip = Rack::Utils::Context.new(pool, skip_session) sreq = Rack::MockRequest.new(skip) pool.instance_variable_set('@pool', MiniTest::Mock.new) res0 = sreq.get("/") res0.body.must_equal('{"counter"=>1}') assert pool.pool.verify end end it "updates deep hashes correctly" do hash_check = proc do |env| session = env['rack.session'] unless session.include? 'test' session.update :a => :b, :c => { :d => :e }, :f => { :g => { :h => :i} }, 'test' => true else session[:f][:g][:h] = :j end [200, {}, [session.inspect]] end with_pool_management(hash_check) do |pool| req = Rack::MockRequest.new(pool) res0 = req.get("/") session_id = (cookie = res0["Set-Cookie"])[session_match, 1] ses0 = pool.with { |c| c.get(session_id) } req.get("/", "HTTP_COOKIE" => cookie) ses1 = pool.with { |c| c.get(session_id) } ses1.wont_equal(ses0) end end # anyone know how to do this better? it "cleanly merges sessions when multithreaded" do unless $DEBUG 1.must_equal(1) # fake assertion to appease the mighty bacon next end warn 'Running multithread test for Session::Redis' with_pool_management(incrementor) do |pool| req = Rack::MockRequest.new(pool) res = req.get('/') res.body.must_equal('{"counter"=>1}') cookie = res["Set-Cookie"] session_id = cookie[session_match, 1] delta_incrementor = lambda do |env| # emulate disconjoinment of threading env['rack.session'] = env['rack.session'].dup Thread.stop env['rack.session'][(Time.now.usec*rand).to_i] = true incrementor.call(env) end tses = Rack::Utils::Context.new pool, delta_incrementor treq = Rack::MockRequest.new(tses) tnum = rand(7).to_i+5 r = Array.new(tnum) do Thread.new(treq) do |run| run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true) end end.reverse.map{|t| t.run.join.value } r.each do |request| request['Set-Cookie'].must_equal(cookie) request.body.must_include('"counter"=>2') end session = pool.with { |c| c.get(session_id) } session.size.must_equal(tnum+1) # counter session['counter'].must_equal(2) # meeeh tnum = rand(7).to_i+5 r = Array.new(tnum) do |i| app = Rack::Utils::Context.new pool, time_delta req = Rack::MockRequest.new app Thread.new(req) do |run| run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true) end end.reverse.map{|t| t.run.join.value } r.each do |request| request['Set-Cookie'].must_equal(cookie) request.body.must_include('"counter"=>3') end session = pool.with { |c| c.get(session_id) } session.size.must_equal(tnum+1) session['counter'].must_equal(3) drop_counter = proc do |env| env['rack.session'].delete 'counter' env['rack.session']['foo'] = 'bar' [200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect] end tses = Rack::Utils::Context.new pool, drop_counter treq = Rack::MockRequest.new(tses) tnum = rand(7).to_i+5 r = Array.new(tnum) do Thread.new(treq) do |run| run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true) end end.reverse.map{|t| t.run.join.value } r.each do |request| request['Set-Cookie'].must_equal(cookie) request.body.must_include('"foo"=>"bar"') end session = pool.with { |c| c.get(session_id) } session.size.must_equal(r.size+1) session['counter'].must_be_nil session['foo'].must_equal('bar') end end private def with_pool_management(*args) yield simple(*args) yield pooled(*args) yield external_pooled(*args) end def simple(app, options = {}) Rack::Session::Redis.new(app, options) end def pooled(app, options = {}) Rack::Session::Redis.new(app, options) Rack::Session::Redis.new(app, options.merge(pool_size: 5, pool_timeout: 10)) end def external_pooled(app, options = {}) Rack::Session::Redis.new(app, options.merge(pool: ::ConnectionPool.new(size: 1, timeout: 1) { ::Redis::Store::Factory.create("redis://127.0.0.1:6380/1") })) end end redis-rack-2.0.2/.gitignore0000644000004100000410000000003713100057465015560 0ustar www-datawww-dataGemfile.lock *.gem tmp/ stdout redis-rack-2.0.2/redis-rack.gemspec0000644000004100000410000000223213100057465017160 0ustar www-datawww-data# -*- encoding: utf-8 -*- $:.push File.expand_path('../lib', __FILE__) require 'redis/rack/version' Gem::Specification.new do |s| s.name = 'redis-rack' s.version = Redis::Rack::VERSION s.authors = ['Luca Guidi'] s.email = ['me@lucaguidi.com'] s.homepage = 'http://redis-store.org/redis-rack' s.summary = %q{Redis Store for Rack} s.description = %q{Redis Store for Rack applications} s.license = 'MIT' s.rubyforge_project = 'redis-rack' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] s.add_runtime_dependency 'redis-store', ['< 1.4', '>= 1.2'] s.add_runtime_dependency 'rack', '>= 1.5', '< 3' s.add_development_dependency 'rake', '~> 10' s.add_development_dependency 'bundler', '~> 1.3' s.add_development_dependency 'mocha', '~> 0.14.0' s.add_development_dependency 'minitest', '~> 5' s.add_development_dependency 'redis-store-testing' s.add_development_dependency 'connection_pool', '~> 1.2.0' end redis-rack-2.0.2/README.md0000644000004100000410000000362713100057465015057 0ustar www-datawww-data# Redis session store for Rack __`redis-rack`__ provides a Redis-backed session store for __Rack__. See the main [redis-store readme] for general guidelines. **NOTE:** This is not [redis-rack-cache][], the library for using Redis as a backend store for the `Rack::Cache` HTTP cache. All this gem does is store the Rack session within Redis. [![Build Status](https://secure.travis-ci.org/redis-store/redis-rack.png?branch=master)](http://travis-ci.org/redis-store/redis-rack?branch=master) [![Code Climate](https://codeclimate.com/github/redis-store/redis-store.png)](https://codeclimate.com/github/redis-store/redis-rack) [![Gem Version](https://badge.fury.io/rb/redis-rack.png)](http://badge.fury.io/rb/redis-rack) ## Installation Install with Bundler by adding the following to Gemfile: ```ruby gem 'redis-rack' ``` Then, run: ```shell $ bundle install ``` Or, you can install it manually using RubyGems: ```shell $ gem install redis-rack ``` ## Usage If you are using redis-store with Rails, consider using the [redis-rails gem](https://github.com/redis-store/redis-rails) instead. For standalone usage: ```ruby # config.ru require 'rack' require 'rack/session/redis' use Rack::Session::Redis ``` ## Development To install this gem for development purposes: ```shell $ gem install bundler # note: you don't need to do this if you already have it installed $ git clone git://github.com/redis-store/redis-rack.git $ cd redis-rack $ bundle install ``` ## Running tests To run tests: ```shell $ bundle exec rake ``` If you are on **Snow Leopard** you have to run the following command to build this software: ```shell $ env ARCHFLAGS="-arch x86_64" bundle exec rake ``` ## Copyright 2009 - 2013 Luca Guidi - [http://lucaguidi.com](http://lucaguidi.com), released under the MIT license [redis-rack-cache]: https://github.com/redis-store/redis-rack-cache [redis-store readme]: https://github.com/redis-store/redis-store