2222
2323import java .util .HashMap ;
2424import java .util .Map ;
25+ import java .util .concurrent .TimeUnit ;
2526
2627import javax .inject .Inject ;
28+ import javax .inject .Named ;
2729import javax .inject .Singleton ;
2830
2931import org .jooby .Session ;
3234import redis .clients .jedis .Jedis ;
3335import redis .clients .jedis .JedisPool ;
3436
37+ import com .typesafe .config .Config ;
38+ import com .typesafe .config .ConfigFactory ;
39+ import com .typesafe .config .ConfigValueFactory ;
40+
41+ /**
42+ * A {@link Session.Store} powered by <a href="http://redis.io/">Redis</a>.
43+ *
44+ * <h2>usage</h2>
45+ *
46+ * <pre>
47+ * {
48+ * use(new Redis());
49+ *
50+ * session(RedisSessionStore.class);
51+ *
52+ * get("/", req {@literal ->} {
53+ * req.session().set("name", "jooby");
54+ * });
55+ * }
56+ * </pre>
57+ *
58+ * The <code>name</code> attribute and value will be stored in a
59+ * <a href="http://redis.io/">Redis</a> db.
60+ *
61+ * Session are persisted as
62+ * a <a href="http://redis.io/topics/data-types#hashes">Redis Hash</a>.
63+ *
64+ * <h2>options</h2>
65+ *
66+ * <h3>timeout</h3>
67+ * <p>
68+ * By default, a redis session will expire after <code>30 minutes</code>. Changing the default
69+ * timeout is as simple as:
70+ * </p>
71+ *
72+ * <pre>
73+ * # 8 hours
74+ * session.timeout = 8h
75+ *
76+ * # 15 seconds
77+ * session.timeout = 15
78+ *
79+ * # 120 minutes
80+ * session.timeout = 120m
81+ * </pre>
82+ *
83+ * If no timeout is required, use <code>-1</code>.
84+ *
85+ * <h3>key prefix</h3>
86+ * <p>
87+ * Default redis key prefix is <code>sessions</code>. Sessions in redis will look like:
88+ * <code>sessions:ID</code>
89+ *
90+ * <p>
91+ * It's possible to change the default key setting the <code>jedis.sesssion.prefix</code> properties
92+ * </p>
93+ *
94+ * @author edgar
95+ * @since 0.5.0
96+ */
3597@ Singleton
3698public class RedisSessionStore implements Session .Store {
3799
38100 private JedisPool pool ;
39101
40- @ Inject
41- public RedisSessionStore (final JedisPool pool ) {
102+ private int timeout ;
103+
104+ private String prefix ;
105+
106+ /**
107+ * Creates a new {@link RedisSessionStore}.
108+ *
109+ * @param pool Jedis pool.
110+ * @param prefix Session key prefix on redis.
111+ * @param timeout Session timeout in seconds.
112+ */
113+ public RedisSessionStore (final JedisPool pool , final String prefix ,
114+ final int timeout ) {
42115 this .pool = requireNonNull (pool , "Jedis pool is required." );
116+ this .timeout = timeout ;
117+ this .prefix = requireNonNull (prefix , "Prefix is required." );
118+ }
119+
120+ /**
121+ * Creates a new {@link RedisSessionStore}.
122+ *
123+ * @param pool Jedis pool.
124+ * @param prefix Session key prefix on redis.
125+ * @param timeout Session timeout expression, like <code>30m</code>.
126+ */
127+ @ Inject
128+ public RedisSessionStore (final JedisPool pool ,
129+ final @ Named ("jedis.session.prefix" ) String prefix ,
130+ @ Named ("jedis.session.timeout" ) final String timeout ) {
131+ this (pool , prefix , seconds (timeout ));
43132 }
44133
45134 @ Override
46135 public Session get (final Builder builder ) {
47136 try (Jedis jedis = pool .getResource ()) {
48- Map <String , String > attrs = jedis .hgetAll ("sessions:" + builder .sessionId ());
137+ String key = key (builder .sessionId ());
138+ Map <String , String > attrs = jedis .hgetAll (key );
139+ if (timeout > 0 ) {
140+ // touch session
141+ jedis .expire (key , timeout );
142+ }
49143 return builder
50- .accessedAt (Long .parseLong (attrs .remove ("accessedAt" )))
51- .createdAt (Long .parseLong (attrs .remove ("createdAt" )))
52- .savedAt (Long .parseLong (attrs .remove ("savedAt" )))
53- .set (attrs ).build ();
144+ .accessedAt (Long .parseLong (attrs .remove ("_accessedAt" )))
145+ .createdAt (Long .parseLong (attrs .remove ("_createdAt" )))
146+ .savedAt (Long .parseLong (attrs .remove ("_savedAt" )))
147+ .set (attrs )
148+ .build ();
54149 }
55150 }
56151
57152 @ Override
58153 public void save (final Session session ) {
59154 try (Jedis jedis = pool .getResource ()) {
60- String key = "sessions:" + session . id ( );
155+ String key = key ( session );
61156 Map <String , String > attrs = new HashMap <>(session .attributes ());
62- attrs .put ("createdAt " , Long .toString (session .createdAt ()));
63- attrs .put ("accessedAt " , Long .toString (session .accessedAt ()));
64- attrs .put ("savedAt " , Long .toString (session .savedAt ()));
157+ attrs .put ("_createdAt " , Long .toString (session .createdAt ()));
158+ attrs .put ("_accessedAt " , Long .toString (session .accessedAt ()));
159+ attrs .put ("_savedAt " , Long .toString (session .savedAt ()));
65160 jedis .hmset (key , attrs );
161+ if (timeout > 0 ) {
162+ jedis .expire (key , timeout );
163+ }
66164 }
67165 }
68166
@@ -74,10 +172,26 @@ public void create(final Session session) {
74172 @ Override
75173 public void delete (final String id ) {
76174 try (Jedis jedis = pool .getResource ()) {
77- String key = "sessions:" + id ;
78- jedis .del (key );
175+ jedis .del (key (id ));
79176 }
80177
81178 }
82179
180+ private String key (final String id ) {
181+ return prefix + ":" + id ;
182+ }
183+
184+ private String key (final Session session ) {
185+ return key (session .id ());
186+ }
187+
188+ private static int seconds (final String value ) {
189+ try {
190+ return Integer .parseInt (value );
191+ } catch (NumberFormatException ex ) {
192+ Config config = ConfigFactory .empty ()
193+ .withValue ("timeout" , ConfigValueFactory .fromAnyRef (value ));
194+ return (int ) config .getDuration ("timeout" , TimeUnit .SECONDS );
195+ }
196+ }
83197}
0 commit comments