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_option/2. 91
92http_session:session_option(redis_db, atom).
93http_session:session_option(redis_ro, atom).
94http_session:session_option(redis_prefix, atom).
95
96http_session:hooked :-
97 http_session:session_setting(redis_db(_)).
98
112
113:- dynamic
114 peer/2, 115 last_used/2. 116
117
118http_session:hook(assert_session(SessionID, Peer)) :-
119 session_db(rw, SessionID, DB, Key),
120 http_session:session_setting(timeout(Timeout)),
121 asserta(peer(SessionID, Peer)),
122 ip_name(Peer, PeerS),
123 get_time(Now),
124 redis(DB, hset(Key,
125 peer, PeerS,
126 last_used, Now)),
127 expire(SessionID, Timeout).
128http_session:hook(set_session_option(SessionID, Setting)) :-
129 session_db(rw, SessionID, DB, Key),
130 Setting =.. [Name,Value],
131 redis(DB, hset(Key, Name, Value as prolog)),
132 ( Setting = timeout(Timeout)
133 -> expire(SessionID, Timeout)
134 ; true
135 ).
136http_session:hook(get_session_option(SessionID, Setting)) :-
137 session_db(ro, SessionID, DB, Key),
138 Setting =.. [Name,Value],
139 redis(DB, hget(Key, Name), Value).
140http_session:hook(active_session(SessionID, Peer, LastUsed)) :-
141 ( last_used(SessionID, LastUsed0),
142 peer(SessionID, Peer0)
143 -> LastUsed = LastUsed0,
144 Peer = Peer0
145 ; session_db(ro, SessionID, DB, Key),
146 redis(DB, hget(Key, peer), PeerS),
147 ip_name(Peer, PeerS),
148 redis(DB, hget(Key, last_used), LastUsed as number),
149 update_session(SessionID, LastUsed, _, Peer)
150 ).
151http_session:hook(set_last_used(SessionID, Now, Timeout)) :-
152 LastUsed is floor(Now/10)*10,
153 update_session(SessionID, LastUsed, Updated, _Peer),
154 ( Updated == true
155 -> session_db(rw, SessionID, DB, Key),
156 redis(DB, hset(Key, last_used, Now)),
157 expire(SessionID, Timeout)
158 ; true
159 ).
160http_session:hook(asserta(session_data(SessionID, Data))) :-
161 must_be(ground, Data),
162 session_data_db(rw, SessionID, DB, Key),
163 redis(DB, lpush(Key, Data as prolog)).
164http_session:hook(assertz(session_data(SessionID, Data))) :-
165 must_be(ground, Data),
166 session_data_db(rw, SessionID, DB, Key),
167 redis(DB, rpush(Key, Data as prolog)).
168http_session:hook(retract(session_data(SessionID, Data))) :-
169 session_data_db(rw, SessionID, DB, Key),
170 redis_get_list(DB, Key, 10, List),
171 member(Data, List),
172 redis(DB, lrem(Key, 1, Data as prolog)).
173http_session:hook(retractall(session_data(SessionID, Data))) :-
174 forall(http_session:hook(retract(session_data(SessionID, Data))),
175 true).
176http_session:hook(session_data(SessionID, Data)) :-
177 session_data_db(rw, SessionID, DB, Key),
178 redis_get_list(DB, Key, 10, List),
179 member(Data, List).
180http_session:hook(current_session(SessionID, Data)) :-
181 session_db(ro, SessionID, DB, Key),
182 redis(DB, hget(Key, last_used), Time as number),
183 number(Time),
184 get_time(Now),
185 Idle is Now - Time,
186 ( nonvar(Data)
187 -> ( Data = peer(Peer)
188 -> redis(DB, hget(Key, peer), PeerS),
189 ip_name(Peer, PeerS)
190 ; Data = idle(Idle0)
191 -> Idle0 = Idle
192 ; http_session:hook(session_data(SessionID, Data))
193 )
194 ; ( Data = peer(Peer),
195 redis(DB, hget(Key, peer), PeerS),
196 ip_name(Peer, PeerS)
197 ; Data = idle(Idle)
198 ; non_reserved_property(Data),
199 http_session:hook(session_data(SessionID, Data))
200 )
201 ).
202http_session:hook(close_session(SessionID)) :-
203 gc_session(SessionID).
204http_session:hook(gc_sessions) :-
205 gc_sessions.
206
207non_reserved_property(P) :-
208 var(P),
209 !.
210non_reserved_property(peer(_)) :- !, fail.
211non_reserved_property(idle(_)) :- !, fail.
212non_reserved_property(_).
219update_session(SessionID, LastUsed, Updated, Peer) :-
220 transaction(update_session_(SessionID, LastUsed, Updated, Peer)).
221
222update_session_(SessionID, LastUsed, Updated, Peer) :-
223 update_last_used(SessionID, Updated, LastUsed),
224 update_peer(SessionID, Peer).
225
226update_last_used(SessionID, Updated, LastUsed), nonvar(LastUsed) =>
227 ( last_used(SessionID, LastUsed)
228 -> true
229 ; retractall(last_used(SessionID, _)),
230 asserta(last_used(SessionID, LastUsed)),
231 Updated = true
232 ).
233update_last_used(_, _, _) =>
234 true.
235
236update_peer(SessionID, Peer), nonvar(Peer) =>
237 ( peer(SessionID, Peer)
238 -> true
239 ; retractall(peer(SessionID, _)),
240 asserta(peer(SessionID, Peer))
241 ).
242update_peer(_, _) =>
243 true.
244
245
246 249
250expire(SessionID, Timeout) :-
251 get_time(Now),
252 Time is integer(Now+Timeout),
253 session_expire_db(rw, DB, Key),
254 redis(DB, zadd(Key, Time, SessionID)).
255
256gc_sessions :-
257 session_expire_db(ro, DB, Key),
258 get_time(Now),
259 End is integer(Now),
260 redis(DB, zrangebyscore(Key, "-inf", End), TimedOut as atom),
261 forall(member(SessionID, TimedOut),
262 gc_session(SessionID)).
263
264gc_session(_) :-
265 prolog_current_frame(Frame),
266 prolog_frame_attribute(Frame, parent, PFrame),
267 prolog_frame_attribute(PFrame, parent_goal, gc_session(_)),
268 !.
269gc_session(SessionID) :-
270 debug(http_session(gc), 'GC session ~p', [SessionID]),
271 session_db(ro, SessionID, DB, SessionKey),
272 session_expire_db(rw, DB, TMOKey),
273 redis(DB, zrem(TMOKey, SessionID)),
274 redis(DB, hget(SessionKey, peer), PeerS),
275 ip_name(Peer, PeerS),
276 broadcast(http_session(end(SessionID, Peer))),
277 session_db(rw, SessionID, DBw, SessionKey),
278 redis(DBw, del(SessionKey)),
279 session_data_db(rw, SessionID, DBw, DataKey),
280 redis(DBw, del(DataKey)),
281 retractall(peer(SessionID, _)),
282 retractall(last_used(SessionID, _)).
283
284
285
291session_db(RW, SessionID, DB, Key) :-
292 nonvar(SessionID),
293 !,
294 redis_db(RW, DB),
295 key_prefix(Prefix),
296 atomics_to_string([Prefix,session,SessionID], :, Key).
297session_db(RW, SessionID, DB, Key) :-
298 session_expire_db(RW, DB, TMOKey),
299 redis_zscan(DB, TMOKey, Pairs, []),
300 member(SessionIDS-_Timeout, Pairs),
301 atom_string(SessionID, SessionIDS),
302 key_prefix(Prefix),
303 atomics_to_string([Prefix,session,SessionID], :, Key).
304
305session_expire_db(RW, DB, Key) :-
306 redis_db(RW, DB),
307 key_prefix(Prefix),
308 atomics_to_string([Prefix,expire], :, Key).
309
310session_data_db(RW, SessionID, DB, Key) :-
311 redis_db(RW, DB),
312 key_prefix(Prefix),
313 atomics_to_string([Prefix,data,SessionID], :, Key).
314
315key_prefix(Prefix) :-
316 http_session:session_setting(redis_prefix(Prefix)),
317 !.
318key_prefix('swipl:http:sessions').
319
320redis_db(ro, DB) :-
321 http_session:session_setting(redis_ro(DB0)),
322 !,
323 DB = DB0.
324redis_db(_, DB) :-
325 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: