For more information regarding the security incident at F5, the actions we are taking to address it, and our ongoing efforts to protect our customers, click here.

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
No CommentsBe the first to comment