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
Published Sep 16, 2017
Version 1.0

Was this article helpful?

No CommentsBe the first to comment