iRules Common Concepts

1.) Logging

By making use of the built in logging features that are available to you when writing iRules you’ll be able to see what the expected outcome of a rule will be before effecting live traffic, troubleshoot a malfunctioning rule by identifying which sections are failing, identify errors in logic or coding that are returning unexpected results, etc. Logging is the first step in any good troubleshooting guide once you’ve confirmed the rule compiles properly. Logging is your friend!
iRule Source
when HTTP_REQUEST {
  log local0. "Domain: [HTTP::host]"
  log local0. "URI: [HTTP::uri]"
}

when HTTP_RESPONSE {
  if { [HTTP::header exists "Location"] } {
    log local0. "Location: [HTTP::header Location]"
  }
}

Logging is great for troubleshooting, but can impact performance. Rather than adding logging statements for troubleshooting and removing them when finished, you can set a debug variable and place all appropriate logging within IF statements so the logging can be enabled & disabled with a single variable setting.
Note: The static prefix was introduced with v10 and allows for Clustered Multi-Processing (CMP; effectively multicore or parallel processing) compatible variables (read only constants or per TMM read/write). A separate copy of each variable marked static is created on each TMM instance (effectively each processor core) and each instance is completely independent of the others.

when RULE_INIT {
  set ::debug 1
}

when HTTP_REQUEST {
  if { $::debug } {
    log local0. "Domain: [HTTP::host]"
    log local0. "URI: [HTTP::uri]"
  }
}

when HTTP_RESPONSE {
  if { $::debug } {
    if { [HTTP::header exists "Location"] } {
      log local0. "Location: [HTTP::header Location]"
    }
  }
}

2.) HTTP Redirect

Simple HTTP redirect upon connecting to a particular URI. This is a very commonly requested iRule function, though sometimes the logic surrounding the redirect itself can be a bit more involved, the concept remains the same. Make logical comparisons, if a certain criteria is found, send the request to a given location.
iRule Source
when HTTP_REQUEST {
   if {[HTTP::path] eq "/"}{
      log local0. "redirecting client [IP::client_addr]"
      HTTP::redirect "http://[HTTP::host]/cda/homepage.do"
   }
}

Also see: HTTPToHTTPSRedirect_302

3.) URI Re-Writing

Users often times want to be able to seamlessly re-write the URI portion of the HTTP request a user sends in. For instance, when you request http://mydomain.com, you get sent to http://mydomain.com/userlogin.aspx?login=new This can be done without a redirect, and is transparent to the user until the response from the server contains the new URI.
iRule Source
when HTTP_REQUEST {
   if {[HTTP::uri] == "/"}{
      HTTP::uri "/New/URI"
   }
}

4.) JSessionID Persistence

This rule searches for the JSESSIONID stored in a cookie and persists based on that information if it exists. Many different permutations of such a rule might exist, including ones that look in the URI for the JSESSIONID, or perform different actions when one is not found.
iRule Source
when HTTP_REQUEST {
  if { [HTTP::cookie exists "JSESSIONID"] } {
    persist uie [HTTP::cookie "JSESSIONID"]
  } else {
    set jsess [findstr [string tolower [HTTP::query]] "jsessionid" 11 ";"]
    if { $jsess != "" } {
      persist uie $jsess
    }
  }
}

5.) Limit the number of Client Requests

This rule limits the number of connections that any given client IP can establish with the virtual server that the rule is applied to. This can be modified to include a white-list of internal or trusted IPs that wouldn’t be subject to the limiting, to redirect those that exceed the limit to an explanation page, etc. With the normal_close local variable, we only decrease the amount of active connections if the close event is executed normally, and not after the reject command execution from the CLIENT_ACCEPTED event.
iRule Source
when RULE_INIT {
  array set ::active_clients { }
}
when CLIENT_ACCEPTED {
  set client_ip [IP::remote_addr]
  set normal_close 1
  if { [info exists ::active_clients($client_ip)] } {
    if {$::active_clients($client_ip) > 5 } {
      set normal_close 0
      reject
      return
    } else {
      incr ::active_clients($client_ip)
    }
  } else {
    set ::active_clients($client_ip) 1
  }
}
when CLIENT_CLOSED {
  if { $normal_close eq 1 }
   {
     if { [info exists ::active_clients($client_ip)] } {
          incr ::active_clients($client_ip) -1
          if { $::active_clients($client_ip) <= 0 } {
               unset ::active_clients($client_ip)
             }
        }
   }
}

