Http sessions not being kept alive

I’m using: SWI-Prolog version 9.1.12

I want the code to: keep http sessions alive by resetting the idle timer at each client request

But what I’m getting is: http sessions time out T seconds after the call to http_open_session/2 where T is set by http_set_session_options([timeout(T)]) i.e. user interaction is ignored as far as the session timeout is concerned.

Looking at http_session.pl I see this:

:- multifile
http:request_expansion/2.

http:request_expansion(Request0, Request) :-
session_setting(enabled(true)),
http_session(Request0, Request, _SessionID).

but http_dispatch.pl appears to expect the expansion to be defined by a closure and a rank (and in module http_dispatch)

A GIT repo search for http:request_expansion shows only:

  • the definition in http_session.pl,
  • a multifile declaration and a comment in http_wrapper.pl
  • documentation in http.doc

Hmm. This should all work out of the box. A little look around in http_session.pl tells me:

  • http:request_expansion/2 calls http_session/3
  • http_session/3 calls valid_session_id/2 if the session is not yet established for this request.
  • valid_session_id/2 checks for a timeout and then closes the session or updates the last usage time.

So, sessions are supposed to time out after 10 minutes idle. You can change that value using http_set_session/1.

If that doesn’t work, there is a bug and the above should make it fairly easy to see which part of the chain is broken.

Hi Jan and happy 2024 :slight_smile:

My GIT search had some hidden results and I can now see that http:request_expansion/2 does indeed call http_session/3. But as far as I can tell, valid_session_id/2 is called only when session/1 is NOT in the Request. The other place valid_session_id/2 is called is in http_in_session/1,2 but again only when session/1 is not in the Request.

I wrote a simple server to try and see what’s going on. With this server, the session never times out and weirdly the idle time never exceeds 10 seconds even though I’ve set the timeout to 60 seconds. I expected to see the idle time get close to zero if I repeatedly and quickly hit the button but it just climbs up to about 10 seconds then goes back to zero. My program prints out the request session_id which seems always to be present in Request which means valid_session_id/2 never gets called and so the session never gets expired.

?- start_server.
% Started server at http://localhost:8080/
true.

?-
[Thread httpd@8080_2] Created session '67d4-f7a3-f754-2de5.MEE-Windows-PC' at path=/ request_session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) [Thread httpd@8080_2] Session id from global variable: ‘67d4-f7a3-f754-2de5.MEE-Windows-PC’
session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) idle(1.8109769821166992)
request_session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC)
[Thread httpd@8080_3] Session id from global variable: '67d4-f7a3-f754-2de5.MEE-Windows-PC' session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) idle(2.4468560218811035) request_session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) [Thread httpd@8080_2] Session id from global variable: ‘67d4-f7a3-f754-2de5.MEE-Windows-PC’
session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) idle(3.0006749629974365)
request_session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC)
[Thread httpd@8080_1] Session id from global variable: '67d4-f7a3-f754-2de5.MEE-Windows-PC' session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) idle(3.534940004348755) request_session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) [Thread httpd@8080_3] Session id from global variable: ‘67d4-f7a3-f754-2de5.MEE-Windows-PC’
session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) idle(4.062686920166016)
request_session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC)
[Thread httpd@8080_2] Session id from global variable: '67d4-f7a3-f754-2de5.MEE-Windows-PC' session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) idle(4.598881959915161) request_session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) [Thread httpd@8080_1] Session id from global variable: ‘67d4-f7a3-f754-2de5.MEE-Windows-PC’
session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) idle(5.128626108169556)
request_session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC)
[Thread httpd@8080_3] Session id from global variable: '67d4-f7a3-f754-2de5.MEE-Windows-PC' session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) idle(5.639517068862915) request_session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) [Thread httpd@8080_1] Session id from global variable: ‘67d4-f7a3-f754-2de5.MEE-Windows-PC’
session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) idle(6.272963047027588)
request_session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC)
[Thread httpd@8080_3] Session id from global variable: '67d4-f7a3-f754-2de5.MEE-Windows-PC' session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) idle(6.944055080413818) request_session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) [Thread httpd@8080_1] Session id from global variable: ‘67d4-f7a3-f754-2de5.MEE-Windows-PC’
session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) idle(8.128846883773804)
request_session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC)
[Thread httpd@8080_2] Session id from global variable: '67d4-f7a3-f754-2de5.MEE-Windows-PC' session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) idle(1.319667100906372) request_session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) [Thread httpd@8080_1] Session id from global variable: ‘67d4-f7a3-f754-2de5.MEE-Windows-PC’
session_id(67d4-f7a3-f754-2de5.MEE-Windows-PC) idle(3.7105391025543213)

Cheers
Mike

:- use_module(library(http/http_server)).
:- use_module(library(http/http_session)).

start_server :-
http_server([port(8080), timeout(60)]).

:- http_handler(root(.),
http_redirect(moved, location_by_id(home_page)),
[]).
:- http_handler(root(home), home_page, []).

:- debug(http_session).

home_page(_Request) :-
reply_html_page(
title('Session test server'),
\test_form).

test_form -->
html([form([action(button_pressed), method(post)],
input([type(submit), value('Submit')]))]).

:- http_handler(root(button_pressed), test_page, []).

test_page(Request) :-
( memberchk(session(Request_Session_ID), Request)
-> format(user_error, '~w~n', [request_session_id(Request_Session_ID)])
; format(user_error, 'No session ID in request~n', [])
),
( http_in_session(Session_ID)
-> http_current_session(Session_ID, idle(Idle)),
format(user_error, '~w ~w~n', [session_id(Session_ID), idle(Idle)])
; otherwise
-> format(user_error, 'No session~n', [])
),
reply_html_page(
title('Session test server'),
\test_form).

session_test.pl (1.17 KB)

That is correct. The session is not in the original request structure. Once gone through the expansion, it is. As a result, the task for determining and validating the session is done exactly once per request. Subsequent calls inside the request handling that want access to the session find the information in the request structure.

That is correct too. If you look into the idle time maintenance, you’ll see it records the last used time in a dynamic predicate. We reduce the logic by rounding the time to 10 seconds. As the idle counter is reset inside the handler, it is always between 0 and 10 seconds. What happens though is that a request after the session timeout gets a new session. So, if you ask for session data, it is gone.

This does not set the session timeout. It sets the timeout for Keep-Alive connections. That is, aft er a worker processes a request from a client it keeps the connection open for this amount of time. If the client makes a new request before the timeout, the connection is re-used. Else, the server closes it. As this keeps a server thread waiting, it is rather expensive and the value should normally be kept at a value that is just high enough to deal with the series of requests typically involved with loading a page.

To change the session timeout, use

http_set_session_options([timeout(60)])

I made a little test with your program and it behaves as advertised as far as I can tell.

Thanks Jan. My silly error specifying the wrong timeout and using a test timeout of less than 10 seconds sent me down a rabbit hole. Everything of course works perfectly :slight_smile:

1 Like