Rack::State – Secure & Modular State Management¶ ↑
Rack::State is middleware that securely stores and manages object state.
Applications use the Manager API to set, get, and delete state of any object that requires persistence in a stateless environment.
The Core Components¶ ↑
Diagram of All the Players¶ ↑
State (middleware) | +--> Manager (env[rack.state.KEY]) <-- API (get/set/delete object) | +--> client-side: cookie (KEY = token) | +--> server-side: Store (token, object) | +--> Memory +--> File +--> Postgres +--> PreparedPostgres
The KEY and Store adapter may be set for each instance of middleware.
Design & Motivation¶ ↑
Primary goal¶ ↑
State management for multiple objects with independent control of the visibility, security, and storage of each object's state.
But we have Rack::Session¶ ↑
Rack::Session provides state management, but does so using only a single SessionHash, which is statically located in Rack's environment. The drawback is the lack of fine-grained control, efficiency, and multiple instances. This single hash-like object is always available to the entire application whether it is needed or not.
I attempted to modify and bend Rack::Session to meet my design goal, but there seemed to be no clean and easy solution. Rack::State was born…
Use Case¶ ↑
An application has the following areas that require data persistence:
site-wide personalization – real name, theme, etc.
blog activity tracking – articles read, favorites, shares
secure store – TLS for entire store, shopping cart, and checkout
Three instances of Rack::State can be used. The domain, path, expiration, token security (i.e., HttpOnly & Secure cookie flags) and storage backend can be set appropriately for each area.
For example, site-wide personalization may set path to “/” with a long expiration, but the secure store could use “/store” with expiration at end of session and set the HttpOnly and Secure flags as well. Additionally, site-wide personalization and blog activity tracking states could be stored in the database while the secure store state could be saved in files.
Usage Examples¶ ↑
Manage a single state in memory¶ ↑
Here's a simple rackup (config.ru) file that uses the Rack::State middleware with default options.
require 'rack' require 'rack/state' use Rack::State run MyApp
The application can access the State::Manager via the environment. The following code sets up Rack::State to function similarly to Rack::Session.
def session env['rack.state.token'].get or env['rack.state.token'].set Hash.new end session['username']
Multiple state management with different storage adapters¶ ↑
Below, the flash state will use the default Memory store and store will save state in files under “tmp/state-store” in the project's root. Additionally, if “/store” requires encryption it is wise to secure the token.
use Rack::State, key: 'flash', path: '/', max_age: 60*60 use Rack::State, key: 'store', path: '/store', secure: true, store: Rack::State::Store::File.new('tmp/state-store')
Then on the application side you may have some helper methods like this:
def flash env['rack.state.flash'].get or env['rack.state.flash'].set Flash.new end def store env['rack.state.store'] end store.set SecureStore.new # start shopping session store.get.cart.add :item7 flash.notice 'Added Item 7' store.get.transaction.process flash.notice 'Transaction successful' store.delete # remove from client/server after checkout
Choose a specific storage adapter depending on the environment¶ ↑
Here we use the Postgres store for production, otherwise use the default Memory store for development and testing.
if ENV['RACK_ENV'] == 'production' DB = PG::Connection.new use Rack::State, store: Rack::State::Store::Postgres.new(DB) else use Rack::State end
Keep state of an arbitrary object using helper methods¶ ↑
First setup the middleware key and helpers.
use Rack::State, key: 'myobj' def my_object env['rack.state.myobj'].get end def set_my_object(obj) obj ? env['rack.state.myobj'].set(obj) : env['rack.state.myobj'].delete end
Then get state tracking in your app.
set_my_object ObjectA.new # instance of ObjectA persistently stored my_object.do_something # interact with original instance set_my_object nil # remove instance from state store set_my_object ObjectB.new # do it all again with another object
Install, Test & Contribute¶ ↑
Install the gem:
$ sudo gem install rack-state
Or clone the Mercurial repository:
$ hg clone https://bitbucket.org/pachl/rack-state
State is tested with Christian Neukirchen's awesome test framework, Bacon. Get some bacon and start cooking:
$ sudo gem install bacon
Run the entire test suite from the project's root:
$ bacon -a
To test a specific component, such as a new storage adapter, run:
$ bacon spec/spec_COMPONENT.rb
Submit a patch¶ ↑
After you fix a bug or develop a storage adapter, please submit your code and tests via a Bitbucket pull request.
Or for simple one-off patches, use the following workflow:
$ hg clone https://bitbucket.org/pachl/rack-state $ cd rack-state $ # EDIT AND ADD NEW FILES $ hg add # NEW FILES $ hg commit -m 'describe your changes' $ hg export tip > patch.diff $ mail -s 'rack-state patch' email@example.com < patch.diff
- Ruby Gem
- Source Code
- Bug Tracker
Rack::State was developed and tested on OpenBSD 5.3 using Ruby 1.9.3 and Rack 1.5.
2013-09-05, v0.0.0: Initial design, development and testing
2013-09-22, v0.0.1: First public release
2013-09-22, v0.0.2: Update Gem info and documentation
2014-04-20, v0.0.3: Add homepage and bug tracker URLs
Copyright © 2014, Clint Pachl <firstname.lastname@example.org>
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.