Request Resubmit on timeout
Problem this snippet solves:
In some cases, a BIG-IP can be sending a request to a pool member that is "stuck" at the application layer; effectively the server is inoperative. While it would be ideal to detect inoperative pool members with health monitoring, occasionally the failures are intermittent.
This iRule allows a user-defined timeout value after which it will "give up" on the originally selected server and then resubmit the request (including payload) to a new server. In order to do this, it must disable the HTTP profile on the connection and so when it resubmits the request, it inserts a Connection: close header so that the resubmitted request will be the last HTTP request for that connection.
It also detects a timeout on the retried request and if a timeout is detected, it will send a 504 Gateway Timeout error.
The current version of the iRule is built for HTTPS on both sides (client-ssl and server-ssl profile on the virtual server) but could be adapted for non SSL on either or both sides relatively easily.
How to use this snippet:
Enable iRule on Virtual Server (that has HTTP profile, client-ssl and server-ssl profiles enabled) and set the timeout value (default is 5 seconds) for HTTP request.
Code :
when RULE_INIT { # timeout in milliseconds (1000ms = 1 second) set static::response_timeout 5000 set static::retry_debug 1 } when HTTP_REQUEST { set retrycount 0 if {$static::retry_debug}{ log "Request Received for [HTTP::uri]" } set initialrequest_id [\ after $static::response_timeout { if {$static::retry_debug}{ log "Timeout $static::response_timeout elapsed without server response. [clock seconds]" LB::detach LB::reselect HTTP::disable LB::connect incr retrycount if {$static::retry_debug}{ log "Request variable contains: $newrequest" } } }\ ] set request [HTTP::request] set newrequest [findstr $request "" 0 "\r\n\r\n"] if {$static::retry_debug}{ log "Did findstr - oldrequest: $request - newrequest: $newrequest" } append newrequest "\r\nConnection: close" append newrequest [findstr $request "\r\n\r\n" 0] if {$static::retry_debug}{ log "Inserted Connection Close header - originalrequest: $request - newrequest: $newrequest" } if {[HTTP::header exists "Content-Length"]}{ HTTP::collect [HTTP::header "Content-Length"] } } when SERVERSSL_HANDSHAKE { log "Server Connected: Retry Count: $retrycount" if {$retrycount}{ if {$static::retry_debug}{ log "Going to send HTTP request data to new server" } SSL::respond $newrequest if {$static::retry_debug}{ log "Collecting Response Data from Retry and setting new timeout timer" } set retryrequest_id [\ after $static::response_timeout { if {$static::retry_debug}{ log "Double Timeout - Initial Request timed out and Retry timed out" } clientside { SSL::respond "HTTP/1.0 504 Gateway Timeout\r\nServer: BIG-IP\r\nConnection: close\r\n\r\n" } #clientside { TCP::release } clientside { TCP::close } }\ ] SSL::collect #SSL::release #TCP::release #HTTP::enable } elseif {$retrycount > 1} { if {$static::retry_debug} { log "Exceeded retry count" } } else { if {$static::retry_debug}{ log "Not a retry; request: $newrequest" } } } when SERVERSSL_DATA { if {$retrycount}{ if {$static::retry_debug}{ log "Response Data from Retried Request - Inserting Connection: close header" log "Canceling retryrequest_id" } after cancel $retryrequest_id set response [SSL::payload] set newresponse [findstr $response "" 0 "\r\n\r\n"] if {$static::retry_debug}{ log "Did findstr - origresponse: $response - newresponse: $newresponse" } append newresponse "\r\nConnection: close" append newresponse [findstr $response "\r\n\r\n" 0] SSL::payload replace 0 [string length $response] $newresponse SSL::release TCP::close clientside { TCP::close } } } when HTTP_REQUEST_DATA { if {$static::retry_debug}{ log "Got some payload data for $request" } append newrequest [HTTP::payload] if {$static::retry_debug}{ log "Newrequest with Payload: $newrequest" } HTTP::release } when HTTP_RESPONSE { if {[info exists initialrequest_id]}{ if {$static::retry_debug}{ log "Canceling after timeout with id $initialrequest_id because server responded prior to timeout" } after cancel $initialrequest_id } }
Tested this on version:
13.0