redrack-session 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/Gemfile +3 -0
- data/LICENSE +19 -0
- data/README.md +40 -0
- data/Rakefile +2 -0
- data/lib/redrack-session.rb +2 -0
- data/lib/redrack/session.rb +7 -0
- data/lib/redrack/session/middleware.rb +102 -0
- data/lib/redrack/session/version.rb +6 -0
- data/redrack-session.gemspec +28 -0
- data/spec/redrack/session_spec.rb +195 -0
- data/spec/spec_helper.rb +41 -0
- data/spec/support/rack_app.rb +121 -0
- metadata +131 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (C) 2007, 2008, 2009, and 2010 by Christian Neukirchen
|
2
|
+
<purl.org/net/chneukirchen> and (C) 2011 by Kendall Gifford
|
3
|
+
|
4
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
5
|
+
this software and associated documentation files (the "Software"), to deal in
|
6
|
+
the Software without restriction, including without limitation the rights to
|
7
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
8
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
9
|
+
subject to the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be included in all
|
12
|
+
copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
15
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
16
|
+
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
17
|
+
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
18
|
+
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
19
|
+
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
## redrack-session
|
2
|
+
|
3
|
+
Redis session store for rack applications.
|
4
|
+
|
5
|
+
This provides browser sessions for your rack application, storing a unique
|
6
|
+
session ID in a cookie in the client's browser and the session data in a redis
|
7
|
+
server.
|
8
|
+
|
9
|
+
### Usage
|
10
|
+
|
11
|
+
This gem may be used in much the same way as you might use the
|
12
|
+
`Rack::Session::Memcached` middleware provided by the rack gem, or any other
|
13
|
+
rack session middleware for that matter. Just add it to your rack middleware
|
14
|
+
stack and then you can read and write objects to the hash provided in
|
15
|
+
`env["rack.session"]`.
|
16
|
+
|
17
|
+
### To Do
|
18
|
+
|
19
|
+
- Flush out this README and improvide inline code documentation
|
20
|
+
- Create redrack-cache gem
|
21
|
+
- Create redrack-throttle gem
|
22
|
+
- Create redrack-localize gem
|
23
|
+
- Create redrack gem to package all of the above as single rack middleware
|
24
|
+
- Create redrails-session gem
|
25
|
+
- Create redrails-throttle gem
|
26
|
+
- Create redrails-localize gem
|
27
|
+
- Create redrails gem to package all of the redrails-* gems above, linking all redrack-* middleware into a rails app
|
28
|
+
|
29
|
+
### Credits and License
|
30
|
+
|
31
|
+
Though "authored" by myself (Kendall Gifford), this gem was heavily inspired by
|
32
|
+
by the `Rack::Session::Memcached` rack middleware included in the rack gem. The
|
33
|
+
RSpec tests were even more heavily inspired by the rack gem and are basically a
|
34
|
+
translation of the test cases in the rack codebase that test
|
35
|
+
`Rack::Session::Memcached`.
|
36
|
+
|
37
|
+
Licensed using the standard
|
38
|
+
[MIT License](http://en.wikipedia.org/wiki/MIT_License). See the file
|
39
|
+
[LICENSE](http://github.com/zettabyte/redrack-session/blob/master/LICENSE) in
|
40
|
+
the root folder of the project.
|
data/Rakefile
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rack/session/abstract/id'
|
3
|
+
require 'redis'
|
4
|
+
require 'redis-namespace'
|
5
|
+
|
6
|
+
module Redrack
|
7
|
+
module Session
|
8
|
+
class Middleware < Rack::Session::Abstract::ID
|
9
|
+
|
10
|
+
attr_reader :redis # provide raw access to redis database (for testing)
|
11
|
+
|
12
|
+
# redis-specific default options (following the Abstract::ID pattern)
|
13
|
+
DEFAULT_OPTIONS = Rack::Session::Abstract::ID::DEFAULT_OPTIONS.merge(
|
14
|
+
:redis_password => nil, # optional authentication password for redis server
|
15
|
+
:redis_namespace => nil, # optional namespace under which session keys are stored
|
16
|
+
:redis_path => nil, # specify this if connecting to redis server via socket
|
17
|
+
:redis_host => "127.0.0.1", # hostname or IP of redis database server
|
18
|
+
:redis_port => 6379, # port to connect to redis database server
|
19
|
+
:redis_database => 0, # default redis database to hold sessions
|
20
|
+
:redis_timeout => 5 # default redis connection timeout (seconds)
|
21
|
+
)
|
22
|
+
|
23
|
+
def initialize(app, options = {})
|
24
|
+
super
|
25
|
+
@mutex = Mutex.new
|
26
|
+
|
27
|
+
# process redis-specific options
|
28
|
+
if @default_options[:redis_path].is_a?(String)
|
29
|
+
redis_options = { :path => @default_options[:redis_path] }
|
30
|
+
else
|
31
|
+
redis_options = {
|
32
|
+
:host => (@default_options[:redis_host] || "127.0.0.1"),
|
33
|
+
:port => (@default_options[:redis_port] || 6379)
|
34
|
+
}
|
35
|
+
end
|
36
|
+
redis_options[:db] = @default_options[:redis_database] || 0
|
37
|
+
redis_options[:timeout] = @default_options[:redis_timeout] || 5
|
38
|
+
redis_options[:password] = @default_options[:redis_password] if @default_options[:redis_password].is_a?(String)
|
39
|
+
|
40
|
+
# create connection to our redis database and ensure we're connected
|
41
|
+
@redis = ::Redis.new(redis_options.merge(:thread_safe => true))
|
42
|
+
@redis.ping
|
43
|
+
|
44
|
+
# store session keys under specified namespace (if any)
|
45
|
+
if @default_options[:redis_namespace]
|
46
|
+
@redis = ::Redis::Namespace.new(@default_options[:redis_namespace], :redis => @redis)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def generate_sid
|
52
|
+
# Atomically test if sid available and reserve it if it is
|
53
|
+
sid = super # first iteration
|
54
|
+
sid = super until @redis.setnx(sid, Marshal.dump({}))
|
55
|
+
# Set our allocated sid to expire if it isn't used any time soon
|
56
|
+
expiry = (@default_options[:expire_after] || 0).to_i
|
57
|
+
@redis.expire(sid, expiry <= 0 ? 600 : expiry)
|
58
|
+
sid
|
59
|
+
end
|
60
|
+
|
61
|
+
def get_session(env, sid)
|
62
|
+
with_lock(env, [nil, {}]) do
|
63
|
+
if sid and @redis.exists(sid)
|
64
|
+
session = Marshal.load(@redis.get(sid))
|
65
|
+
else
|
66
|
+
sid, session = generate_sid, {}
|
67
|
+
end
|
68
|
+
[sid, session]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def set_session(env, session_id, new_session, options)
|
73
|
+
expiry = options[:expire_after]
|
74
|
+
expiry = expiry.nil? ? 0 : expiry + 1
|
75
|
+
|
76
|
+
with_lock(env, false) do
|
77
|
+
@redis.del(session_id)
|
78
|
+
@redis.set(session_id, Marshal.dump(new_session.to_hash))
|
79
|
+
@redis.expire(session_id, expiry) if expiry > 0
|
80
|
+
session_id
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def destroy_session(env, session_id, options)
|
85
|
+
with_lock(env) do
|
86
|
+
@redis.del(session_id)
|
87
|
+
generate_sid unless options[:drop]
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def with_lock(env, default = nil)
|
92
|
+
@mutex.lock if env["rack.multithread"]
|
93
|
+
yield
|
94
|
+
rescue
|
95
|
+
default
|
96
|
+
ensure
|
97
|
+
@mutex.unlock if @mutex.locked?
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "redrack/session/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "redrack-session"
|
7
|
+
s.version = Redrack::Session::VERSION
|
8
|
+
s.authors = ["Kendall Gifford"]
|
9
|
+
s.email = ["zettabyte@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/zettabyte/redrack-session"
|
11
|
+
s.summary = "Redis session store for rack applications."
|
12
|
+
s.description = <<-DESC.gsub(/^\s*/, "")
|
13
|
+
Redis session store for rack applications.
|
14
|
+
|
15
|
+
This was inspired by the Rack::Session::Memcached session store.
|
16
|
+
DESC
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_development_dependency "bundler", "~> 1.0.21"
|
23
|
+
s.add_development_dependency "rspec", "~> 2.7.0"
|
24
|
+
s.add_development_dependency "rack-test", "~> 0.6.1"
|
25
|
+
s.add_runtime_dependency "rack", "~> 1.3.5"
|
26
|
+
s.add_runtime_dependency "redis", "~> 2.2.2"
|
27
|
+
s.add_runtime_dependency "redis-namespace", "~> 1.1.0"
|
28
|
+
end
|
@@ -0,0 +1,195 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.expand_path("../../spec_helper", __FILE__)
|
3
|
+
|
4
|
+
module Redrack
|
5
|
+
describe Session do
|
6
|
+
|
7
|
+
it "faults on no connection" do
|
8
|
+
expect { app(:redis_host => "nosuchserver") }.to raise_error(SocketError)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "connects to existing server" do
|
12
|
+
expect { app() }.to_not raise_error
|
13
|
+
end
|
14
|
+
|
15
|
+
it "passes options to Redis" do
|
16
|
+
main_app = Redrack::Session::Middleware.new(Redrack::Session::RackApp.new, :redis_namespace => "test:rack:session")
|
17
|
+
main_app.redis.namespace.should == "test:rack:session"
|
18
|
+
end
|
19
|
+
|
20
|
+
it "creates a new cookie" do
|
21
|
+
get "/"
|
22
|
+
rack_mock_session.cookie_jar["rack.session"].should be_a(String)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "determines session from a cookie" do
|
26
|
+
get("/add") { |r| r.body.should match /Session Counter: 1/ }
|
27
|
+
get("/add") { |r| r.body.should match /Session Counter: 2/ }
|
28
|
+
get("/add") { |r| r.body.should match /Session Counter: 3/ }
|
29
|
+
end
|
30
|
+
|
31
|
+
it "determines session only from a cookie by default" do
|
32
|
+
get "/add"
|
33
|
+
sid = rack_mock_session.cookie_jar["rack.session"]
|
34
|
+
clear_cookies
|
35
|
+
get("/add", "rack.session" => sid) { |r| r.body.should match /Session Counter: 1/ }
|
36
|
+
sid = rack_mock_session.cookie_jar["rack.session"]
|
37
|
+
clear_cookies
|
38
|
+
get("/add", "rack.session" => sid) { |r| r.body.should match /Session Counter: 1/ }
|
39
|
+
end
|
40
|
+
|
41
|
+
it "determines session from params" do
|
42
|
+
mock_session = Rack::MockSession.new(app(:cookie_only => false))
|
43
|
+
session = Rack::Test::Session.new(mock_session)
|
44
|
+
session.get "/add"
|
45
|
+
sid = mock_session.cookie_jar["rack.session"]
|
46
|
+
session.clear_cookies
|
47
|
+
session.get("/add", "rack.session" => sid) { |r| r.body.should match /Session Counter: 2/ }
|
48
|
+
session.get("/add", "rack.session" => sid) { |r| r.body.should match /Session Counter: 3/ }
|
49
|
+
end
|
50
|
+
|
51
|
+
it "survives nonexistant cookies" do
|
52
|
+
rack_mock_session.set_cookie("rack.session=badsessionid")
|
53
|
+
get("/add") { |r| r.body.should match /Session Counter: 1/ }
|
54
|
+
rack_mock_session.cookie_jar["rack.session"].should_not match /badsessionid/
|
55
|
+
end
|
56
|
+
|
57
|
+
it "maintains freshness" do
|
58
|
+
mock_session = Rack::MockSession.new(app(:expire_after => 3))
|
59
|
+
session = Rack::Test::Session.new(mock_session)
|
60
|
+
session.get("/add") { |r| r.body.should match /Session Counter: 1/ }
|
61
|
+
sid = mock_session.cookie_jar["rack.session"]
|
62
|
+
session.get("/add") { |r| r.body.should match /Session Counter: 2/ }
|
63
|
+
mock_session.cookie_jar["rack.session"].should == sid
|
64
|
+
puts "Sleeping to expire session..." if $DEBUG
|
65
|
+
sleep 5
|
66
|
+
session.get("/add") { |r| r.body.should match /Session Counter: 1/ }
|
67
|
+
mock_session.cookie_jar["rack.session"].should_not == sid
|
68
|
+
end
|
69
|
+
|
70
|
+
it "does not send the same session id if it did not change" do
|
71
|
+
get("/add") { |r| r.body.should match /Session Counter: 1/ }
|
72
|
+
sid = rack_mock_session.cookie_jar["rack.session"]
|
73
|
+
get("/add") do |r|
|
74
|
+
r.headers["Set-Cookie"].should be_nil
|
75
|
+
r.body.should match /Session Counter: 2/
|
76
|
+
end
|
77
|
+
get("/add") do |r|
|
78
|
+
r.headers["Set-Cookie"].should be_nil
|
79
|
+
r.body.should match /Session Counter: 3/
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it "deletes cookies with :drop option" do
|
84
|
+
main_mock_session = Rack::MockSession.new(app())
|
85
|
+
drop_mock_session = Rack::MockSession.new(drop_app())
|
86
|
+
main_session = Rack::Test::Session.new(main_mock_session)
|
87
|
+
drop_session = Rack::Test::Session.new(drop_mock_session)
|
88
|
+
main_session.get("/add") { |r| r.body.should match /Session Counter: 1/ }
|
89
|
+
sid = main_mock_session.cookie_jar["rack.session"]
|
90
|
+
drop_mock_session.set_cookie("rack.session=#{sid}")
|
91
|
+
drop_session.get("/add") do |r|
|
92
|
+
r.header["Set-Cookie"].should be_nil
|
93
|
+
r.body.should match /Session Counter: 2/
|
94
|
+
end
|
95
|
+
main_session.get("/add") { |r| r.body.should match /Session Counter: 1/ }
|
96
|
+
main_mock_session.cookie_jar["rack.session"].should_not == sid
|
97
|
+
end
|
98
|
+
|
99
|
+
it "provides new session id with :renew option" do
|
100
|
+
main_mock_session = Rack::MockSession.new(app())
|
101
|
+
renew_mock_session = Rack::MockSession.new(renew_app())
|
102
|
+
main_session = Rack::Test::Session.new(main_mock_session)
|
103
|
+
renew_session = Rack::Test::Session.new(renew_mock_session)
|
104
|
+
main_session.get("/add") { |r| r.body.should match /Session Counter: 1/ }
|
105
|
+
old_sid = main_mock_session.cookie_jar["rack.session"]
|
106
|
+
renew_mock_session.set_cookie("rack.session=#{old_sid}")
|
107
|
+
renew_session.get("/add") { |r| r.body.should match /Session Counter: 2/ }
|
108
|
+
new_sid = renew_mock_session.cookie_jar["rack.session"]
|
109
|
+
new_sid.should_not == old_sid
|
110
|
+
main_mock_session.clear_cookies
|
111
|
+
main_mock_session.set_cookie("rack.session=#{new_sid}")
|
112
|
+
main_session.get("/add") { |r| r.body.should match /Session Counter: 3/ }
|
113
|
+
main_mock_session.clear_cookies
|
114
|
+
main_mock_session.set_cookie("rack.session=#{old_sid}")
|
115
|
+
main_session.get("/add") { |r| r.body.should match /Session Counter: 1/ }
|
116
|
+
end
|
117
|
+
|
118
|
+
it "omits cookie with :defer option" do
|
119
|
+
mock_session = Rack::MockSession.new(defer_app())
|
120
|
+
session = Rack::Test::Session.new(mock_session)
|
121
|
+
session.get("/add") { |r| r.body.should match /Session Counter: 1/ }
|
122
|
+
mock_session.cookie_jar["rack.session"].should be_nil
|
123
|
+
end
|
124
|
+
|
125
|
+
it "updates deep hashes correctly" do
|
126
|
+
main_app = Redrack::Session::Middleware.new(Redrack::Session::RackApp.new)
|
127
|
+
mock_session = Rack::MockSession.new(main_app)
|
128
|
+
session = Rack::Test::Session.new(mock_session)
|
129
|
+
session.get("/set-deep-hash")
|
130
|
+
sid = mock_session.cookie_jar["rack.session"]
|
131
|
+
first = main_app.redis.get(sid)
|
132
|
+
session.get("/mutate-deep-hash")
|
133
|
+
first.should_not equal(main_app.redis.get(sid))
|
134
|
+
end
|
135
|
+
|
136
|
+
it "cleanly merges sessions when multithreaded" do
|
137
|
+
mutex = Mutex.new
|
138
|
+
count = 0
|
139
|
+
main_app = Redrack::Session::Middleware.new(Redrack::Session::RackApp.new)
|
140
|
+
main_mock_session = Rack::MockSession.new(main_app)
|
141
|
+
thread_mock_session = Rack::MockSession.new(threaded_app())
|
142
|
+
main_session = Rack::Test::Session.new(main_mock_session)
|
143
|
+
thread_session = Rack::Test::Session.new(thread_mock_session)
|
144
|
+
random_thread_count = lambda { rand(7).to_i + 5 }
|
145
|
+
main_session.get("/add") { |r| r.body.should match /Session Counter: 1/ }
|
146
|
+
sid = main_mock_session.cookie_jar["rack.session"]
|
147
|
+
thread_mock_session.set_cookie("rack.session=#{sid}")
|
148
|
+
|
149
|
+
# do several, multi-threaded requests...
|
150
|
+
num_threads = random_thread_count.call
|
151
|
+
threads = (1..num_threads).map do |i|
|
152
|
+
Thread.new(thread_session, thread_mock_session) do |session, mock|
|
153
|
+
mutex.synchronize { count += 1 }
|
154
|
+
session.get("/add", {}, "rack.multithreaded" => true)
|
155
|
+
[mock.last_response.body, mock.cookie_jar["rack.session"]]
|
156
|
+
end
|
157
|
+
end
|
158
|
+
sleep 1 until mutex.synchronize { count == num_threads }
|
159
|
+
threads.each { |thread| sleep 1 until thread.stop? }
|
160
|
+
requests = threads.reverse.map { |t| t.run.join.value }
|
161
|
+
count = 2
|
162
|
+
requests.each do |response|
|
163
|
+
response.first.should match /Session Counter: #{count}/
|
164
|
+
response.last.should == sid
|
165
|
+
count += 1
|
166
|
+
end
|
167
|
+
|
168
|
+
# verify all our timestamps were written by the threaded_app
|
169
|
+
session = Marshal.load(main_app.redis.get(sid))
|
170
|
+
session.size.should == num_threads + 1
|
171
|
+
session["counter"].should == num_threads + 1
|
172
|
+
|
173
|
+
# test multi-threaded session element deletion
|
174
|
+
old_threads = num_threads
|
175
|
+
num_threads = random_thread_count.call
|
176
|
+
threads = (1..num_threads).map do |i|
|
177
|
+
Thread.new(main_session, main_mock_session) do |session, mock|
|
178
|
+
session.get("/drop-counter", {}, "rack.multithreaded" => true)
|
179
|
+
[mock.last_response.body, mock.cookie_jar["rack.session"]]
|
180
|
+
end
|
181
|
+
end
|
182
|
+
requests = threads.reverse.map { |t| t.join.value }
|
183
|
+
requests.each do |response|
|
184
|
+
response.first.should match /Session Foo: bar/
|
185
|
+
response.last.should == sid
|
186
|
+
end
|
187
|
+
session = Marshal.load(main_app.redis.get(sid))
|
188
|
+
session.size.should == old_threads + 1
|
189
|
+
session["foo"].should == "bar"
|
190
|
+
session["counter"].should be_nil
|
191
|
+
|
192
|
+
end
|
193
|
+
|
194
|
+
end
|
195
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'rubygems'
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'redrack-session'
|
5
|
+
require 'rspec'
|
6
|
+
|
7
|
+
#
|
8
|
+
# Load all support files...
|
9
|
+
#
|
10
|
+
Dir[File.join(File.expand_path("..", __FILE__), "support", "**", "*.rb")].each { |f| require f }
|
11
|
+
|
12
|
+
#
|
13
|
+
# Configure RSpec to include Rack::Test methods and to always provide easy
|
14
|
+
# access to an instance of our test rack app, wrapped with the middleware we're
|
15
|
+
# testing (Redrack::Session::Middleware) and Rack::Lint.
|
16
|
+
#
|
17
|
+
RSpec.configure do |config|
|
18
|
+
require 'rack/test'
|
19
|
+
config.include Rack::Test::Methods
|
20
|
+
|
21
|
+
def app(options = {})
|
22
|
+
Redrack::Session::RackApp.app(options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def drop_app(options = {})
|
26
|
+
Redrack::Session::RackApp.drop_app(options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def renew_app(options = {})
|
30
|
+
Redrack::Session::RackApp.renew_app(options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def defer_app(options = {})
|
34
|
+
Redrack::Session::RackApp.defer_app(options)
|
35
|
+
end
|
36
|
+
|
37
|
+
def threaded_app(options = {})
|
38
|
+
Redrack::Session::RackApp.threaded_app(options)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'redrack-session'
|
3
|
+
|
4
|
+
#
|
5
|
+
# Define simple rack application that expects to be used with the
|
6
|
+
# Redrack::Session middleware in an underlying layer for testing our middleware.
|
7
|
+
#
|
8
|
+
module Redrack
|
9
|
+
module Session
|
10
|
+
class RackApp
|
11
|
+
|
12
|
+
#
|
13
|
+
# Rack application definition
|
14
|
+
#
|
15
|
+
def call(env)
|
16
|
+
request = Rack::Request.new(env)
|
17
|
+
session = env["rack.session"]
|
18
|
+
session["counter"] ||= 0 # Always at least initialize the session
|
19
|
+
if request.path == "/add"
|
20
|
+
session["counter"] += 1 # Add one to counter
|
21
|
+
elsif request.path == "/set-deep-hash"
|
22
|
+
session[:a] = :b
|
23
|
+
session[:c] = { :d => :e }
|
24
|
+
session[:f] = { :g => { :h => :i } }
|
25
|
+
elsif request.path == "/mutate-deep-hash"
|
26
|
+
session[:f][:g][:h] = :j
|
27
|
+
elsif request.path == "/drop-counter"
|
28
|
+
session.delete "counter"
|
29
|
+
session["foo"] = "bar"
|
30
|
+
end
|
31
|
+
Rack::Response.new do |response|
|
32
|
+
response.write "<!doctype html>\n<html>\n<head><title>Redrack::Session Test App</title></head>\n<body>\n<pre>"
|
33
|
+
if session["counter"]
|
34
|
+
response.write "Session Counter: #{session["counter"]}"
|
35
|
+
elsif session["foo"]
|
36
|
+
response.write "Session Foo: #{session["foo"]}"
|
37
|
+
else
|
38
|
+
response.write "Nothing"
|
39
|
+
end
|
40
|
+
response.write "\n</pre>\n</body>\n</html>\n"
|
41
|
+
end.finish
|
42
|
+
end
|
43
|
+
|
44
|
+
#
|
45
|
+
# Define helper class method that creates an instance of our simple rack
|
46
|
+
# application with our session middleware available.
|
47
|
+
#
|
48
|
+
def self.app(options = {})
|
49
|
+
Rack::Lint.new(Redrack::Session::Middleware.new(new, options))
|
50
|
+
end
|
51
|
+
|
52
|
+
#
|
53
|
+
# Define helper class that gets an instance of our simple rack app wrapped
|
54
|
+
# in a context that sets "rack.session.options" :drop setting to true.
|
55
|
+
#
|
56
|
+
def self.drop_app(options = {})
|
57
|
+
main_app = new()
|
58
|
+
drop_middleware = proc { |env| env["rack.session.options"][:drop] = true; main_app.call(env) }
|
59
|
+
Rack::Lint.new(Rack::Utils::Context.new(Redrack::Session::Middleware.new(main_app, options), drop_middleware))
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# Define helper class that gets an instance of our simple rack app wrapped
|
64
|
+
# in a context that sets "rack.session.options" :renew setting to true.
|
65
|
+
#
|
66
|
+
def self.renew_app(options = {})
|
67
|
+
main_app = new()
|
68
|
+
renew_middleware = proc { |env| env["rack.session.options"][:renew] = true; main_app.call(env) }
|
69
|
+
Rack::Lint.new(Rack::Utils::Context.new(Redrack::Session::Middleware.new(main_app, options), renew_middleware))
|
70
|
+
end
|
71
|
+
|
72
|
+
#
|
73
|
+
# Define helper class that gets an instance of our simple rack app wrapped
|
74
|
+
# in a context that sets "rack.session.options" :defer setting to true.
|
75
|
+
#
|
76
|
+
def self.defer_app(options = {})
|
77
|
+
main_app = new()
|
78
|
+
defer_middleware = proc { |env| env["rack.session.options"][:defer] = true; main_app.call(env) }
|
79
|
+
Rack::Lint.new(Rack::Utils::Context.new(Redrack::Session::Middleware.new(main_app, options), defer_middleware))
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# Define helper class that gets an instance of our simple rack app wrapped
|
84
|
+
# in a context that emulates the disconjoinment of multithreaded access.
|
85
|
+
#
|
86
|
+
def self.threaded_app(options = {})
|
87
|
+
main_app = new()
|
88
|
+
threaded_middleware = proc do |env|
|
89
|
+
Thread.stop
|
90
|
+
env["rack.session"][(Time.now.usec * rand).to_i] = true
|
91
|
+
main_app.call(env)
|
92
|
+
end
|
93
|
+
Rack::Lint.new(Rack::Utils::Context.new(Redrack::Session::Middleware.new(main_app, options), threaded_middleware))
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# If "running" this file, then run an instance of Redrack::Session::RackApp in
|
102
|
+
# WEBrick. Got this "trick" from the Rack::Lobster example rack app.
|
103
|
+
#
|
104
|
+
if $0 == __FILE__
|
105
|
+
require 'rack'
|
106
|
+
|
107
|
+
# Exit when asked...
|
108
|
+
%w{ HUP INT TERM }.each do |sig|
|
109
|
+
trap(sig) do
|
110
|
+
STDERR.puts "Recieved signal: #{sig}"
|
111
|
+
Rack::Handler::WEBrick.shutdown
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Run WEBrick server...
|
116
|
+
Rack::Handler::WEBrick.run(
|
117
|
+
Rack::ShowExceptions.new(Redrack::Session::RackApp.app),
|
118
|
+
:Port => 3000 # use default rails development port
|
119
|
+
)
|
120
|
+
|
121
|
+
end
|
metadata
ADDED
@@ -0,0 +1,131 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redrack-session
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Kendall Gifford
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-11-02 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: bundler
|
16
|
+
requirement: &16870880 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.0.21
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *16870880
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rspec
|
27
|
+
requirement: &16870340 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ~>
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.7.0
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *16870340
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rack-test
|
38
|
+
requirement: &16869800 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 0.6.1
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *16869800
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: rack
|
49
|
+
requirement: &16869260 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 1.3.5
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *16869260
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: redis
|
60
|
+
requirement: &16868580 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ~>
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: 2.2.2
|
66
|
+
type: :runtime
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *16868580
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: redis-namespace
|
71
|
+
requirement: &16867500 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ~>
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 1.1.0
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *16867500
|
80
|
+
description: ! 'Redis session store for rack applications.
|
81
|
+
|
82
|
+
This was inspired by the Rack::Session::Memcached session store.
|
83
|
+
|
84
|
+
'
|
85
|
+
email:
|
86
|
+
- zettabyte@gmail.com
|
87
|
+
executables: []
|
88
|
+
extensions: []
|
89
|
+
extra_rdoc_files: []
|
90
|
+
files:
|
91
|
+
- .gitignore
|
92
|
+
- Gemfile
|
93
|
+
- LICENSE
|
94
|
+
- README.md
|
95
|
+
- Rakefile
|
96
|
+
- lib/redrack-session.rb
|
97
|
+
- lib/redrack/session.rb
|
98
|
+
- lib/redrack/session/middleware.rb
|
99
|
+
- lib/redrack/session/version.rb
|
100
|
+
- redrack-session.gemspec
|
101
|
+
- spec/redrack/session_spec.rb
|
102
|
+
- spec/spec_helper.rb
|
103
|
+
- spec/support/rack_app.rb
|
104
|
+
homepage: https://github.com/zettabyte/redrack-session
|
105
|
+
licenses: []
|
106
|
+
post_install_message:
|
107
|
+
rdoc_options: []
|
108
|
+
require_paths:
|
109
|
+
- lib
|
110
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
111
|
+
none: false
|
112
|
+
requirements:
|
113
|
+
- - ! '>='
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
requirements: []
|
123
|
+
rubyforge_project:
|
124
|
+
rubygems_version: 1.8.10
|
125
|
+
signing_key:
|
126
|
+
specification_version: 3
|
127
|
+
summary: Redis session store for rack applications.
|
128
|
+
test_files:
|
129
|
+
- spec/redrack/session_spec.rb
|
130
|
+
- spec/spec_helper.rb
|
131
|
+
- spec/support/rack_app.rb
|