6.) Information inserted into HTTP Header (Header Inserts/Reading)

Many users are looking to store some information from the SSL cert or transaction in a header to be passed to their back-end servers for use in a later
iRule Source
when CLIENTSSL_CLIENTCERT {
  # set time to maintain session data (in seconds)
  set session_timeout 300

  set ssl_stuff [list anything1 anything2]
  set ssl_cert [SSL::cert 0]
  set ssl_errstr [X509::verify_cert_error_string [SSL::verify_result]]
  lset ssl_stuff 0 $ssl_cert
  lset ssl_stuff 1 $ssl_errstr
  session add ssl [SSL::sessionid] $ssl_stuff $session_timeout
}

when HTTP_REQUEST {
  set ssl_stuff2 [session lookup ssl [SSL::sessionid]]
  set ssl_cert2 [lindex $ssl_stuff2 0]
  set ssl_errstr2 [lindex $ssl_stuff2 1]
  if { $ssl_errstr2 eq "ok" } {
    HTTP::header insert SSLClientCertStatus $ssl_errstr2
    HTTP::header insert SSLClientCertSN [X509::serial_number $ssl_cert2]
  } else {
    # send HTTP 302 redirect to an error page
    HTTP::redirect "http://192.168.0.64/error.html"
  }
}

7.) Loadbalance Reselect

There are certain situations in which users want to use the same request that was sent from the client, but retry transmitting it to a different member of a pool (whether it’s the same pool as the last attempt or not). This can be because of a failure on the first attempt or because of some application needs they have in their configuration.
iRule Source
when CLIENT_ACCEPTED {
  TCP::collect 34
}

when CLIENT_DATA {
  set usrid [string range [TCP::payload] 24 33]
  if {[regexp {\d{10}} $usrid]} {
    persist uie $usrid
    log local0. "Persisting $usrid"
  }
}

when LB_FAILED {
  set p [LB::server pool]
  set n [LB::server addr]
  log local0. "Set node down: $n"
  LB::down node $n
  log local0. "Deleting $usrid from Pool $p"
  persist delete uie $usrid
  LB::detach
  LB::reselect pool $p
  set x [LB::server addr]
  log local0. "After reselect node is $x"
}

8.) Query Pool for Information

Some commands allow you to query a given pool for information, whether that’s the active_members command to see how many pool members are marked as up, or something like the LB::status command, the information can be used in many different and useful ways.
iRule Source
when LB_FAILED {
  if { [LB::status pool $poolname member $ip $port] eq "down" } {
    log "Server $ip $port down!"
  }
}

when HTTP_REQUEST {
  if { [active_members your_pool] < 1 } {
    HTTP::redirect "http://somedomain.com/somepage.html"
  } else {
    pool your_pool
  }
}

9.) Session / Persistence table lookups

Besides being extremely useful ways to retrieve information being stored in the Session or Persistence tables, knowing how to perform session and persistence lookups allows users to store data in those tables as opposed to using the much more resource intensive, messier global variable option.
iRule Source
when HTTP_REQUEST {
  set persistA [persist lookup simple [IP::client_addr]]
  log "persistA output is $persistA"
}
when CLIENTSSL_CLIENTCERT {
  log "begining"
  log "ssl cert count = + [SSL::cert count]"
  if { [SSL::cert count] > 0 } {
    session add ssl [SSL::sessionid] [X509::cert_fields [SSL::cert 0]
    [SSL::verify_result] whole] 450
    log "session ID: + [SSL::sessionid] + [X509::cert_fields [SSL::cert 0] [SSL::verify_result] whole]"
    log "certfields lookup = [session lookup ssl {[SSL::sessionid] any}]"
  }
}

10.) Using matchclass and findclass to query Data Groups

By storing data in Data Groups or “classes”, users are able to store large data structures in an organized, reasonably efficient structure that allows for use across multiple connections in a flexible manner.
iRule Source
class myCSID {
  "b02181 pl_1"
  "h05527 pl_1"
  "x07770 pl_2"
  "y07070 pl_3"
}

iRule Source

when HTTP_REQUEST {
  set CSID [string tolower [URI::path [HTTP::uri] 1 1]]
  if { [matchclass $::myCSID starts_with $CSID] } {
    pool [findclass $CSID $::myCSID " "]
  } else {
      pool defaultPool
  }
}