Forum Discussion

FishNiX_29746's avatar
FishNiX_29746
Icon for Nimbostratus rankNimbostratus
Feb 19, 2012

Flexible host + uri redirector with data-groups

Hi!

We have an irule that I've been trying to port over from v9. In v9 it was simple... I had 2 classes, a keep_uri and a nokeep_uri class, each in the format:

hostheader1.example.com /someuri1 http://destination1.example.com/otheruri1 301

hostheader2.foobar.com /someuri2 http://destination2.example.com/otheruri2 301

hostheader3.foobaz.com /someuri3 http://destination3.example.com/otheruri3 302

etc...

From there I could easily loop looking for a [HTTP:host] match and then loop looking for a [HTTP::uri] starts_with [getfield $row " " 2] in the "keep_uri" case or in the nokeep_uri, ([string tolower [HTTP::uri]] equals [getfield $row " " 2]) or ([string tolower [HTTP::uri]] starts_with "[getfield $row " " 2]/"). In v10/v11, the data-groups have been moved to key-value pairs, which I understand is probably faster and easier to search, but feels less flexible.

Here is what I've come up with:


ltm rule redirect_by_host_and_uri {

when HTTP_REQUEST {

get rid of any port information in the host header + set the url variable

set url [substr [string tolower [HTTP::host]] 0 ':' ][HTTP::uri]

log local0. "Saved into url variable: $url"

if { [class match $url starts_with redirect_301_keep] } {

HTTP::respond 301 Location [class match -value -- $url starts_with redirect_301_keep][HTTP::uri]

log local0. "Matched $url in redirect_301_keep, redirecting to [class match -value -- $url starts_with redirect_301_keep][HTTP::uri]"

} elseif { [class match $url equals redirect_301_nokeep] }{

HTTP::respond 301 Location [class match -value -- $url equals redirect_301_nokeep]

log local0. "Matched $url in redirect_301_nokeep, redirecting to [class match -value -- $url equals redirect_301_nokeep]"

} elseif { [class match $url equals redirect_302_nokeep] }{

HTTP::redirect [class match -value -- $url equals redirect_302_nokeep]

log local0. "Matched $url in redirect_302_nokeep, redirecting to [class match -value -- $url equals redirect_302_nokeep]"

}

set cn redirect_301_nokeep

set id [class startsearch $cn]

while { [class anymore $cn $id] } {

set n [class nextelement $cn $id]

set u [lindex $n 0]

set r [lindex $n 1]

if { ($url starts_with "$u/") }{

log local0. "Matched url: $url with element [class nextelement -name $cn $id]"

HTTP::respond 301 Location $r

break

}

}

class donesearch $cn $id

set cn redirect_302_nokeep

set id [class startsearch $cn]

while { [class anymore $cn $id] } {

set n [class nextelement $cn $id]

set u [lindex $n 0]

set r [lindex $n 1]

if { ($url starts_with "$u/") }{

log local0. "Matched url: $url with element [class nextelement -name $cn $id]"

HTTP::redirect $r

break

}

}

class donesearch $cn $id

}

}

ltm data-group internal redirect_301_keep {

description "A list of host/uri pairs to redirect with 301 and keep the uri intact."

records {

foobar.example.com/foo/bar {

data http://www.example.com/

}

}

type string

}

ltm data-group internal redirect_301_nokeep {

description "A list of hosts/uris to match and 301 redirect without keeping the URI"

records {

foobar.example.com/baz {

data http://www.example.com/

}

}

type string

}

ltm data-group internal redirect_302_nokeep {

description "A list of host/uris to match and 302 redirect"

records {

foobar.example.com/302me {

data http://www.example.com/something

}

}

type string

}

This actually seems to work if I have more than 1 entry in the classes, but when I have one entry (as shown) I get these errors:

Feb 19 07:56:18 tmm err tmm[8448]: 01220001:3: TCL error: /Common/redirect_by_host_and_uri - Class iterator has no more elements (line 37) invoked from within "class nextelement -name $cn $id"

Feb 19 07:57:56 tmm2 err tmm2[8450]: 01220001:3: TCL error: /Common/redirect_by_host_and_uri - Class iterator has no more elements (line 37) invoked from within "class nextelement -name $cn $id"

I think I understand *why* I get these errors, but I don't understand how to prevent it. Is there a way to act on the *current* element instead of nextelement? Is there a better way to do this?

Thanks!

3 Replies

  • Hi FishNiX,

     

     

    A couple points:

     

     

    You can retrieve the name and value of a data group entry using -element. This avoids needing to run two class searches to check if the name matches and then get the value.

     

     

    I haven't read your rule closely enough to know why you're looping through the data group. Ideally, you'd avoid this by using default class command options. If that can't be done, then save the value of the current element and use that to log values. When you log [class nextelement -name $cn $id] you're actually getting the next element instead of logging the current element. If you only have one element in the data group, you'll get the runtime error.

     

     

    Aaron
  • Thanks Aaron -

     

     

    I don't think I have a choice wrt iterating over the data-group... I want to modify the key when doing a match and I'm not sure of a better way to do that. For example:

     

     

    Someone requests site.example.com/foo/bar

     

     

    I have a data-group entry that says site.example.com/foo { data http://someothersite.example.com/baz }

     

     

    I need to match on equals site.example.com/foo or starts_with site.example.com/foo/ <-- notice the trailing slash

     

     

    I don't know if there is better way to modify the entry directly with the class command?
  • BTW - Is the only way to use the [class startsearch $cn], [class anymore $cn $id], class donesearch $cn $id and get the current element, to use a counter? Something like this...

    
    set cn redirect_302_nokeep
    set id [class startsearch $cn]
    set i 0
    
    while { [class anymore $cn $id] } {
    
      set n [class element $i $cn]
    
      set u [lindex $n 0]
      set r [lindex $n 1]
    
       Do some stuff with the CURRENT values
    
      set i [expr {$i + 1}]
    }
    class donesearch $cn $id
    
    

    I assume I could do a foreach too? not sure if that would be a lot slower? Something like this

    
    set cn redirect_302_nokeep
    foreach { e } { [class names $cn] } {
    
      set u [lindex $e 0]
      set r [lindex $e 1]
    
       Do some stuff
    
    }