Weblogic JSessionID Persistence

Problem this snippet solves:

Contributed by: unRuleY, Summarized by: deb

Note: The previous version of this iRule contained escaped newlines following the session command, which in versions 10.0 - 10.2.0 causes TMM to core as documented in CR135937 / SOL11427. This was fixed in 10.2.1.

See this related Codeshare example for details on how to take advantage of session replication on the WebLogic servers with targeted node failover in an iRule.

Provides persistence on the jsessionid value found in either the URI or a cookie.

When a request is received, the iRule first looks for a "jsessionid" cookie, and if not found, for a "jsessionid" parameter in the requested URI. If either is found, a persistence record is created if it doesn't already exist, or followed if it does. If neither is found, the request is load balanced according to the load balancing method applied to the virtual server and persisted based on the client's IP address.

In order to ensure the second and subsequent requests follow the first, LTM must create a persistence record indicating the pool member to which the first request was load balanced. If the server is setting the jsessionid in a cookie, the persistence key value may be extracted from the server response to create the persistence record. If the server is setting the jsessionid in the URLs, source address persistence with a short timeout is recommended to track the original destination until the jsessionid is sent.

How to use this snippet:

To ensure a new persistence record is followed when a request is re-load balanced in a client-side Keep-Alive connection, apply a OneConnect profile to the virtual server.

The iRule assumes the jsessionid is in upper case when used as a cookie name. If this isn't the case, please update the example.

To persist on jsessionid, create the iRule below and create a custom Universal persistence profile, with Match Across Services enabled, that uses the iRule. Then use this custom Universal persistence profile as the Default Persistence profile on your Virtual Server. Applying a Fallback Persistence profile of type Source Address Affinity with a host mask and a short timeout (the default source_addr persistence profile will do the trick) to your Virtual Server is also recommended.

Attention, if you are running firmware 11.0 - 11.2.1 and enabled "Match Across Services"! There is a bug inside. SOL14061

This iRule requires LTM v10. or higher.

Code :

when HTTP_REQUEST {
   # Log details for the request
   set log_prefix "[IP::client_addr]:[TCP::client_port]"
   log local0. "$log_prefix: Request to [HTTP::uri] with cookie: [HTTP::cookie value JSESSIONID]"

   # Check if there is a JSESSIONID cookie
   if { [HTTP::cookie "JSESSIONID"] ne "" }{
      # Persist off of the cookie value with a timeout of 1 hour (3600 seconds)
      persist uie [string tolower [HTTP::cookie "JSESSIONID"]] 3600

      # Log that we're using the cookie value for persistence and the persistence key if it exists.
      log local0. "$log_prefix: Used persistence record from cookie. Existing key? [persist lookup uie [string tolower [HTTP::cookie "JSESSIONID"]]]"

   } else {
      # Parse the jsessionid from the path. The jsessionid, when included in the URI, is in the path, 
      # not the query string: /path/to/file.ext;jsessionid=1234?param=value
      set jsess [findstr [string tolower [HTTP::path]] "jsessionid=" 11]

      # Use the jsessionid from the path for persisting with a timeout of 1 hour (3600 seconds)
      if { $jsess != "" } {
         persist uie $jsess 3600

         # Log that we're using the path jessionid for persistence and the persistence key if it exists.
         log local0. "$log_prefix: Used persistence record from path: [persist lookup uie $jsess]"
      }
   }
}
when HTTP_RESPONSE {
   # Check if there is a jsessionid cookie in the response
   if { [HTTP::cookie "JSESSIONID"] ne "" }{
      # Persist off of the cookie value with a timeout of 1 hour (3600 seconds)
      persist add uie [string tolower [HTTP::cookie "JSESSIONID"]] 3600

      log local0. "$log_prefix: Added persistence record from cookie: [persist lookup uie [string tolower [HTTP::cookie "JSESSIONID"]]]"
   }
}
Published Mar 18, 2015
Version 1.0

Was this article helpful?

