Forum Discussion

scott_sams_8256's avatar
scott_sams_8256
Icon for Nimbostratus rankNimbostratus
Jun 08, 2010

irule redirection question

i guess i am looking for an opinion on an idea i have.

 

 

we manage a handful of domains and are starting to get more and more redirect requests. i was told by our ecom director that i should expect more and more to come in. my concern is the way we have done them in the past is to setup a dns entry, point it to an ip, nat to a vip and then do a redirect with an irule.

 

 

i had thought that maybe i should consider doing the same but using one ip, and then just add to my irule and do a redirect based on the inbound uri request. i guess i am wondering that if this were to grow to 10,20, or even 30 different redirects would this become problematic?

 

 

thanks for any opinions or ideas on this.

 

 

scott

 

7 Replies

  • Hi Scott,

     

     

    What you've described is entirely possible. We have customers who are using datagroups with 100's and 1000's of source/target/action (rewrite, redirect, etc).

     

     

    Aaron
  • Thanks Aaron,

     

    Out of curiosity is it just and if...then setup? or something more complex. I do realize from previous redirects you must convert all to lower first.
  • Here is an example I wrote for a customer for 9.4.7, based on preliminary project plans. The iRule was never used in production, so I didn't fully test or performance tune it. I've anonymized the rule, but not tested it, so try this on a test unit or test VS first.

    If you're on 10.x you would want to replace the class references with class commands and the global variables in RULE_INIT to static::variables.

     Name: customer_HTTPS_redirects_rewrites_rule
    
     HTTPS Redirects and Rewrites iRule
    
     BIG-IP ASM
       Tested on version 9.4.7
    
     Current version: 
        v1.2 2009 Sept 10 - Aaron Hooley
    
     Version: 1.0 - 2009 Aug 14 - Initial version tested
     Version: 1.1 - 2009 Aug 27 - Updates to query string logic
     Version: 1.2 - 2009 Sept 10 - Logging updates
    
     Description:
        This rule performs HTTP redirection or host/URI rewriting based on the configuration in a datagroup.
    
     Functionality:
    
       1. This iRule can be used on an HTTP and/or HTTPS virtual server.  
          A single configuration datagroup is referenced regardless of the client side HTTP or HTTPS setup.
    
       2. The requested host and URI are checked against the source field of the configuration datagroup.  
          The longest matching configuration line is used.
          The check is done explicitly using the requested path (/path/to/file.ext in the example URI /path/to/file.ext?param=value),
          logically stripping the query string from the requested URI.
    
       3. Query string handling:
          The requested query string is not evaluated in the matching, but is preserved in rewrites and redirects.  
          If the original request has a query string, it will be preserved.  If the original request has no query string and the destination does,
          the destination query string is used.
    
     Configuration requirements:
    
        1. A datagroup named customer_redirects_rewrites_class must be configured using the following space delimited format:
    
           source (host and/or URI)   | destination (URL for redirects or host and/or URI for rewrites | action (rewrite or redirect)
           ---------------------------|----------------------------------------------------------------|-----------------------------
           www.example.co.uk/dir/     | https://www.example.co.uk/new/directory/                       | redirect
           www.example.co.uk/rewrite  | www.example.co.uk/cs/rewritten                                 | rewrite
    
           The Host portion of any entry must be set to lower case.
           The fields must use URL encoding for spaces or the parsing will fail.
           The destination must not include the protocol if the action is 'rewrite'
    
        2. The virtual server must have this iRule added as a resource.
    
    
    when RULE_INIT {
    
        Log debug messages to /var/log/ltm? (0=none, 1=minimal, 2=verbose)
       set ::customer_HTTPS_debug 1
       if {$::customer_HTTPS_debug > 0}{log local0. "Debug set to $::customer_HTTPS_debug"}
    }
    when HTTP_REQUEST {
    
       if {$::customer_HTTPS_debug > 0}{
    
          set log_prefix "[IP::client_addr]:[TCP::client_port] ([TCP::local_port])"
          log local0. "$log_prefix: -------------------------------------------------------------------------"
          log local0. "$log_prefix: New request to [HTTP::host][HTTP::uri] with UA: [HTTP::header User-Agent]"
       }
    
        Check requested host/URI against the datagroup to see if there are any matching rules
           for redirecting or rewriting the request.
        Look for the most specific (longest) match by searching first for the requested host/URI and then just requested URI.
        Evaluate all rules to find the longest match.
    
        Reset the longest match
       set longest_match 0
       set matching_line ""
    
        Loop through each datagroup line (need to update [set classname] to [class get classname] for 10.x)
       foreach line [set ::customer_HTTPS_redirects_rewrites_class] {
    
          if {$::customer_HTTPS_debug > 1}{log local0. "$log_prefix: Checking [string tolower [HTTP::host]][HTTP::path] against [getfield $line " " 1]"}
    
           Check if the current line matches the host/URI
          if {[string match [string tolower [HTTP::host]][HTTP::path] [getfield $line " " 1]]}{
    
             if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: Matched: $line"}
    
              Found a match, so save the length of the current source token
             set current_length [string length [getfield $line " " 1]]
    
             if {$current_length > $longest_match}{
    
                 This is the longest match so far
                set matching_line $line
    
                 Update the length of the current match
                set longest_match $current_length
    
                if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: New longest match: $matching_line"}
             }
          }
       }
    
        Check if we haven't found a host/URI match.  
       if {$matching_line eq ""}{
    
           Loop through each datagroup line
          foreach line [set ::customer_HTTPS_redirects_rewrites_class] {
    
             if {$::customer_HTTPS_debug > 1}{log local0. "$log_prefix: Checking [HTTP::path] against [getfield $matching_line " " 1]"}
    
              Check if the current line matches the host/URI
             if {[string match [getfield $matching_line " " 1] [HTTP::path]]}{
    
                if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: Matched: $matching_line"}
    
                 Found a match, so save the length of the current source token
                set current_length [string length [getfield $matching_line " " 1]]
    
                if {$current_length > $longest_match}{
    
                    This is the longest match so far
                   set matching_line $line
    
                    Update the length of the current match
                   set longest_match $current_length
    
                   if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: New longest match: $line"}
                }
             }
          }
       }
       if {$matching_line eq ""}{
    
          if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: No match for [HTTP::host][HTTP::uri], exiting."}
    
           If there still isn't a match then exit this event in this rule
          return
    
       } else {
    
           There was a matching line in the config datagroup so save the line
          set destination [getfield $matching_line " " 2]
          if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: Processing matching line: $matching_line"}
    
           Check for a query string in the original request as we'll need to include it in the redirect
           As there is a bug with HTTP::query (CR96326) parse HTTP::uri to get the query string
          set request_query_string [getfield [HTTP::uri] ? 2]
    
           Check if the source and destination both have a query string
          if {$request_query_string != "" && [scan $destination {%[^?]?%[^?]} destination_path destination_query_string]==2}{
    
             set destination [string map -nocase "$destination_query_string $request_query_string" $destination]
             if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: Replaced destination query string with original query string.\
                Orig: $destination_query_string Dest: $request_query_string"}
          }
           Check the action from the rule
          switch [string tolower [getfield $matching_line " " 3]] {
    
             "redirect" {
                 Redirect to the new location
                HTTP::respond 301 noserver Location [getfield $matching_line " " 2]
                if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: Redirecting to [getfield $matching_line " " 2]"}
             }
             "rewrite" {
                 Rewrite the host and URI
    
                   Need to determine if destination is a host plus URI or an absolute URI
                   The first character for a URI is a /, and a host plus URI is an alphanumeric
                switch [string range $destination 0 0] {
                   "/" {
                       Destination is a URI so just rewrite the URI
                      HTTP::uri $destination
                      if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: Rewriting URI to $destination"}
                   }
                   default {
                       Destination is a host and URI
                       Save the host and URI separately using scan to save everything up to the first / as the host
                         and everything including and after as the URI
                      scan $destination {%[^/]%s} host uri
                      if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: Parsed host: $host, URI: $uri"}
    
      HTTP::header replace Host $host
                      if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: Rewrote host to $host"}
    
                       Rewrite the URI if the destination URI is not empty or /
                      if {[string length $uri] > 1}{
                         HTTP::uri $uri
                         if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: Rewrote URI to $uri"}
                      }
                   }
                }
             }
          }
       }
    }
    when HTTP_REQUEST priority 501 {
    
        This event is for debugging only and can be commented out or removed after testing is complete
       if {$::customer_HTTPS_debug > 0 && [getfield $matching_line " " 3] ne "redirect"}{
          log local0. "$log_prefix: 501: Host: [HTTP::host], URI: [HTTP::uri]"
       }
    }
    

    Aaron
  • wow, that looks pretty in depth. i will take this and work on converting it to 10.1 and implementing in a qa. in the interim since i only have three now, would this suffice?

     

     

    when HTTP_REQUEST {

     

    if {[string tolower [HTTP::host]] equals "www.example1.com" } {

     

    HTTP::redirect "http://www.redirect1.com/redirect"

     

    }

     

    elseif {[string tolower [HTTP::host]] equals "www.example2.com" } {

     

    HTTP::redirect "http://redirect2.com/main.aspx"

     

    }

     

    elseif {[string tolower [HTTP::host]] equals "www.example3.com" } {

     

    HTTP::redirect "http://redirect3.com/lite/main.aspx"

     

    }

     

    }
  • You could do it that way. The [HTTP::host] vaule of a URL isn't case sensitive only the [HTTP::uri] or [HTTP::path] is and that depends on the Operating System hosting the website.

     

     

    I this way is alittle cleaner and easier to read:

     

    
    when HTTP_REQUEST {
    switch -glob [HTTP::host] {
    "www.website1.com" { HTTP::respond 301 Location "http://www.google.com" }
    "www.website2.com" { HTTP::redirect "http://www.yahoo.com" }
    "www.website3.com" { pool website3pool }
    }
    }
    

     

     

    HTTP::respond allows you to specify the HTTP Status Code to a 301 (Permanent)

     

    HTTP::redirect gives a default 302 (Temporary) Redirect

     

    or you can route the traffic to a specific pool.

     

     

    Hope this helps.
  • Thanks Michael,

     

    I will look at that. I do have a concern over the case sensitive issue. Originally I had created this iRule with just one redirect (added on for this) and we were having issues. The best I could tell it was as it should be based on examples

     

    here on devcentral. Finally I was forced to open a ticket due to the urgency and the tech informed me to try the case change. It worked immediately.

     

     

    when HTTP_REQUEST {

     

    if {[string tolower [HTTP::host]] equals "xxxx.com" } {

     

    HTTP::redirect "http://www.xxxx.com"

     

    }

     

    }

     

  • I'm sorry. I'm crossing my realities.

    For normal browsing (without the LTM or any iRules) then the case does not matter for what the LTM picks up as [HTTP::host].

    When you are processing the [HTTP::host] value through an iRule then the case IS sensitive and needs to be put in a known comparative state, so you should use the "string tolower". I apologize for the mistake and confusion.

    
    when HTTP_REQUEST {
        switch -glob [string tolower [HTTP::host]] {
            "www.website1.com" { HTTP::respond 301 Location "http://www.google.com" }
            "www.website2.com" { HTTP::redirect "http://www.yahoo.com" }
            "www.website3.com" { pool website3pool }
        }
    }