34
35:- module(http_redis_plugin, []). 36:- use_module(library(http/http_session)). 37:- autoload(library(error), [must_be/2]). 38:- autoload(library(lists), [member/2]). 39:- autoload(library(redis), [redis/3, redis/2, redis_get_list/4, redis_zscan/4]). 40:- autoload(library(broadcast), [broadcast/1]). 41:- use_module(library(debug), [debug/3]). 42:- autoload(library(socket), [ip_name/2]).
87:- multifile
88 http_session:hooked/0,
89 http_session:hook/1,
90 http_session:session_setting/1,
91 http_session:session_option/2. 92
93http_session:session_option(redis_db, atom).
94http_session:session_option(redis_ro, atom).
95http_session:session_option(redis_prefix, atom).
96
97http_session:hooked :-
98 http_session:session_setting(redis_db(_)).
99
113
114:- dynamic
115 peer/2, 116 last_used/2. 117
118
119http_session:hook(assert_session(SessionID, Peer)) :-
120 session_db(rw, SessionID, DB, Key),
121 http_session:session_setting(timeout(Timeout)),
122 asserta(peer(SessionID, Peer)),
123 ip_name(Peer, PeerS),
124 get_time(Now),
125 redis(DB, hset(Key,
126 peer, PeerS,
127 last_used, Now)),
128 expire(SessionID, Timeout).
129http_session:hook(set_session_option(SessionID, Setting)) :-
130 session_db(rw, SessionID, DB, Key),
131 Setting =.. [Name,Value],
132 redis(DB, hset(Key, Name, Value as prolog)),
133 ( Setting = timeout(Timeout)
134 -> expire(SessionID, Timeout)
135 ; true
136 ).
137http_session:hook(get_session_option(SessionID, Setting)) :-
138 session_db(ro, SessionID, DB, Key),
139 Setting =.. [Name,Value],
140 redis(DB, hget(Key, Name), Value).
141http_session:hook(active_session(SessionID, Peer, LastUsed)) :-
142 ( last_used(SessionID, LastUsed0),
143 peer(SessionID, Peer0)
144 -> LastUsed = LastUsed0,
145 Peer = Peer0
146 ; session_db(ro, SessionID, DB, Key),
147 redis(DB, hget(Key, peer), PeerS),
148 ip_name(Peer, PeerS),
149 redis(DB, hget(Key, last_used), LastUsed as number),
150 update_session(SessionID, LastUsed, _, Peer)
151 ).
152http_session:hook(set_last_used(SessionID, Now, Timeout)) :-
153 http_session:session_setting(granularity(TimeGranularity)),
154 LastUsed is floor(Now/TimeGranularity)*TimeGranularity,
155 update_session(SessionID, LastUsed, Updated, _Peer),
156 ( Updated == true
157 -> session_db(rw, SessionID, DB, Key),
158 redis(DB, hset(Key, last_used, Now)),
159 expire(SessionID, Timeout)
160 ; true
161 ).
162http_session:hook(asserta(session_data(SessionID, Data))) :-
163 must_be(ground, Data),
164 session_data_db(rw, SessionID, DB, Key),
165 redis(DB, lpush(Key, Data as prolog)).
166http_session:hook(assertz(session_data(SessionID, Data))) :-
167 must_be(ground, Data),
168 session_data_db(rw, SessionID, DB, Key),
169 redis(DB, rpush(Key, Data as prolog)).
170http_session:hook(retract(session_data(SessionID, Data))) :-
171 session_data_db(rw, SessionID, DB, Key),
172 redis_get_list(DB, Key, 10, List),
173 member(Data, List),
174 redis(DB, lrem(Key, 1, Data as prolog)).
175http_session:hook(retractall(session_data(SessionID, Data))) :-
176 forall(http_session:hook(retract(session_data(SessionID, Data))),
177 true).
178http_session:hook(session_data(SessionID, Data)) :-
179 session_data_db(rw, SessionID, DB, Key),
180 redis_get_list(DB, Key, 10, List),
181 member(Data, List).
182http_session:hook(current_session(SessionID, Data)) :-
183 session_db(ro, SessionID, DB, Key),
184 redis(DB, hget(Key, last_used), Time as number),
185 number(Time),
186 get_time(Now),
187 Idle is Now - Time,
188 ( nonvar(Data)
189 -> ( Data = peer(Peer)
190 -> redis(DB, hget(Key, peer), PeerS),
191 ip_name(Peer, PeerS)
192 ; Data = idle(Idle0)
193 -> Idle0 = Idle
194 ; http_session:hook(session_data(SessionID, Data))
195 )
196 ; ( Data = peer(Peer),
197 redis(DB, hget(Key, peer), PeerS),
198 ip_name(Peer, PeerS)
199 ; Data = idle(Idle)
200 ; non_reserved_property(Data),
201 http_session:hook(session_data(SessionID, Data))
202 )
203 ).
204http_session:hook(close_session(SessionID)) :-
205 gc_session(SessionID).
206http_session:hook(gc_sessions) :-
207 gc_sessions.
208
209non_reserved_property(P) :-
210 var(P),
211 !.
212non_reserved_property(peer(_)) :- !, fail.
213non_reserved_property(idle(_)) :- !, fail.
214non_reserved_property(_).
221update_session(SessionID, LastUsed, Updated, Peer) :-
222 transaction(update_session_(SessionID, LastUsed, Updated, Peer)).
223
224update_session_(SessionID, LastUsed, Updated, Peer) :-
225 update_last_used(SessionID, Updated, LastUsed),
226 update_peer(SessionID, Peer).
227
228update_last_used(SessionID, Updated, LastUsed), nonvar(LastUsed) =>
229 ( last_used(SessionID, LastUsed)
230 -> true
231 ; retractall(last_used(SessionID, _)),
232 asserta(last_used(SessionID, LastUsed)),
233 Updated = true
234 ).
235update_last_used(_, _, _) =>
236 true.
237
238update_peer(SessionID, Peer), nonvar(Peer) =>
239 ( peer(SessionID, Peer)
240 -> true
241 ; retractall(peer(SessionID, _)),
242 asserta(peer(SessionID, Peer))
243 ).
244update_peer(_, _) =>
245 true.
246
247
248 251
252expire(SessionID, Timeout) :-
253 get_time(Now),
254 Time is integer(Now+Timeout),
255 session_expire_db(rw, DB, Key),
256 redis(DB, zadd(Key, Time, SessionID)).
257
258gc_sessions :-
259 session_expire_db(ro, DB, Key),
260 session_expire_db(rw, DB, TMOKey),
261 get_time(Now),
262 End is integer(Now),
263 redis(DB, zrangebyscore(Key, "-inf", End), TimedOut as atom),
264 ( member(SessionID, TimedOut),
265 redis(DB, zrem(TMOKey, SessionID), 1), 266 gc_session(SessionID),
267 fail
268 ; true
269 ).
273gc_session(_) :-
274 prolog_current_frame(Frame),
275 prolog_frame_attribute(Frame, parent, PFrame),
276 prolog_frame_attribute(PFrame, parent_goal, gc_session(_)),
277 !.
278gc_session(SessionID) :-
279 debug(http_session(gc), 'GC session ~p', [SessionID]),
280 session_db(ro, SessionID, DB, SessionKey),
281 redis(DB, hget(SessionKey, peer), PeerS),
282 ip_name(Peer, PeerS),
283 broadcast(http_session(end(SessionID, Peer))),
284 session_db(rw, SessionID, DBw, SessionKey),
285 redis(DBw, del(SessionKey)),
286 session_data_db(rw, SessionID, DBw, DataKey),
287 redis(DBw, del(DataKey)),
288 retractall(peer(SessionID, _)),
289 retractall(last_used(SessionID, _)).
290
291
292
298session_db(RW, SessionID, DB, Key) :-
299 nonvar(SessionID),
300 !,
301 redis_db(RW, DB),
302 key_prefix(Prefix),
303 atomics_to_string([Prefix,session,SessionID], :, Key).
304session_db(RW, SessionID, DB, Key) :-
305 session_expire_db(RW, DB, TMOKey),
306 redis_zscan(DB, TMOKey, Pairs, []),
307 member(SessionIDS-_Timeout, Pairs),
308 atom_string(SessionID, SessionIDS),
309 key_prefix(Prefix),
310 atomics_to_string([Prefix,session,SessionID], :, Key).
311
312session_expire_db(RW, DB, Key) :-
313 redis_db(RW, DB),
314 key_prefix(Prefix),
315 atomics_to_string([Prefix,expire], :, Key).
316
317session_data_db(RW, SessionID, DB, Key) :-
318 redis_db(RW, DB),
319 key_prefix(Prefix),
320 atomics_to_string([Prefix,data,SessionID], :, Key).
321
322key_prefix(Prefix) :-
323 http_session:session_setting(redis_prefix(Prefix)),
324 !.
325key_prefix('swipl:http:sessions').
326
327redis_db(ro, DB) :-
328 http_session:session_setting(redis_ro(DB0)),
329 !,
330 DB = DB0.
331redis_db(_, DB) :-
332 http_session:session_setting(redis_db(DB))
Hook session management to use Redis
This module acts as a plugin for library(http/http_session), storing session information on a Redis server. This has several consequences:
The library is activated by loading it in addition to library(http/http_session) and using http_set_session_options/1 to configure the Redis database as below. The redis_server/2 predicate from library(redis) can be used to specify the parameters for the redis server such as host, port or authentication.
Redis key usage
All Redis keys reside under a prefix specified by the option
redis_prefix(Prefix)
, which defaults to'swipl:http:session'
. Here we find: