CodeShare Refresh: HTTP Session Limit
The iRules CodeShare on DevCentral is an amazingly powerful, diverse collection of iRules that perform a myriad of tasks ranging from credit card scrubbing to form based authentication to, as in toda...
Published Dec 27, 2011
Version 1.0Colin_Walker_12
Historic F5 Account
Joined May 12, 2005
Colin_Walker_12
Historic F5 Account
Joined May 12, 2005
brad_11480
May 10, 2018Nimbostratus
This is the code I finally put in place to limit sessions (not connections). It uses an application set cookie. Many times applications will have this JSESSIONID is common. Otherwise a cookie can be inserted if the application doesn't set one. The first call will not have a cookie-- indicating a new user with a new session. Once that cookie exists it will honor the session even if the limit has been reached.
devcentral codeshare seems to be based on a connection not a session as it maintains the source port and IP
as web sessions may initiate multiple sessions from the same IP but different ports.
In this case the application maintains a session cookie which is specified in static sessioncookie variable.
(could always add a HTTP_RESPONSE section to create/insert a cookie if the application doesn't maintain one).
If the cookie doesn't exist this must be a new user who hasn't established a server session yet. In that case
check the current session table size and if we are under the limit, let them in. The server will generate the session cookie.
Subsequent calls will be permitted regardless of the size of the active session table- don't want to drop a user who is in session.
Active session table entries are aged for responsewait seconds. If the user is slow the entry will drop out and could
permit another user to begin a session even though this user returns and recreates their sessioncookie entry.
In that case it will go 'overlimit'. This might need tweaking depending on the application use.
when RULE_INIT {
set static::max_active_clients 2000
set static::responsewait 300
set static::sessioncookie "ANONYMOUS_COOKIE"
}
when HTTP_REQUEST {
this gets the pool name assuming it is in the /Common/ path so it strips the leading /Common/ (or on/ trail)
set hstable "limit-[findstr [LB::server pool] "on/" 3]"
if {[HTTP::cookie exists $static::sessioncookie]} {
if {[HTTP::request_num] == 1}{
giving them responsewait seconds for their next transaction otherwise they will be removed from the table.
but the logic will add them back in again on their next transaction as they have a sessioncookie.
note that the lookup will reset the expiration timer.
if { [table incr -subtable $hstable -mustexist [HTTP::cookie value $static::sessioncookie]] == "" } {
log local1. "Entry for expired [HTTP::cookie value $static::sessioncookie] [IP::client_addr]:[TCP::client_port] recreated."
table set -subtable $hstable [HTTP::cookie value $static::sessioncookie] 0 $static::responsewait
}
}
} else {
if cookie not present & connection limit reached, sorry
if {[table keys -subtable $hstable -count] >= $max_active_clients} {
log local0. "SESSION LIMIT REACHED. CLIENT IP [IP::client_addr] WAS GIVEN SORRY RESPONSE. Session count/limit: [table keys -subtable httplimit -count]/$max_active_clients"
HTTP::redirect "http://sorry.domain.com/"
HTTP::respond 200 content "[ifile get ifile_iCarsLimit]" "Cache-Control" "no-cache" "Pragma" "no-cache" "Connection" "Close"
HTTP::respond 200 content "SorrySorry, we are currently busy serving other customers and cannot serve you at this moment, but please try again in a couple minutes!" "Cache-Control" "no-cache" "Pragma" "no-cache" "Connection" "Close"
event disable
}
}
}
simple code would be to simply allow the cookie to be set in the request above.
the below will see if the session cookie is being created and if so will set the table entry for it along with the timeout.
then the code above does a lookup which resets the timeout. It will issue an error if the lookup fails, which means that
the entry aged out and a subsequent request from that client came in. Log and recreate - a way to see how much this happens.
when HTTP_RESPONSE {
if {[HTTP::header exists "Set-Cookie"]} {
foreach cookievalue [HTTP::header values "Set-Cookie"] {
if {$cookievalue starts_with $static::sessioncookie} {
some applications add the cookie with each transaction - increment if there otherwise create
if { [table incr -subtable $hstable -mustexist [HTTP::cookie value $static::sessioncookie]] == "" } {
table set -subtable $hstable [findstr $cookievalue "=" 1 ";"] 0 $static::responsewait
log local1. "Entry for session $cookievalue IP: [IP::client_addr]:[TCP::client_port] created."
}
}
}
}
}
code to dump the table
if {[URI::decode [HTTP::uri]] starts_with "/F5/report"} {
if {[URI::decode [HTTP::uri]] starts_with "/F5/reportall"} {
set rpt ""
foreach key [table keys -notouch -subtable $hstable] {
set remain [table timeout -subtable $hstable -remaining $key]
set value [table lookup -subtable $hstable -notouch $key]
append rpt "\n"
}
append rpt "$static::sessioncookieTxnsTimeout$key$value$remainTimeout is $static::responsewait seconds (use /F5/reportall-reset to reset.)"
set rfc1123date [clock format [clock seconds] -format "%a, %d %h %Y %T GMT" -gmt true]
HTTP::respond 200 content "Session Limit TableSession Limit Table - $hstable$rpt" "Cache-Control" "no-cache" "Pragma" "no-cache" Date $rfc1123date Expires $rfc1123date "Connection" "Close"
if {[URI::decode [HTTP::uri]] starts_with "/F5/reportall-reset"} {
table delete -all -subtable $hstable
}
event disable
} else {
set sessct [table keys -subtable $hstable -count]
HTTP::respond 200 content "ReportCurrent Session Count/Limit is $sessct/$max_active_clients." "Cache-Control" "no-cache" "Pragma" "no-cache" "Connection" "Close"
event disable
}
}
I don't want to confuse things by introducing something else, but on the other hand, happy to share what I ended up implementing.