Forum Discussion

Alex_B_'s avatar
Alex_B_
Icon for Nimbostratus rankNimbostratus
Jan 20, 2017

Respond with cached content after timeout

As commented in the explanation for the "after" command (https://devcentral.f5.com/wiki/iRules.after.ashx) we can make some actions with the http request. It's possible to respond with cached content if web acceleration application is also configured with stand-in period codes? Something like that:

when RULE_INIT {
    set static::response_timeout  15000
}

when HTTP_REQUEST {

    set monitor_id [\
        after $static::response_timeout {

        Respond with cached content

       }\
    ]
}

when HTTP_RESPONSE {
    log local0. "Received server response. "
    if {[info exists monitor_id]} {
        log local0. "Canceling after script with id $monitor_id"
        after cancel $monitor_id
}

Thanks in advance.

1 Reply

  • Hi yes you can do this but it's quite complicated unfortunately. You'll need to setup 2 virtual servers, vs_webapp, and vs_webapp_backend (the 2nd one needs to have the same name as the first one, but with '_backend' appended). The config of the 2 can be identical with the exception of the iRles (below) attached. In addition only vs_webapp needs to be enabled on the ingress VLAN.

    vs_webapp has the following iRule attached;-

    when RULE_INIT {
         The wait interval in ms
        set static::wait_ms 200
         The low watermark for wait iterations - used for conditional GETs where stale content exists in WAM - when set low will result in more requests being served stale content
         Use 0 here to achieve "serve stale on expiration" behaviour
        set static::low_loop_cnt 20
         The high watermark for wait iterations - used for non-conditional GETs where no stale content exists in WAM - this must be hi gher than $low_loop_cnt
         when set too low will result in many concurrent requests being sent to OWS and if set too high may result in too many requests queued concurrently
         would suggest 60<=high_loop_cnt<=300 but depends on the application and baseline testing of that.
        set static::high_loop_cnt 240
    }
    when HTTP_REQUEST {
    
        set debug 0
    
         log prefix for connection/request tracking. 
         All debug logs will start with [xxxx.yyyy] where xxxx is the connection and yyyy is the request id
        if {$debug} { 
             per request identifier
            set prefix "\[[expr {int (rand() * 10000)}]\] "
             this notifies backend VIP we are in debug mode and passes the connection prefix.
            HTTP::header replace "X-tls-debug" $prefix
         }
    
         set table "[getfield [HTTP::host] : 1][string tolower [URI::decode [HTTP::path]]]"
         if {$debug} {log local0. "${prefix}$table [clock clicks -milliseconds]"}
    
          Check if there is already an in-progress request for this host/page combo
         set i 1
         while {[table lookup $table] ne "" } {
             if {$i == 1} {
                  Mark (to the downstream virtual) this as a queued request by inserting header
                 HTTP::header insert X-mcms-queued "yes"
                 if {[table lookup $table] eq "COND"} {
                      Use the low loop watermark as this object is in cache so stale content could be served
                     set loop_cnt $static::low_loop_cnt
                 } else {
                      Use the high loop watermark as this object is not in cache so stale content cannot served
                     set loop_cnt $static::high_loop_cnt
                 }  
             }
              Limit the number of times this gets executed so that we know whether it timed out (never returned) 
              or whether the request returned successfully and table entry was deleted by backend virtual
             if {$i > $loop_cnt} {
                  Exceeded loop count
                 if {$debug} {log local0. "${prefix}Exceeded $loop_cnt, break out of loop"}
                 break
             }
    
              Wait for $static::wait_ms before checking again 
              hopefully by the time the request moves through,  WAM will have a valid copy of the page cached
             after $static::wait_ms
             incr i
          }
          if {$debug && $i > 1 } {log local0. "${prefix}Stopped waiting after $i loops"}
    
          if {[HTTP::header exists X-mcms-queued]} {
              wait just a few milli-seconds to ensure request gets into cache
             after 10
          }             
    
           Choose downstream virtual (append backend to this virtual name) - WAM processing will take place before traffic hits this next virtual
          virtual "[virtual]_backend"
    }
    

    and vs_webapp_backend has the following iRule attached;

    when HTTP_REQUEST {
    
      set debug 0
    
      if {[HTTP::header exists "X-tls-debug"] } {
        set prefix [HTTP::header "X-tls-debug"]
        set debug 1
      } 
    
       Set table name - this MUST match the name used in the iRule on the front-end VIP
      set table "[getfield [HTTP::host] : 1][string tolower [URI::decode [HTTP::path]]]"
    
       Check if conditional GET - this can be used to inform front-end whether to wait a short time (and serve stale content),  or a long time
      if {[HTTP::header exists "if-none-match"] || [HTTP::header exists "if-modified-since"]} {
        set req_stat "COND"
        if {[HTTP::header exists X-mcms-queued]} {
           This was a conditional queued request that was not served from cache - could mean request still in progress but exceeded high_loop_cnt
          if {$debug} {log local0. "${prefix}Request timed out $table.....invoke stand-in functionality"}
          HTTP::respond 500 noserver Retry-After 600
          return    
        } elseif {[table lookup $table] ne "" || [table lookup "200$table"] ne ""} {
           Flag indicating request in progress is now set or flag indicating response just returned is set - serve stale 
          if {$debug} {log local0. "${prefix}race condition $table.....invoke stand-in functionality"}
            HTTP::respond 500 noserver Retry-After 600
            return  
          }
        } else {
             Mark as non conditional GET
            set req_stat "N_COND"
        }
      } 
       Create table entry with timeout that will allow the high watermark to be reached by queued requests
      table set $table $req_stat indefinite [expr {int($static::high_loop_cnt * $static::wait_ms / 1000) + 1}]
    
      if {$debug} {log local0. "${prefix}session key $table is set to '[table lookup $table]' with expiry [expr {int($static::high_loop_cnt * $static::wait_ms / 1000) + 1}]"}
    }
    when LB_FAILED {
         Either the pool is down,  or the selected pool member has failed to complete the 3-way handshake
      if {$debug} {log local0. "${prefix}LB_FAILED"}
    
       Return a 500 to trigger WAM caching 'stand-in' functionality
      HTTP::respond 500 noserver Retry-After 600
      return
    } 
    when HTTP_RESPONSE {
       It was a page request for which we have now received a response - delete the flag (table entry) indicating that there is currently an request for this page in progress
      table delete $table
    }