For those not in the know we here at F5 take training our field folks pretty seriously. Every time there is a new batch of Field Sales Engineers (FSEs) - the people that head from customer to customer to make the magic happen, showing off what F5 technology can really do - we bring them out to Seattle for a two week, intensive, no holds barred session of drinking from the fire hose training. During this time they go through training at the hands of some of F5's top experts on a variety of subjects. Each different technology, presentation styles, the market and what is happening currently to name just a few. It just so happens that one of the topics that has come to be a requested portion of the curriculum is this little iRules Challenge that started out as a one off on a whim from one of the awesome people putting on the boot camp.

Here's how it works: I dream up some imaginary customer scenario. Something that is conceivable for a field engineer to run into. I then lay out some requirements, and ask each engineer to solve the problem using an iRule. Mind you these people are all brand new to F5, most have little or no programming background, and I'm not necessarily playing nice with these requirements. They get a week to submit an entry, and then I judge the entries to select the top 3 for fabulous gifts and prizes (see: f5 swag). What started out as a one off is now part of the core content of the boot camp, and apparently FSE managers around the world are watching to see who wins, and cheer on the new FSEs from their region. Pretty awesome right?

So that brings us to today. Last week I had the pleasure of judging another FSE iRules challenge, and I'm here to put the winners on display to the masses as they so rightly deserve. This time around the top 2 spots were decided as narrowly as they ever have been, as both were really solid entries. Everyone that submitted an entry should be proud though, and keep on kicking tail with iRules, because every single one showed promise and a solid understanding of the concepts required to solve such a problem, especially considering some of these folks have only been with F5 for a matter of weeks.

First let's cover what the challenge was:

 

Scenario: A client needs to build in application specific redirects based on the URI, as well as query specific rewriting that is transparent to end users.

 

Desired Solution:

  • Any request to domain.com/app[1-20] should be permanently redirected to domain.com/newapp[1-20]
  • Any request to domain.com/ should be re-written for the back end as domain.com/newapp[1-20]?user=
  • This will require you to check / against a list of known users, and reference which application (newapp1-20) they should be associated with.
  • You will need to build this list of users and relate them to applications such as:
    • Bob -> 20
    • Joe -> 10
    • Sally ->
    • So domain.com/bob -> domain.com/newapp20?user=bob
  • Any response bound for the user that directs the user to domain.com/newapp[1-20]?user= must be re-written to the requesting format of domain.com/
  • All traffic to this VIP should be SSL only, including redirects where needed.

 

Like I said, these aren't kiddy pool style challenges. These are "Hi, how are you? Good to meet you. This is the deep end, enjoy your swim" style challenges. Meek need not apply. Now then, on to the winners!

 

Third Place - Clive Chan

Clive's iRule showed a solid understanding of the basic redirection portion of the challenge, and with some more time I'm confident that he'll be cranking out complete iRule solutions in the field.

   1: when HTTP_REQUEST {  
   2:   if {[class match [HTTP::uri] equals Oldapp]}{
   3:         HTTP::respond 301 Location "https://[getfield [HTTP::host] ":" 1][class search -value Oldapp equals [HTTP::uri]]"
   4:         log local0. "domain.com/app\[1-20\] permanently redirected to domain.com/newapp\[1-20\]"
   5:     } elseif {[class match [HTTP::uri] equals Userapp]}{
   6:  
   7:         set username [getfield [HTTP::uri] "/" 2]
   8:         set appnum [class search -value Userapp equals [HTTP::uri]]
   9:  
  10:         HTTP::uri /newapp$appnum?user=$username
  11:         HTTP::redirect "https://[HTTP::host][HTTP::uri]" 
  12:         log local0. "domain.com/$username re-written for the back end as domain./newapp$appnum?user=$username"
  13:     } else {
  14:         HTTP::redirect "https://[HTTP::host][HTTP::uri]" 
  15:         log local0. "Directed Traffic to SSL"
  16:     }
  17: }

