rack-redic 1.3.0 → 2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: bf92c89449d8d8709de10c6764860f6bf170e5d0
4
- data.tar.gz: bf999b9c9675ad376c52ae1eb6df3042f6c4d7a1
2
+ SHA256:
3
+ metadata.gz: a16047a76a6da5329f8a6cff500f6b158f35e8d9a15ac918d57086cd7b00b6ea
4
+ data.tar.gz: 828029bedc09226ed4309177b0cfd9c41d61e134b889cdba3528abcd5ec08c63
5
5
  SHA512:
6
- metadata.gz: 245c12a951c0c0c395064a64ab594f2d5e8774979bdcb3feb23fc855666612e50db63eead26aa5df911287a7c1f184918ea1a2cc69ae01f07f52e8fa17c0807b
7
- data.tar.gz: 5a0d6bff6ad7faae40884f75174d43728e87ea4526f73eb0b826ac414a2f05851b4c10f643ee0c53fb1bae5faa6ae6fe58f52f6222c1df731ff33d331b9f8e3a
6
+ metadata.gz: d07c50cbabcffc62ddf7ffbb3d4cf562d47e31c54a2f34efad4f6f522d458fe942e23aa68eadda9cd69ad1b793b704034bd20492ad5ef142a42052f5395fdc17
7
+ data.tar.gz: 144ab76c0f54775dcffc752cb7fb4a84812d7583bd3fc338e119001d1b4b7e51da3feba148997e0b1acbb59d04dbc97b10e522c8c84d69e55e9293e85461d27a
data/README.org ADDED
@@ -0,0 +1,105 @@
1
+ * Rack::Session::Redic
2
+
3
+ =Rack::Session::Redic= provides simple cookie based session management. Session
4
+ data is stored in [[http://redis.io][Redis]] via the [[https://github.com/amakawa/redic][Redic]] gem. The corresponding session key is
5
+ maintained in the cookie.
6
+
7
+ Options include:
8
+
9
+ - =:marshaller= - You may optionally supply the class/module you would
10
+ like to use when marshalling objects in and out of Redis. All that is
11
+ required is that this class respond to the =load= and =dump= methods,
12
+ returning the session hash and a string respectively.
13
+ - =:url= - Additionally, you may pass in the URL for your Redis server.
14
+ The default URL is fetched from the =ENV= as =REDIS_URL= in keeping
15
+ with Heroku and others' practices.
16
+ - =:expire_after= - Finally, expiration will be passed to the Redis
17
+ server via the 'EX' option on 'SET'. Expiration should be in seconds,
18
+ just like Rack's default handling of the =:expire_after= option. This
19
+ option will refresh the expiration set in Redis with each request.
20
+
21
+ Any other options will get passed to =Rack::Session::Abstract::Persisted=.
22
+
23
+ ** Installation
24
+
25
+ Add this line to your application's Gemfile or gems.rb file:
26
+
27
+ #+begin_src ruby
28
+ gem 'rack-redic', require: 'rack/session/redic'
29
+ #+end_src
30
+
31
+ And then execute:
32
+
33
+ #+begin_src sh
34
+ bundle
35
+ #+end_src
36
+
37
+ Or install it yourself as:
38
+
39
+ #+begin_src sh
40
+ gem install rack-redic
41
+ #+end_src
42
+
43
+ ** Usage
44
+
45
+ Anywhere in your Rack application just add:
46
+
47
+ #+begin_src ruby
48
+ # Most basic usage.
49
+ use Rack::Session::Redic
50
+
51
+ # Optionally pass in a marshaller.
52
+ use Rack::Session::Redic, marshaller: Oj
53
+
54
+ # And/or pass in the URL of your Redis server.
55
+ use Rack::Session::Redic, marshaller: Oj, url: 'redis://host:port'
56
+
57
+ # And/or pass in the expiration. (1_800 is 30 minutes in seconds)
58
+ use Rack::Session::Redic, marshaller: Oj, url: 'redis://host:port', expire_after: 1_800
59
+ #+end_src
60
+
61
+ ** Custom Marshallers
62
+
63
+ Since the class/module passed as =:marshaller= only needs to respond to the
64
+ methods =load= and =dump=, you can create any kind of marshaller you would like.
65
+ I've included examples for MessagePack and Oj here as reference.
66
+
67
+ *** [[https://github.com/msgpack/msgpack-ruby][MessagePack]]
68
+
69
+ #+begin_src ruby
70
+ require 'msgpack'
71
+
72
+ MessagePack::DefaultFactory.register_type(0x00, Symbol)
73
+
74
+ module MessagePackMarshaller
75
+ def self.dump(object)
76
+ MessagePack.pack(object)
77
+ end
78
+
79
+ def self.load(string)
80
+ MessagePack.unpack(string)
81
+ end
82
+ end
83
+ #+end_src
84
+
85
+ Then, while adding it your Rack application.
86
+
87
+ #+BEGIN_SRC ruby
88
+ use Rack::Session::Redic, marshaller: MessagePackMarshaller
89
+ #+END_SRC
90
+
91
+ *NOTE:* MessagePack [[https://github.com/msgpack/msgpack-ruby#serializing-and-deserializing-symbols][serializes symbols as strings by default]] so I suggest
92
+ customizing that behavior per their instructions. You can [[https://github.com/msgpack/msgpack/blob/master/spec.md#types-extension-type][read more about
93
+ MessagePack's extension formats here]].
94
+
95
+ *** [[https://github.com/ohler55/oj][Oj]]
96
+
97
+ Oj responds to =load= and =dump= by default so there's no adapter module needed.
98
+
99
+ #+begin_src ruby
100
+ use Rack::Session::Redic, marshaller: Oj
101
+ #+end_src
102
+
103
+ ** License
104
+
105
+ The gem is available as open source under the terms of the [[http://opensource.org/licenses/MIT][MIT License]].
@@ -1,11 +1,10 @@
1
- # encoding: UTF-8
2
1
  # frozen_string_literal: true
3
2
  require 'rack/session/abstract/id'
4
3
  require 'redic'
4
+ require 'securerandom'
5
5
 
6
6
  module Rack
7
7
  module Session
8
- #
9
8
  # Rack::Session::Redic provides simple cookie based session management.
10
9
  # Session data is stored in Redis via the Redic gem. The corresponding
11
10
  # session key is maintained in the cookie.
@@ -25,152 +24,95 @@ module Rack
25
24
  # refresh the expiration set in Redis with each request.
26
25
  #
27
26
  # Any other options will get passed to Rack::Session::Abstract::Persisted.
28
- #
29
27
  class Redic < Abstract::Persisted
30
- REDIS_URL = 'REDIS_URL'.freeze
31
-
28
+ HASH = {}.freeze
29
+
30
+ # Redis commands.
31
+ DELETE = 'DEL'
32
+ EX = 'EX'
33
+ EXISTS = 'EXISTS'
34
+ GET = 'GET'
35
+ SET = 'SET'
36
+
37
+ # Assorted.
38
+ REDIS_URL = 'REDIS_URL'
39
+ ZERO = 0
40
+
41
+ # Access the storage interface directly. Needed for testing.
42
+ #
43
+ # @return [Redic]
32
44
  attr_reader :storage
33
45
 
34
- def initialize(app, options = {})
35
- super
46
+ def initialize(app, options = HASH)
47
+ super(app, options)
36
48
 
37
- @mutex = Mutex.new
38
- @storage = Storage.new(
39
- options[:expire_after],
40
- options.fetch(:marshaller) { Marshal },
41
- options.fetch(:url) { ENV.fetch(REDIS_URL) }
42
- )
49
+ @expires = options[:expire_after]
50
+ @marshaller = options.fetch(:marshaller) { Marshal }
51
+ @storage = ::Redic.new(options.fetch(:url) { ENV.fetch(REDIS_URL) })
43
52
  end
44
53
 
45
- # Only accept a generated session ID if it doesn't exist.
54
+ # Generate a session ID that doesn't already exist.
55
+ #
56
+ # Based on Rack::Session::Abstract::Persisted#generate_sid and
57
+ # Rack::Session::Memcache#generate_sid but without the conditional check.
58
+ # We always generate the session ID from SecureRandom#hex.
59
+ #
60
+ # @return [String]
46
61
  def generate_sid
47
62
  loop do
48
- sid = super
49
- break sid unless @storage.exists?(sid)
63
+ session_id = SecureRandom.hex(@sid_length)
64
+ break session_id unless @storage.call(EXISTS, session_id) != ZERO
50
65
  end
51
66
  end
52
67
 
53
68
  # Find the session (or generate a blank one).
54
- def find_session(_req, sid)
55
- @mutex.synchronize do
56
- unless sid && session = @storage.get(sid)
57
- sid, session = generate_sid, {}
58
- end
59
-
60
- [sid, session]
69
+ def find_session(_req, session_id)
70
+ unless session_id && (session = deserialize(@storage.call(GET, session_id)))
71
+ session_id, session = generate_sid, {} # rubocop:disable Style/ParallelAssignment
61
72
  end
73
+
74
+ [session_id, session]
62
75
  end
63
76
 
64
77
  # Write the session.
65
- def write_session(_req, session_id, new_session, _options)
66
- @mutex.synchronize do
67
- @storage.set(session_id, new_session)
78
+ def write_session(_req, session_id, session_data, _options)
79
+ arguments = [SET, session_id, serialize(session_data)]
80
+ arguments.push(EX, @expires) if @expires
68
81
 
69
- session_id
70
- end
82
+ @storage.call(*arguments)
83
+
84
+ session_id
71
85
  end
72
86
 
73
87
  # Kill the session.
74
88
  def delete_session(_req, session_id, options)
75
- @mutex.synchronize do
76
- @storage.delete(session_id)
77
- generate_sid unless options[:drop]
78
- end
79
- end
80
-
81
- # A wrapper around Redic to simplify calls.
82
- class Storage
83
- # Redis commands.
84
- DELETE = 'DEL'.freeze
85
- EX = 'EX'.freeze
86
- EXISTS = 'EXISTS'.freeze
87
- GET = 'GET'.freeze
88
- SET = 'SET'.freeze
89
-
90
- # Assorted.
91
- ZERO = 0
92
-
93
- # @param expires [Integer]
94
- # The number of seconds for Redis to retain keys.
95
- # @param marshaller [#dump, #load]
96
- # The module or class used to marshal objects. It must respond to
97
- # #dump and #load.
98
- # @param url [String]
99
- # The URL to access Redis at.
100
- def initialize(expires, marshaller, url)
101
- @expires = expires
102
- @marshaller = marshaller
103
- @storage = ::Redic.new(url)
104
- end
89
+ @storage.call(DELETE, session_id)
105
90
 
106
- # Check for an identifier's existence.
107
- #
108
- # @param id [String]
109
- # The key to check for.
110
- # @return [Boolean]
111
- def exists?(id)
112
- @storage.call(EXISTS, id) != ZERO
113
- end
114
-
115
- # Retrieve an object.
116
- #
117
- # @param id [String]
118
- # The key in Redis to retrieve from.
119
- # @return [Object, nil]
120
- # The object stored at the identifier provided, or nil.
121
- def get(id)
122
- deserialize(@storage.call(GET, id))
123
- end
124
-
125
- # Store an object.
126
- #
127
- # @param id [String]
128
- # The key to use to store the object.
129
- # @param object [Object]
130
- # Any object that can be serialized.
131
- # @return [String]
132
- # See {https://redis.io/commands/set#return-value Redis' docs for more}.
133
- def set(id, object)
134
- arguments = [SET, id, serialize(object)]
135
- arguments += [EX, @expires] if @expires
136
-
137
- @storage.call(*arguments)
138
- end
139
-
140
- # Remove an object.
141
- #
142
- # @param id [String]
143
- # The key to delete.
144
- # @return [Integer]
145
- # The number of keys that were deleted. See
146
- # {https://redis.io/commands/del#return-value Redis' docs for more}.
147
- def delete(id)
148
- @storage.call(DELETE, id)
149
- end
91
+ generate_sid unless options[:drop]
92
+ end
150
93
 
151
- private
94
+ private
152
95
 
153
- # Serialize an object using our marshaller.
154
- #
155
- # @param object [Object]
156
- # @return [String]
157
- # The object serialized by the marshaller.
158
- def serialize(object)
159
- @marshaller.dump(object)
160
- end
96
+ # Serialize an object using our marshaller.
97
+ #
98
+ # @param object [Object]
99
+ # @return [String]
100
+ # The object as serialized by the marshaller.
101
+ def serialize(object)
102
+ @marshaller.dump(object)
103
+ end
161
104
 
162
- # Deserialize a string back into an object.
163
- #
164
- # @param string [String]
165
- # @return [Object, nil]
166
- # Returns the object as loaded by the marshaller, or nil.
167
- def deserialize(string)
168
- @marshaller.load(string) if string
169
-
170
- # In the case that loading fails, return a nil.
171
- rescue
172
- nil
173
- end
105
+ # Deserialize a string back into an object.
106
+ #
107
+ # @param string [String]
108
+ # @return [Object, nil]
109
+ # Returns the object as loaded by the marshaller, or nil.
110
+ def deserialize(string)
111
+ @marshaller.load(string) if string
112
+
113
+ # In the case that loading fails, return a nil.
114
+ rescue # rubocop:disable Style/RescueStandardError
115
+ nil
174
116
  end
175
117
  end
176
118
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-redic
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 2.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Lecklider
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-12 00:00:00.000000000 Z
11
+ date: 2021-07-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: minitest
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -53,7 +67,35 @@ dependencies:
53
67
  - !ruby/object:Gem::Version
54
68
  version: '0'
55
69
  - !ruby/object:Gem::Dependency
56
- name: msgpack
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop-minitest
57
99
  requirement: !ruby/object:Gem::Requirement
58
100
  requirements:
59
101
  - - ">="
@@ -67,7 +109,35 @@ dependencies:
67
109
  - !ruby/object:Gem::Version
68
110
  version: '0'
69
111
  - !ruby/object:Gem::Dependency
70
- name: oj
112
+ name: rubocop-packaging
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop-performance
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop-rake
71
141
  requirement: !ruby/object:Gem::Requirement
72
142
  requirements:
73
143
  - - ">="
@@ -87,27 +157,15 @@ executables: []
87
157
  extensions: []
88
158
  extra_rdoc_files: []
89
159
  files:
90
- - ".editorconfig"
91
- - ".gitignore"
92
- - CODE_OF_CONDUCT.md
93
- - Gemfile
94
160
  - LICENSE.txt
95
- - Makefile
96
- - README.md
97
- - Rakefile
161
+ - README.org
98
162
  - lib/rack/session/redic.rb
99
- - rack-redic.gemspec
100
- - test/all.rb
101
- - test/helper.rb
102
- - test/session_redic_test.rb
103
- - test/storage_msgpack_test.rb
104
- - test/storage_oj_test.rb
105
- - test/storage_test.rb
106
- - test/support/storage_marshaller_interface.rb
107
163
  homepage: https://github.com/evanleck/rack-redic
108
164
  licenses:
109
165
  - MIT
110
- metadata: {}
166
+ metadata:
167
+ bug_tracker_uri: https://github.com/evanleck/rack-redic/issues
168
+ source_code_uri: https://github.com/evanleck/rack-redic
111
169
  post_install_message:
112
170
  rdoc_options: []
113
171
  require_paths:
@@ -116,23 +174,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
116
174
  requirements:
117
175
  - - ">="
118
176
  - !ruby/object:Gem::Version
119
- version: '0'
177
+ version: 2.5.0
120
178
  required_rubygems_version: !ruby/object:Gem::Requirement
121
179
  requirements:
122
180
  - - ">="
123
181
  - !ruby/object:Gem::Version
124
182
  version: '0'
125
183
  requirements: []
126
- rubyforge_project:
127
- rubygems_version: 2.6.13
184
+ rubygems_version: 3.2.22
128
185
  signing_key:
129
186
  specification_version: 4
130
187
  summary: Rack::Session in Redis via Redic
131
- test_files:
132
- - test/all.rb
133
- - test/helper.rb
134
- - test/session_redic_test.rb
135
- - test/storage_msgpack_test.rb
136
- - test/storage_oj_test.rb
137
- - test/storage_test.rb
138
- - test/support/storage_marshaller_interface.rb
188
+ test_files: []
data/.editorconfig DELETED
@@ -1,24 +0,0 @@
1
- # http://EditorConfig.org
2
- # This is the top most config file.
3
- root = true
4
-
5
- # All files
6
- [*]
7
-
8
- # Unix-style newlines with a newline ending every file
9
- end_of_line = lf
10
- insert_final_newline = true
11
-
12
- # Character set
13
- charset = utf-8
14
-
15
- # Trim extra whitespace.
16
- trim_trailing_whitespace = true
17
-
18
- # Soft tabs and 2 spaces.
19
- indent_style = space
20
- indent_size = 2
21
-
22
- # Makefiles use tabs
23
- [Makefile]
24
- indent_style = tab
data/.gitignore DELETED
@@ -1,9 +0,0 @@
1
- /.bundle/
2
- /.yardoc
3
- /Gemfile.lock
4
- /_yardoc/
5
- /coverage/
6
- /doc/
7
- /pkg/
8
- /spec/reports/
9
- /tmp/
data/CODE_OF_CONDUCT.md DELETED
@@ -1,49 +0,0 @@
1
- # Contributor Code of Conduct
2
-
3
- As contributors and maintainers of this project, and in the interest of
4
- fostering an open and welcoming community, we pledge to respect all people who
5
- contribute through reporting issues, posting feature requests, updating
6
- documentation, submitting pull requests or patches, and other activities.
7
-
8
- We are committed to making participation in this project a harassment-free
9
- experience for everyone, regardless of level of experience, gender, gender
10
- identity and expression, sexual orientation, disability, personal appearance,
11
- body size, race, ethnicity, age, religion, or nationality.
12
-
13
- Examples of unacceptable behavior by participants include:
14
-
15
- * The use of sexualized language or imagery
16
- * Personal attacks
17
- * Trolling or insulting/derogatory comments
18
- * Public or private harassment
19
- * Publishing other's private information, such as physical or electronic
20
- addresses, without explicit permission
21
- * Other unethical or unprofessional conduct
22
-
23
- Project maintainers have the right and responsibility to remove, edit, or
24
- reject comments, commits, code, wiki edits, issues, and other contributions
25
- that are not aligned to this Code of Conduct, or to ban temporarily or
26
- permanently any contributor for other behaviors that they deem inappropriate,
27
- threatening, offensive, or harmful.
28
-
29
- By adopting this Code of Conduct, project maintainers commit themselves to
30
- fairly and consistently applying these principles to every aspect of managing
31
- this project. Project maintainers who do not follow or enforce the Code of
32
- Conduct may be permanently removed from the project team.
33
-
34
- This code of conduct applies both within project spaces and in public spaces
35
- when an individual is representing the project or its community.
36
-
37
- Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
- reported by contacting a project maintainer at evan@lecklider.com. All
39
- complaints will be reviewed and investigated and will result in a response that
40
- is deemed necessary and appropriate to the circumstances. Maintainers are
41
- obligated to maintain confidentiality with regard to the reporter of an
42
- incident.
43
-
44
- This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
- version 1.3.0, available at
46
- [http://contributor-covenant.org/version/1/3/0/][version]
47
-
48
- [homepage]: http://contributor-covenant.org
49
- [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Load gem dependencies.
4
- gemspec
data/Makefile DELETED
@@ -1,18 +0,0 @@
1
- .DEFAULT_GOAL := test
2
- .PHONY: test
3
- .SILENT: test
4
-
5
- BE := bundle exec
6
-
7
- # Run our test suite.
8
- #
9
- # To test an individual file, pass the "file" argument like so:
10
- #
11
- # make test file=test/storage_test.rb
12
- #
13
- test:
14
- ifeq ($(origin file), undefined)
15
- $(BE) ruby -r ./test/helper.rb test/all.rb
16
- else
17
- $(BE) ruby -r ./test/helper.rb $(file)
18
- endif
data/README.md DELETED
@@ -1,97 +0,0 @@
1
- # Rack::Session::Redic
2
-
3
- `Rack::Session::Redic` provides simple cookie based session management. Session data is stored in [Redis](http://redis.io) via the [Redic](https://github.com/amakawa/redic) gem. The corresponding session key is maintained in the cookie.
4
-
5
- Options include:
6
-
7
- - `:marshaller` - You may optionally supply the class/module you would like to use when marshalling objects in and out of Redis. All that is required is that this class respond to the `load` and `dump` methods, returning the session hash and a string respectively.
8
- - `:url` - Addtionally, you may pass in the URL for your Redis server. The default URL is fetched from the `ENV` as `REDIS_URL` in keeping with Heroku and others' practices.
9
- - `:expire_after` - Finally, expiration will be passed to the Redis server via the 'EX' option on 'SET'. Expiration should be in seconds, just like Rack's default handling of the `:expire_after` option. This option will refresh the expiration set in Redis with each request.
10
-
11
- Any other options will get passed to `Rack::Session::Abstract::Persisted`.
12
-
13
-
14
- ## Installation
15
-
16
- Add this line to your application's Gemfile:
17
-
18
- ```ruby
19
- gem 'rack-redic', require: 'rack/session/redic'
20
- ```
21
-
22
- And then execute:
23
-
24
- $ bundle
25
-
26
- Or install it yourself as:
27
-
28
- $ gem install rack-redic
29
-
30
-
31
- ## Usage
32
-
33
- Anywhere in your Rack application just add:
34
-
35
- ```ruby
36
- # Most basic usage.
37
- use Rack::Session::Redic
38
-
39
- # Optionally pass in a marshaller.
40
- use Rack::Session::Redic, marshaller: Oj
41
-
42
- # And/or pass in the URL of your Redis server.
43
- use Rack::Session::Redic, marshaller: Oj, url: 'redis://host:port'
44
-
45
- # And/or pass in the expiration. (1_800 is 30 minutes in seconds)
46
- use Rack::Session::Redic, marshaller: Oj, url: 'redis://host:port', expire_after: 1_800
47
- ```
48
-
49
- ### Custom Marshallers
50
-
51
- Since the class/module passed as `:marshaller` only needs to respond to the methods `load` and `dump`, you can create any kind of marshaller you would like. I've included examples for MessagePack and Oj here as reference.
52
-
53
- #### [MessagePack](https://github.com/msgpack/msgpack-ruby)
54
-
55
- ```ruby
56
- require 'msgpack'
57
-
58
- MessagePack::DefaultFactory.register_type(0x00, Symbol)
59
-
60
- module MessagePackMarshaller
61
- def dump(object)
62
- MessagePack.pack(object)
63
- end
64
- module_function :dump
65
-
66
- def load(string)
67
- MessagePack.unpack(string)
68
- end
69
- module_function :load
70
- end
71
- ```
72
-
73
- Then, while adding it your Rack application.
74
-
75
- ```ruby
76
- use Rack::Session::Redic, marshaller: MessagePackMarshaller
77
- ```
78
-
79
- **NOTE:** MessagePack [serializes symbols as strings by default](https://github.com/msgpack/msgpack-ruby#serializing-and-deserializing-symbols) so I suggest customizing that behavior per their instructions. You can [read more about MessagePack's extension formats here](https://github.com/msgpack/msgpack/blob/master/spec.md#types-extension-type).
80
-
81
- #### [Oj](https://github.com/ohler55/oj)
82
-
83
- Oj responds to `load` and `dump` by default so there's no adapter module needed.
84
-
85
- ```ruby
86
- use Rack::Session::Redic, marshaller: Oj
87
- ```
88
-
89
-
90
- ## Contributing
91
-
92
- Bug reports and pull requests are welcome on GitHub at [https://github.com/evanleck/rack-redic](https://github.com/evanleck/rack-redic). This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
93
-
94
-
95
- ## License
96
-
97
- The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile DELETED
@@ -1,3 +0,0 @@
1
- # encoding: UTF-8
2
- # frozen_string_literal: true
3
- require 'bundler/gem_tasks'
data/rack-redic.gemspec DELETED
@@ -1,22 +0,0 @@
1
- # encoding: UTF-8
2
- # frozen_string_literal: true
3
- Gem::Specification.new do |spec|
4
- spec.name = 'rack-redic'
5
- spec.version = '1.3.0'
6
- spec.authors = ['Evan Lecklider']
7
- spec.email = ['evan@lecklider.com']
8
-
9
- spec.summary = 'Rack::Session in Redis via Redic'
10
- spec.description = 'Rack::Session in Redis via Redic'
11
- spec.homepage = 'https://github.com/evanleck/rack-redic'
12
- spec.license = 'MIT'
13
- spec.files = `git ls-files`.split("\n")
14
- spec.test_files = spec.files.grep(/^test/)
15
-
16
- spec.add_dependency 'rack'
17
- spec.add_dependency 'redic'
18
-
19
- spec.add_development_dependency 'minitest'
20
- spec.add_development_dependency 'msgpack'
21
- spec.add_development_dependency 'oj'
22
- end
data/test/all.rb DELETED
@@ -1,5 +0,0 @@
1
- # encoding: UTF-8
2
- # frozen_string_literal: true
3
-
4
- # Require all test files.
5
- Dir.glob('test/*.rb').each(&method(:require))
data/test/helper.rb DELETED
@@ -1,14 +0,0 @@
1
- # encoding: UTF-8
2
- # frozen_string_literal: true
3
-
4
- # Add our project folder to the root of our load path.
5
- $LOAD_PATH.unshift File.expand_path('../..', __FILE__)
6
-
7
- # Ensure we have this set before trying to initialize anything.
8
- ENV['REDIS_URL'] ||= 'redis://localhost:6379'
9
-
10
- # Require our core library.
11
- require 'rack/session/redic'
12
-
13
- # Kick off the tests.
14
- require 'minitest/autorun'
@@ -1,234 +0,0 @@
1
- # encoding: UTF-8
2
- # frozen_string_literal: true
3
- require 'rack/lint'
4
- require 'rack/mock'
5
-
6
- # These tests are unceremoniously copied and modified from
7
- # https://github.com/rack/rack/blob/master/test/spec_session_memcache.rb.
8
- describe Rack::Session::Redic do
9
- ROOT = '/'
10
-
11
- session_key = Rack::Session::Abstract::Persisted::DEFAULT_OPTIONS[:key]
12
- session_match = /#{session_key}=([0-9a-fA-F]+);/
13
-
14
- incrementor = lambda do |env|
15
- env['rack.session']['counter'] ||= 0
16
- env['rack.session']['counter'] += 1
17
-
18
- Rack::Response.new(env['rack.session'].inspect).to_a
19
- end
20
-
21
- drop_session = Rack::Lint.new(proc do |env|
22
- env['rack.session.options'][:drop] = true
23
- incrementor.call(env)
24
- end)
25
-
26
- renew_session = Rack::Lint.new(proc do |env|
27
- env['rack.session.options'][:renew] = true
28
- incrementor.call(env)
29
- end)
30
-
31
- defer_session = Rack::Lint.new(proc do |env|
32
- env['rack.session.options'][:defer] = true
33
- incrementor.call(env)
34
- end)
35
-
36
- skip_session = Rack::Lint.new(proc do |env|
37
- env['rack.session.options'][:skip] = true
38
- incrementor.call(env)
39
- end)
40
-
41
- incrementor = Rack::Lint.new(incrementor)
42
-
43
- it 'creates a new cookie' do
44
- redic = Rack::Session::Redic.new(incrementor)
45
- response = Rack::MockRequest.new(redic).get(ROOT)
46
-
47
- assert_includes response[Rack::SET_COOKIE], "#{ session_key }="
48
- assert_equal response.body, '{"counter"=>1}'
49
- end
50
-
51
- it 'determines session from a cookie' do
52
- redic = Rack::Session::Redic.new(incrementor)
53
- request = Rack::MockRequest.new(redic)
54
- response = request.get(ROOT)
55
-
56
- cookie = response[Rack::SET_COOKIE]
57
-
58
- assert_equal request.get(ROOT, Rack::HTTP_COOKIE => cookie).body, '{"counter"=>2}'
59
- assert_equal request.get(ROOT, Rack::HTTP_COOKIE => cookie).body, '{"counter"=>3}'
60
- end
61
-
62
- it 'determines session only from a cookie by default' do
63
- redic = Rack::Session::Redic.new(incrementor)
64
- request = Rack::MockRequest.new(redic)
65
- response = request.get(ROOT)
66
- sid = response[Rack::SET_COOKIE][session_match, 1]
67
-
68
- assert_equal request.get("/?rack.session=#{sid}").body, '{"counter"=>1}'
69
- assert_equal request.get("/?rack.session=#{sid}").body, '{"counter"=>1}'
70
- end
71
-
72
- it 'determines session from params' do
73
- redic = Rack::Session::Redic.new(incrementor, cookie_only: false)
74
- request = Rack::MockRequest.new(redic)
75
- response = request.get(ROOT)
76
- sid = response[Rack::SET_COOKIE][session_match, 1]
77
-
78
- assert_equal request.get("/?rack.session=#{sid}").body, '{"counter"=>2}'
79
- assert_equal request.get("/?rack.session=#{sid}").body, '{"counter"=>3}'
80
- end
81
-
82
- it 'survives nonexistant cookies' do
83
- bad_cookie = "rack.session=#{ SecureRandom.hex(16) }"
84
-
85
- redic = Rack::Session::Redic.new(incrementor)
86
- response = Rack::MockRequest.new(redic).get(ROOT, Rack::HTTP_COOKIE => bad_cookie)
87
-
88
- assert_equal response.body, '{"counter"=>1}'
89
-
90
- cookie = response[Rack::SET_COOKIE][session_match]
91
- refute_match /#{ bad_cookie }/, cookie
92
- end
93
-
94
- it 'maintains freshness' do
95
- redic = Rack::Session::Redic.new(incrementor, expire_after: 3)
96
- response = Rack::MockRequest.new(redic).get(ROOT)
97
- assert_includes response.body, '"counter"=>1'
98
-
99
- cookie = response[Rack::SET_COOKIE]
100
- response = Rack::MockRequest.new(redic).get(ROOT, Rack::HTTP_COOKIE => cookie)
101
-
102
- assert_equal response[Rack::SET_COOKIE], cookie
103
- assert_includes response.body, '"counter"=>2'
104
-
105
- puts 'Sleeping to expire session' if $DEBUG
106
- sleep 4
107
-
108
- response = Rack::MockRequest.new(redic).get(ROOT, Rack::HTTP_COOKIE => cookie)
109
- refute_equal response[Rack::SET_COOKIE], cookie
110
- assert_includes response.body, '"counter"=>1'
111
- end
112
-
113
- it 'does not send the same session id if it did not change' do
114
- redic = Rack::Session::Redic.new(incrementor)
115
- request = Rack::MockRequest.new(redic)
116
-
117
- res0 = request.get(ROOT)
118
- cookie = res0[Rack::SET_COOKIE][session_match]
119
- assert_equal res0.body, '{"counter"=>1}'
120
-
121
- res1 = request.get(ROOT, Rack::HTTP_COOKIE => cookie)
122
- assert_nil res1[Rack::SET_COOKIE]
123
- assert_equal res1.body, '{"counter"=>2}'
124
-
125
- res2 = request.get(ROOT, Rack::HTTP_COOKIE => cookie)
126
- assert_nil res2[Rack::SET_COOKIE]
127
- assert_equal res2.body, '{"counter"=>3}'
128
- end
129
-
130
- it 'deletes cookies with :drop option' do
131
- redic = Rack::Session::Redic.new(incrementor)
132
- request = Rack::MockRequest.new(redic)
133
- drop = Rack::Utils::Context.new(redic, drop_session)
134
- dreq = Rack::MockRequest.new(drop)
135
-
136
- res1 = request.get(ROOT)
137
- session = (cookie = res1[Rack::SET_COOKIE])[session_match]
138
- assert_equal res1.body, '{"counter"=>1}'
139
-
140
- res2 = dreq.get(ROOT, Rack::HTTP_COOKIE => cookie)
141
- assert_nil res2[Rack::SET_COOKIE]
142
- assert_equal res2.body, '{"counter"=>2}'
143
-
144
- res3 = request.get(ROOT, Rack::HTTP_COOKIE => cookie)
145
- refute_equal res3[Rack::SET_COOKIE][session_match], session
146
- assert_equal res3.body, '{"counter"=>1}'
147
- end
148
-
149
- it 'provides new session id with :renew option' do
150
- redic = Rack::Session::Redic.new(incrementor)
151
- request = Rack::MockRequest.new(redic)
152
- renew = Rack::Utils::Context.new(redic, renew_session)
153
- renew_request = Rack::MockRequest.new(renew)
154
-
155
- res1 = request.get(ROOT)
156
- session = (cookie = res1[Rack::SET_COOKIE])[session_match]
157
- assert_equal res1.body, '{"counter"=>1}'
158
-
159
- res2 = renew_request.get(ROOT, Rack::HTTP_COOKIE => cookie)
160
- new_cookie = res2[Rack::SET_COOKIE]
161
- new_session = new_cookie[session_match]
162
- refute_equal new_session, session
163
- assert_equal res2.body, '{"counter"=>2}'
164
-
165
- res3 = request.get(ROOT, Rack::HTTP_COOKIE => new_cookie)
166
- assert_equal res3.body, '{"counter"=>3}'
167
-
168
- # Old cookie was deleted
169
- res4 = request.get(ROOT, Rack::HTTP_COOKIE => cookie)
170
- assert_equal res4.body, '{"counter"=>1}'
171
- end
172
-
173
- it 'omits cookie with :defer option but still updates the state' do
174
- redic = Rack::Session::Redic.new(incrementor)
175
- count = Rack::Utils::Context.new(redic, incrementor)
176
- defer = Rack::Utils::Context.new(redic, defer_session)
177
- defer_request = Rack::MockRequest.new(defer)
178
- count_request = Rack::MockRequest.new(count)
179
-
180
- res0 = defer_request.get(ROOT)
181
- assert_nil res0[Rack::SET_COOKIE]
182
- assert_equal res0.body, '{"counter"=>1}'
183
-
184
- res0 = count_request.get(ROOT)
185
- res1 = defer_request.get(ROOT, Rack::HTTP_COOKIE => res0[Rack::SET_COOKIE])
186
- assert_equal res1.body, '{"counter"=>2}'
187
- res2 = defer_request.get(ROOT, Rack::HTTP_COOKIE => res0[Rack::SET_COOKIE])
188
- assert_equal res2.body, '{"counter"=>3}'
189
- end
190
-
191
- it 'omits cookie and state update with :skip option' do
192
- redic = Rack::Session::Redic.new(incrementor)
193
- count = Rack::Utils::Context.new(redic, incrementor)
194
- skip = Rack::Utils::Context.new(redic, skip_session)
195
- skip_request = Rack::MockRequest.new(skip)
196
- count_request = Rack::MockRequest.new(count)
197
-
198
- res0 = skip_request.get(ROOT)
199
- assert_nil res0[Rack::SET_COOKIE]
200
- assert_equal res0.body, '{"counter"=>1}'
201
-
202
- res0 = count_request.get(ROOT)
203
- res1 = skip_request.get(ROOT, Rack::HTTP_COOKIE => res0[Rack::SET_COOKIE])
204
- assert_equal res1.body, '{"counter"=>2}'
205
- res2 = skip_request.get(ROOT, Rack::HTTP_COOKIE => res0[Rack::SET_COOKIE])
206
- assert_equal res2.body, '{"counter"=>2}'
207
- end
208
-
209
- it 'updates deep hashes correctly' do
210
- hash_check = proc do |env|
211
- session = env['rack.session']
212
-
213
- if session.include?('test')
214
- session[:f][:g][:h] = :j
215
- else
216
- session.update a: :b, c: { d: :e }, f: { g: { h: :i } }, 'test' => true
217
- end
218
-
219
- [200, {}, [session.inspect]]
220
- end
221
-
222
- redic = Rack::Session::Redic.new(hash_check)
223
- request = Rack::MockRequest.new(redic)
224
-
225
- res0 = request.get(ROOT)
226
- session_id = (cookie = res0[Rack::SET_COOKIE])[session_match, 1]
227
- ses0 = redic.storage.get(session_id)
228
-
229
- request.get(ROOT, Rack::HTTP_COOKIE => cookie)
230
- ses1 = redic.storage.get(session_id)
231
-
232
- refute_equal ses1, ses0
233
- end
234
- end
@@ -1,28 +0,0 @@
1
- # encoding: UTF-8
2
- # frozen_string_literal: true
3
- require 'msgpack'
4
- require_relative 'support/storage_marshaller_interface'
5
-
6
- MessagePack::DefaultFactory.register_type(0x00, Symbol)
7
-
8
- module MessagePackMarshaller
9
- def dump(object)
10
- MessagePack.pack(object)
11
- end
12
- module_function :dump
13
-
14
- def load(string)
15
- MessagePack.unpack(string)
16
- end
17
- module_function :load
18
- end
19
-
20
- describe Rack::Session::Redic::Storage do
21
- describe 'using the MessagePack as the marshaller' do
22
- include StorageMarshallerInterface
23
-
24
- before do
25
- @store = Rack::Session::Redic::Storage.new(nil, MessagePackMarshaller, ENV['REDIS_URL'])
26
- end
27
- end
28
- end
@@ -1,14 +0,0 @@
1
- # encoding: UTF-8
2
- # frozen_string_literal: true
3
- require 'oj'
4
- require_relative 'support/storage_marshaller_interface'
5
-
6
- describe Rack::Session::Redic::Storage do
7
- describe 'using the Oj as the marshaller' do
8
- include StorageMarshallerInterface
9
-
10
- before do
11
- @store = Rack::Session::Redic::Storage.new(nil, Oj, ENV['REDIS_URL'])
12
- end
13
- end
14
- end
data/test/storage_test.rb DELETED
@@ -1,13 +0,0 @@
1
- # encoding: UTF-8
2
- # frozen_string_literal: true
3
- require_relative 'support/storage_marshaller_interface'
4
-
5
- describe Rack::Session::Redic::Storage do
6
- describe 'using the default marshaller' do
7
- include StorageMarshallerInterface
8
-
9
- before do
10
- @store = Rack::Session::Redic::Storage.new(nil, Marshal, ENV['REDIS_URL'])
11
- end
12
- end
13
- end
@@ -1,30 +0,0 @@
1
- # encoding: UTF-8
2
- # frozen_string_literal: true
3
- module StorageMarshallerInterface
4
- def test_returns_nil_for_empty_keys
5
- assert_nil @store.get('not-here')
6
- end
7
-
8
- def test_saves_objects
9
- object = { saved: true }
10
- @store.set('saving', object)
11
-
12
- assert_equal @store.get('saving'), object
13
- end
14
-
15
- def test_existence_of_keys
16
- @store.set('exists', false)
17
-
18
- assert_equal @store.exists?('exists'), true
19
- end
20
-
21
- def test_deletes_objects
22
- object = { deleted: true }
23
- @store.set('deleted', object)
24
-
25
- assert_equal @store.get('deleted'), object
26
- @store.delete('deleted')
27
-
28
- assert_nil @store.get('deleted')
29
- end
30
- end