iRule Developer:
Sake Blok
F5 Partner:ion-ip
iRule Name:
"iRule_Persist_On_HTTP_Data"
iRule Description:
"This iRule provides persistency based on information in the data-part of the http requests and http responses. It picks up a (custom) tag from the http-response-data to use for persistency. When another request contains this tag in the http-request-data, the request will be sent to the node that first had this tag in it's response."
Key Challenges:
"In a SOAP-based authentication-sceme all requests were spread across a pool of authentication-servers. These servers authenticated the requests by LDAP in the backend and cached the result locally for optimalisation. However, since the requests were loadbalanced to the whole pool, every authentication-server was building a cache for all session-id's. This did not scale well and demanded the use of heavier servers to be able to keep up the work. This iRule is a simplified version of the one that actually runs in production."
Alternatives:
"An alternative was to replace the servers by bigger ones."
Reduced Costs:"This solution did save a lot of money for the end-customer. I do not have an estimate on the amount though."
How it works:
"The iRule collects the data-part from a http-request. It then parses this data to see whether a session-id-tag is present. If it is present, it will look up the session-id in the tag in the persistency-table. It then forwards the request to the server that originally created this session-id. If the session-id-tag is not present, a flag will be set to search for a new session-id in the http-response. If a session-id-tag is found in the http-response, then a new session-id will be added to the persistency-table that links the session-id to the server that created the session-id."
Judge Comments:- "High business value." , Zeus Kerravala
- "Persistence is always a tricky issue. In a SOAP/XML and Web services world, it can be a significant challenge. This rule does a nice job of not only addressing the challenge in one centralized location, it does it elegantly. Not only does it reduce management complexity, it also reduces captical expense costs which is a nice bonus!" , Jeff Browning
- "I really like the way logging is utilized...only when it's necessary, and turned on/off by a single variable, very elegant! The extent to which traffic can be persisted is really shown off here, nice work!" , Jason Rahm
- "This is one of the more sophisticated iRules, and shows a subtle understanding of HTTP." , Jason Bloomberg
- "Very nice. Persistence seems to get re-hashed constantly, and this is one more cool permutation." , Colin Walker
- "I like the extensive conditional debugging. Debugging is one of the biggest issues for new iRule writers and this one shows how to build multiple levels of debugging and turn them on or off with a configuration variable. Plus, any iRule that reduces the need for more servers is good in my book." , Joe Pruitt
when RULE_INIT {
# Debug off (0), Errors-only(1), On(2) or Verbose(3)
set ::debug 3
}
when CLIENT_ACCEPTED {
if { $::debug>=1 } { set cip "[IP::client_addr]:[TCP::client_port]" }
}
when HTTP_REQUEST {
# Do not search the http-response for a new session-id unless a session-id tag was not found in the http-request
set add_persist 0
# look for the content-length header, we need to parse the http-request data
if { [HTTP::header exists "Content-Length"] } {
if { $::debug>=3 } { log local0. "$cip : Collecting [HTTP::header "Content-Length"] bytes of http-request-data" }
# Now collect the HTTP-payload and process it in the http-request-data event
HTTP::collect [HTTP::header "Content-Length"]
} else {
# We should never enter this block!
if { $::debug>=2 } { log local0. "$cip : No Content-Length header in http-request" }
}
}
when HTTP_REQUEST_DATA {
# Look for session-id tag
set sid [findstr [HTTP::payload] "" 9 ""]
if { $sid eq "" } {
# No session-id-tag found, we need to parse the http-response-data for a new session-id
if { $::debug>=3 } { log local0. "$cip : Session-id-tag was not found in http-request!" }
set add_persist 1
} else {
if { $::debug>=2 } {
if { $::debug>=3 } { log local0. "$cip : Found session-id $sid in http-request" }
set p [persist lookup uie $sid ]
if { $p ne ""} {
log local0. "$cip : Sid $sid found in persistency-table ([lindex $p 0] [lindex $p 1] [lindex $p 2])"
} else {
log local0. "$cip : Sid $sid has timed out, adding again!"
}
}
persist uie $sid
}
# Proceed with normal http-parsing
HTTP::release
}
when HTTP_RESPONSE {
# Only parse response if there was no session-id in the request
if { $add_persist == 1 } {
if { [HTTP::header exists "Content-Length"] } {
set len [HTTP::header "Content-Length"]
} else {
set len 4294967295
}
if { $::debug>=3 } { log local0. "$cip : Collecting $len bytes of http-response-data" }
HTTP::collect $len
} else {
if { $::debug>=3 } { log local0. "$cip : No need to parse http-response" }
}
}
when HTTP_RESPONSE_DATA {
# Only parse response if there was no session-id in the request
if { $add_persist == 1 } {
if { $::debug>=3 } { log local0. "$cip : Searching for session-id-tag in http-response-data" }
set sid [findstr [HTTP::payload] "" 9 ""]
if { $sid ne "" } {
if { $::debug>=3 } { log local0. "$cip : Found session-id $sid in http-response" }
set p [persist lookup uie $sid ]
if { $p eq ""} {
persist add uie $sid
if { $::debug>=2 } { log local0. "$cip : Sid $sid added to persistency-table" }
} else {
if { $::debug>=1 } { log local0. "$cip : Sid $sid already in persistency-table ([lindex $p 0] [lindex $p 1] [lindex $p 2])" }
}
} else {
if { $::debug>=2 } { log local0. "$cip : Session-id-tag was not found in http-request nor http-response!" }
}
} else {
if { $::debug>=3 } { log local0. "$cip : No need to parse http-repsonse-data" }
}
HTTP::release
}
when LB_FAILED {
if { $::debug>=1 } { log local0. "$cip : LB failed for sid $sid ([lindex $p 0] [lindex $p 1] [lindex $p 2])" }
# Delete old persistency-entry, otherwise reselect will return the node from the persistency-table again
persist delete uie $sid
LB::reselect pool [lindex $p 0]
}