Second Place - Rubyanne Deang

This iRule was at the top of my list, destined to be the winner until the very last minute when it just barely got bumped into second place. It was a heck of a race and Rubyanne shows some serious promise to be an iRules ninja in no time. She was also asking the most questions out of anyone in the group, which I see as a sign of great things to come. That hunger for knowledge and willingness to seek it out are powerful things.

 

   1: priority 50
   2: when HTTP_REQUEST {
   3:     
   4:     ## Disable the stream filter by default
   5:     STREAM::disable
   6:     
   7:     set internal_uri ""
   8:     
   9:     ## Check for a URI of /
  10:     ## Do some minimal validation of the URI before trying to parse the username
  11:     
  12:     if {[HTTP::uri] eq "/" or [HTTP::uri] contains "."}{
  13:  
  14:               # Exit this event in this rule as the URI is not in the form of /
  15:               return
  16:        }
  17:     
  18:      ## Split the URI into a TCL list on each forward slash. Examples:
  19:        set uri_list [split [HTTP::uri] /]
  20:  
  21:        ## Check if the URI split contains two elements
  22:        if {[llength $uri_list] == 2}{
  23:  
  24:               ## Parse 'app' from the second list element
  25:               set app [lindex $uri_list 1]
  26:               log local0. "uri: [HTTP::uri], parsed \$app: $app"
  27:        }
  28:     
  29:     ## when a request comes in and URI matches app[1-20]
  30:     set newapp [class match -value [HTTP::uri] ends_with app_class]
  31:     set newapp_value [class match -value $app equals username_class]
  32:     
  33:     log local0. "NEWAPP equals $newapp"
  34:     log local0. "NEW APP VALUE equal $newapp_value"
  35:     
  36:     if {$app ne "" && $newapp ne ""}{
  37:         
  38:         ## Assign newapp variable with the matching URI
  39:         
  40:         log local0. "This is the [HTTP::uri] and this is the the new URL https://domain.com/$newapp"
  41:         
  42:         ## Issue a permanent redirect to new URI
  43:         HTTP::respond 301 location "https://domain.com/$newapp"
  44:  
  45:     } elseif {$app ne "" && $newapp_value ne ""}{
  46:         
  47:          ## replace with new URI
  48:             HTTP::uri "/newapp$newapp_value?user=$app"
  49:          log local0.  "This has been rewritten, and the new URI is [HTTP::uri]"
  50:          
  51:           ## Save the find/replace strings for responses        
  52:           set internal_uri "/newapp$newapp_value?user=$app"
  53:           set external_uri "/$app"
  54:           log local0. " This is the internal URI $internal_uri"
  55:           log local0. "This is the external URI $external_uri"
  56:    }
  57:    else {
  58:           ## Do nothing to responses if there wasn't any match
  59:           set internal_uri ""
  60:           log local0. "There is no match,we will do nothing"
  61:             
  62:           }
  63:         
  64:         ## if we're potentially going to rewrite the response headers and/or content
  65:            ## prevert the server from sending a compressed response
  66:         
  67:         if {$internal_uri ne "" && [HTTP::header exists "Accept-Encoding"]}{
  68:                   log local0. "This will be rewritten, and we will prevent the server from sending a compressed response"
  69:                   HTTP::header remove Accept-Encoding
  70:            }
  71:  
  72:  
  73:     }
  74:     
  75:     
  76: ## Number 3.  Any response bound for the user that directs the user to
  77: ## domain.com/newapp[1-20]?user= must be re-written to the requesting format
  78: ## of domain.com/
  79: priority 150
  80: when HTTP_RESPONSE {
  81:       
  82:        ## Check if we're potentially rewriting this response
  83:        if {$internal_uri ne ""}{
  84:               log local0. "There is a value for internal uri which is $internal_uri"
  85:               ## Rewrite the response content using a stream profile if it is text
  86:               if {[HTTP::header Content-Type] contains "text"}{
  87:                     
  88:                      ## Set the stream expression with the find/replace strings
  89:                      STREAM::expression "@$internal_uri@$external_uri@"
  90:                      log local0. "Running a stream, replace $internal_uri with $external_uri"
  91:              
  92:                      ## Enable the stream filter
  93:                      STREAM::enable
  94:               }
  95:        }
  96: }