7 Comments

  • I am wondering who recommends this:

     

    Applying a Fallback Persistence profile of type Source Address Affinity with a host mask and a short timeout (the default source_addr persistence profile will do the trick) to your Virtual Server is also recommended.

     

    The author of this post? or F5 Networks?

     

  • This irule code helps me during the deployment of WebLogic.

     

    Thanks to you .... KUDOS!!!

     

    :)

     

  • Weblogic persistence with JSessionID with max lifetime

    I've added some functionality to the rule by adding a lifetime as a max session time to the rule. The company I work for requested this functionality in order to simplify maintenance because persistent connections tend to never timeout.

    The lifetime is enabled by using a table with a lifetime. If either the persist record doesn't exist or the lifetime is exceeded for the table entry, the session is invalidated by removing all cookies. I've used an iFile for the idle/lifetime exceeded message.

    I've replaced all _ with _ for the JsessionID because if the JSessionID starts with a "_" this is interpreted as an option for the table command by TCL.

    ltm rule persist_jsessionid {
        when HTTP_REQUEST {
    
             set to 0 to disable debug logging, 1 to enable
            set DEBUG 1
    
             set idle timeout and session lifetime, for now the values are set directly in the iRule
            set IDLE 1800
            set TTL 36000
    
             used for logging src_ip/src_port, virtual
            set PREFIX "src_ip=[IP::client_addr], src_port=[TCP::client_port], virtual=[virtual name] : "
    
             log HTTP-request and Cookies header
            if { ${DEBUG} == 1 } { 
                log local0.debug "${PREFIX}[HTTP::method] [HTTP::uri] HTTP/[HTTP::version]" }
                log local0.debug "Cookie: [HTTP::header Cookie]"
            }
            set STATE ""
            set JSESSIONID [string map { \- \_ } [string tolower [HTTP::cookie value "JSESSIONID"]]]
            if { ${JSESSIONID} eq "" } {
                 JSESSIONID Cookie wasn't set or didn't have a value, so check for the cookie in the URI
                set JSESSIONID [string map { \- \_ } [findstr [string tolower [HTTP::path]] "jsessionid=" 11 ";" ]]
            }
            if { ${JSESSIONID} neg "" } {
                 read remaining session lifetime
                set LIFETIME [ table lifetime -subtable cookie -remaining "${JSESSIONID}" ]
                if { ${LIFETIME} eq "" || ${LIFETIME} == -1 } {
                     lifetime exceeded, delete persistency data and cookie
                    set STATE "expired"
                    persist delete uie "${JSESSIONID}"
                    if { ${DEBUG} == 1 } { log local0.debug "${PREFIX}state=expired, jsessionid=${JSESSIONID}" } 
                } elseif { [persist lookup uie "${JSESSIONID}"] eq "" } {
                     idle timeout, delete table entry 
                    set STATE "idle"
                    table delete -subtable cookie "${JSESSIONID}"
                    if { ${DEBUG} == 1 } { log local0.debug "${PREFIX}state=idle, jsessionid=${JSESSIONID}" } 
                } else {
                    persist uie "${JSESSIONID}" ${IDLE}
                    if { ${DEBUG} == 1 } { log local0.debug "${PREFIX}state=persist. jsessionid=${JSESSIONID}, remaining=${LIFETIME}, persist=\"[persist lookup uie "${JSESSIONID}"]\"" }
                }
            }
    
             check if state is idle or expired
            if { ${STATE} eq "idle" || ${STATE} eq "expired" } { 
                 Erase cookies
                set SETCOOKIES ""
                foreach COOKIE_NAME [HTTP::cookie names] { 
                    set SETCOOKIES "${SETCOOKIES}\r\nSet-Cookie: ${COOKIE_NAME}=; path=/; expires=Thu, 01-Jan-1970 01:00:00 GMT"
                }
                if { ${DEBUG} == 1 } { log local0.debug "${PREFIX}Session expired, action=delete, ${SETCOOKIES}" }
    
                 Respond to client
                set CONTENT [ifile get "/Common/timeout.html"]
                HTTP::respond 200 content ${CONTENT} Cache-Control "no-cache" Set-Cookie ${SETCOOKIES}
            }
        }
    
        when LB_SELECTED {
            if { ${DEBUG} == 1 } { log local0.debug "${PREFIX}Pool_member=[LB::server addr]" }
        }
    
        when HTTP_RESPONSE {
             check if the jsessionid cookie is present in the response and has a non-null value
            set JSESSIONID [string map { \- \_} [string tolower [HTTP::cookie value "JSESSIONID"]]]
            if { ${JSESSIONID} ne "" } {
                 Persist on the session cookie value for IDLE time
                persist add uie "${JSESSIONID}" ${IDLE}
                if { ${DEBUG} == 1 } { log local0.debug "${PREFIX}action=persist, jsessionid=${JSESSIONID}, idletime=${IDLE}, persist=\"[persist lookup uie "${JSESSIONID}"]\"" } 
    
                 set lifetime for cookie
                table set -subtable cookie "${JSESSIONID}" "[persist lookup uie "${JSESSIONID}"]" indefinite ${TTL}
                if { ${DEBUG} == 1 } { log local0.debug "${PREFIX}action=add, jsessionid=${JSESSIONID}, table=cookie, lifetime=${TTL}" }
            }
        }
    }
    
  • KUDOS...

     

    is there anyway to log when the JSESSIONID persistence record is deleted from the persistence database and how log it was active?

     

  • Is anyone using this iRule and upgraded to 14.x? We are receiving a TCL error now and it is causing all of our Virtual Servers configured to be inaccessible. <HTTP_REQUEST> - Can't call after responding - ERR_NOT_SUPPORTED (line 1) invoked from within "HTTP::path"

     

    *Edit - We had an LTM Policy on the Virtual Server with the JSESSIONID Persistence iRule that was causing this issue.

  • Tom for some commands just check google as F5 has real good documentation. I did not know what findst 11 was (I did know findst but not the 11 argument) but just checking google https://clouddocs.f5.com/api/irules/findstr.html . findstr [string tolower [HTTP::path]] "jsessionid=" 11. 11 is a counter for number of characters and as you see ''jsessionid='' has 11 characters, so this will match correctly find and match correcly the string.