Skip to content

Commit 9ebb2cb

Browse files
committed
add timeout option to redis session store
1 parent 35a44de commit 9ebb2cb

7 files changed

Lines changed: 383 additions & 48 deletions

File tree

coverage-report/src/test/java/org/jooby/jedis/session/RedisSessionDataFeature.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ public class RedisSessionDataFeature extends ServerFeature {
1515

1616
{
1717
use(ConfigFactory.empty()
18-
.withValue("db", ConfigValueFactory.fromAnyRef("redis://localhost:6379")));
18+
.withValue("db", ConfigValueFactory.fromAnyRef("redis://localhost:6379"))
19+
.withValue("application.session.timeout", ConfigValueFactory.fromAnyRef(120)));
1920

2021
use(new Redis());
2122

@@ -34,10 +35,6 @@ public class RedisSessionDataFeature extends ServerFeature {
3435
return session.attributes();
3536
});
3637

37-
get("/destroy", req -> {
38-
req.session().destroy();;
39-
return "done";
40-
});
4138
}
4239

4340
@Test
@@ -52,11 +49,7 @@ public void save() throws Exception {
5249
.expect(rsp -> {
5350
assertTrue(rsp.equals("{name=edgar, age=34}")
5451
|| rsp.equals("{age=34, name=edgar}"));
55-
}).request(r2 -> {
56-
// cleanup
57-
r2.get("/destroy")
58-
.expect("done");
59-
})
52+
})
6053
);
6154

6255
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package org.jooby.jedis.session;
2+
3+
import static org.junit.Assert.assertTrue;
4+
5+
import org.jooby.Session;
6+
import org.jooby.jedis.Redis;
7+
import org.jooby.jedis.RedisSessionStore;
8+
import org.jooby.test.ServerFeature;
9+
import org.junit.Test;
10+
11+
import com.typesafe.config.ConfigFactory;
12+
import com.typesafe.config.ConfigValueFactory;
13+
14+
public class RedisSessionDataNoTimeoutFeature extends ServerFeature {
15+
16+
{
17+
use(ConfigFactory.empty()
18+
.withValue("db", ConfigValueFactory.fromAnyRef("redis://localhost:6379"))
19+
.withValue("application.session.timeout", ConfigValueFactory.fromAnyRef("0")));
20+
21+
use(new Redis());
22+
23+
session(RedisSessionStore.class);
24+
25+
get("/s1", req -> {
26+
Session session = req.session();
27+
session
28+
.set("name", "edgar")
29+
.set("age", 34);
30+
return session.attributes();
31+
});
32+
33+
get("/s2", req -> {
34+
Session session = req.session();
35+
return session.attributes();
36+
});
37+
38+
get("/destroy", req -> {
39+
req.session().destroy();;
40+
return "done";
41+
});
42+
}
43+
44+
@Test
45+
public void save() throws Exception {
46+
request()
47+
.get("/s1")
48+
.expect(rsp -> {
49+
assertTrue(rsp.equals("{name=edgar, age=34}") || rsp.equals("{age=34, name=edgar}"));
50+
})
51+
.request(r1 ->
52+
r1.get("/s2")
53+
.expect(rsp -> {
54+
assertTrue(rsp.equals("{name=edgar, age=34}")
55+
|| rsp.equals("{age=34, name=edgar}"));
56+
}).request(r2 -> {
57+
// cleanup
58+
r2.get("/destroy")
59+
.expect("done");
60+
})
61+
);
62+
63+
}
64+
}

jooby-jedis/src/main/java/org/jooby/jedis/RedisSessionStore.java

Lines changed: 127 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@
2222

2323
import java.util.HashMap;
2424
import java.util.Map;
25+
import java.util.concurrent.TimeUnit;
2526

2627
import javax.inject.Inject;
28+
import javax.inject.Named;
2729
import javax.inject.Singleton;
2830

2931
import org.jooby.Session;
@@ -32,37 +34,133 @@
3234
import redis.clients.jedis.Jedis;
3335
import 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
3698
public 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
}

jooby-jedis/src/main/resources/org/jooby/jedis/jedis.conf

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,7 @@ jedis.pool.timeBetweenEvictionRuns = -1
2020
jedis.pool.blockWhenExhausted = true
2121
jedis.pool.jmxEnabled = false
2222
jedis.pool.jmxNamePrefix = redis-pool
23+
24+
# session store, key prefix and timeout in seconds
25+
jedis.session.prefix = sessions
26+
jedis.session.timeout = ${application.session.timeout}

0 commit comments

Comments
 (0)