Last, but most certainly not least, the Winner of the iRules FSE Challenge - Brandon Frelich!

Brandon's iRule was the closest out of the bunch on functionality, showed an extremely solid understanding of programming fundamentals with some tricksy logic games that I was shocked to see a newbie playing, reminding me that these people are not at all newbs, just new to F5, and it's even pretty to look at and easy to follow. Really a phenomenal job and a great effort put in here. If you're looking to place bets on a hot iRules up and comer, you'll have to get in line behind me to bet on Brandon.

   1: # Applied to http virtual server VS_domain_com_80 for domain.com
   2: when HTTP_REQUEST {
   3:   HTTP::respond 301 Location “https://[getfield [HTTP::host] : 1][HTTP::uri]”
   4: }
   5:  
   6: # Applied to client ssl virtual server VS_domain_com_443 for domain.com:443
   7: when HTTP_REQUEST {
   8:  HTTP::header remove "Accept-Encoding"
   9:  # Grab the hostname and uri
  10:  # Set a Value to determine if we should do anything with the response
  11:  set iReWroteThis 0
  12:  set hostname [string tolower [HTTP::host]]
  13:  set uri [string tolower [HTTP::uri]]
  14:  # disable stream processing on the request
  15:  STREAM::disable
  16:  # Determine if we should bother to continue processing this request
  17:  if { ($hostname == "domain.com") } {   
  18:     if { $uri starts_with "/app" } {
  19:            # First determine where the uri ends and if there's a query after it
  20:            if { [HTTP::query] == "" } {
  21:                set a [ substr $uri 4 "?" ]
  22:            }
  23:            else {
  24:                # if there isn't a value for query string we'll take up until / in the uri.
  25:                set a [substr $uri 4 "/"]
  26:            }
  27:                    # Ok so we have the characters after. Let's see if they're first a digit
  28:                    # and then between 1 & 20
  29:                    if { [string is digit $a] && ($a <= 20) && ($a >= 1) } {
  30:                        # map /app over with /newapp but preserve the rest of the request
  31:                        HTTP::respond 301 Location [string map {"/app" "/newapp"} $uri]
  32:                    }        
  33:        }
  34:        elseif { [ class match [string map { "/" "" } $uri] equals btcp_red ] } {
  35:            set number [ class match -value [string map { "/" "" } $uri] equals btcp_red ]
  36:            set name [ class match -name [string map { "/" "" } $uri] equals btcp_red ]
  37:            set newquery "\?user=$name"
  38:            set newuri "/newapp$number$newquery"
  39:            HTTP::uri $newuri
  40:            set iReWroteThis 1
  41:            #debug use log local0. "uri is now $uri and name $name and number $number and newuri $newuri$newquery"
  42:        }
  43:     else {
  44:        # debug use log local0. "Well we didn't match anything uri is $uri"
  45:        }
  46:     }
  47: }
  48: when HTTP_RESPONSE {
  49:  # Did we rewrite the request?
  50:  if { $iReWroteThis } {
  51:         # Ok so use the previously stored value to overwrite any reference to domain.com/newapp20?user=bob -> domain.com/bob
  52:         STREAM::expression "@$newuri@$uri@"
  53:         STREAM::enable
  54:  }
  55: }
 
All in all it was a great experience, as always, and the engineers that stepped up to the challenge did an exemplary job. I'm excited for the future of the field if this is the caliber of people we're bringing on. Way to go to all of the participants, and to anyone joining the team in the mean time...your time is coming, I'll see you soon.