Forum Discussion

Dayton_Gray_103's avatar
Dayton_Gray_103
Icon for Nimbostratus rankNimbostratus
Sep 20, 2007

Change URI parameters with a 301 redirect

I am looking to change a URI to remove certain query string parameters and redirect with a 301.

 

Example:

 

Incoming URI

 

www.example.com/example.jsp?zoneId=624906&zz=2255956&parentPage=example

 

or

 

www.example.com/example.jsp?zz=2255956&zoneId=624906&parentPage=example

 

to:

 

www.example.com/example.jsp?zoneId=624906

 

Basically I want to capture the zoneId parameter and drop everything else. The zoneId parameter could exist anywhere in the string.

 

Any help would be appreciated!

10 Replies

  • iRules has a helper function that will do exactly what you want. The findstr command searches a string for a substring and returns that substring up to a terminating character.

    This should do it for you:

    when HTTP_REQUEST {
      set zoneid [findstr [HTTP::uri] "zoneId=" 0 "&"]
      if { [string length $zoneid] > 0 } {
        HTTP::redirect "http://[HTTP::host][HTTP::path]?$zoneid"
      }
    }

    The findstr command above looks in the HTTP::uri value for the string "zoneId=" and will return that string including all trailing characters after that string until it hits the termination character of "&" or the end of string. So this sould return "zoneId=624906" for you above example URIs. Then the HTTP::redirect command is issued on a newly built URI with the original path "/example.jsp" appended with a "?" and the found "zoneId=624906"

    Keep in mind that this example is not case-sensitive so URIs with "ZONEID=..." will not match. If you want to match for all case examples of "zoneid=", then you with have to do a "string tolower" on the HTTP::uri before the test and only test for a lower case character.

    Also, keep in mind that a redirect will only occur for URI's that have the "zoneId=" query string argument in them.

    Hope this helps...

    -Joe
  • Joe,

     

     

    Thanks for the response this is exactly what I was looking for. I seem to have gotten myself into a redirect loop however. Can you suggest how I can resolve this? As you can see I have been trying to get out of it by setting a header and then checking for that header's existence, but it doesn't seem to be working:

     

     

    when HTTP_REQUEST {

     

    if { [HTTP::header exists "LastHop"] } {

     

    return 0

     

    }

     

    else

     

    {

     

    if { [HTTP::path] contains "/zone/" } {

     

    set zoneid [findstr [HTTP::uri] "zoneId=" 0 "&"]

     

    if { [string length $zoneid] > 0 } {

     

    HTTP::header insert LastHop 1

     

    HTTP::respond 301 Location "http://[HTTP::host][HTTP::path]?$zoneid"

     

    }

     

    }

     

    }

     

    }

     

     

    Thank you for the help!
  • The problem is that you are inserting a header in the HTTP response but that is not sent back as part of the request. If you would like to send the client something in the response and have it persisted and sent back on subsequent requests, you'll have to use a HTTP Cookie. The problem with the Cookie approach is that it will be present at a minimum of the current connection. Then future requests within the same connection will still think they have been redirected, when they actually weren't.

    Before you go the cookie route, is there any way you can put logic to not issue the redirect if it's already occurred. How about simply checking whether "zoneId=" is in the URI? If it's there, then assume there has been a redirect.

    when HTTP_REQUEST {
      if { ! ([HTTP::uri] contains "zoneId=") } {
        if { [HTTP::path] contains "/zone/" } {
          set zoneid [findstr [HTTP::uri] "zoneId=" 0 "&"]
          if { [string length $zoneid] > 0 } {
            HTTP::respond 301 Location "http://[HTTP::host][HTTP::path]?$zoneid"
          }
        }
      }
    }

    Now, it will only process the redirect if it contains "/zone/" and not "zoneId=".

    -Joe
  • Are you always removing everything but the zoneID, or is it possible that you'll get a URL with other parameters that you'll want to keep?

    If the only parameter you want is zoneID then you can just check to see if there are any other ones in the request. If theres just the zoneID param then you don't need to do a redirect. To detect this just check to see if the query string contains a '&' character. If it does then there are probably multiple query parameters and you want to redirect. If it doesn't then you're good (assuming the zoneID is there).

    Also, I'd suggest doing two string searches for zoneID, not the one that's suggested. The search you have right now will get any query parameter that ends in "zoneID", which isn't what you want. It's possible you'd detect the wrong parameter.

    Instead, you can do 2 checks. One to see if the string starts with "zoneID=". That would be the case where zoneID is the first query parameter. The other would search for "&zoneID=", which would be the case when the zoneID isn't the first parameter.

    
    when HTTP_REQUEST {
      set zoneID ""
      if { [HTTP::query] starts_with "zoneID=" } {
         We know the first entry is the correct one, so we don't need to
         worry about other params that end in "zoneID"
        set zoneID [findstr [HTTP::query] "zoneID=" 7 "&"]
      }
      else {
        set zoneID [findstr [HTTP::query] "&zoneID=" 8 "&"]
      }
      if { $zoneID == "" } {
        log local0. "No zoneID defined!"
      }
      if { [HTTP::query] contains "&" } {
        log local0. "Multiple parameters, need to redirect"
         Do your redirect here
      }
      else {
        log local0. "Only zoneID provided, no redirect"
         Do whatever you want to do when not redirecting
      }
    }
  • Joe,

     

     

    The problem is that /zone/ is a condition I have to check for as well as zoneId. So if the request uri contains /zone/ and the query string zoneId, I need to remove all other query strings and browser needs to maintain /zone/ and zoneId.
  • The final iRule that seems to be working well is:

    
    when HTTP_REQUEST {
    if { [HTTP::query] contains "&" } {
    if { [HTTP::path] contains "/zone/" } {
    set zoneid [findstr [HTTP::uri] "zoneId=" 0 "&"]
    if { [string length $zoneid] > 0 } {
    HTTP::respond 301 Location "http://[HTTP::host][HTTP::path]?$zoneid"
    }
    }
    }
    else {
    pool misc_pool
    }
    }

    Thank you both very much for all of the help!!!
  • What happens if the URL only has a single query parameter and it isn't zoneID? Right now your irule will treat it exactly the same as a URL that only has the zone ID.

     

     

    Also, what happens if there's a url like this:

     

     

     

    http://yourhost/some/path/with/zone/in/it?foo=a&myDummyzoneId=fjdfak&bar=foo

     

     

     

    No zoneID is defined in that URL, but your iRule will act as if it is, as findstr will mistakenly detect the "myDummyzoneId" param as the zoneID param. Even if zoneId is defined in that url, if something else that ends in "zoneID" appears before it in the query string then you'll pull that value out instead of the proper one.
  • aherrman,

    I appreciate the post.

    The first if statement checks to see if there is more than one query string. If there isn't it will just pass it along to the else (pool definition) unfettered which is desired behavior. Am I missing something??

    I appreciate your concern about other query strings. Could it not be written like this?:

    
    set zoneid [findstr [HTTP::uri] starts_with "zoneId=" 0 "&"]

    Or can you not use a starts_with within a findstr funciton?

    Thanks!
  • So, it's possible that what happens with only a single query parameter is exactly what you want, I just wanted to make sure. It sounds like you always want to make sure there is just a single query parameter, and that it's "zoneID". Right now your irule will treat the following URLs the same:

     

     

     

    http://yourhost/path/with/zone/in/it?foo=bar

     

    http://yourhost/path/with/zone/in/it?zoneId=1234

     

    http://yourhost/thingy?amICrazy=probably

     

    http://yourhost/thingy?zoneId=6543

     

     

     

    All of those URLs will hit the "else" case and go to the default pool. No zoneId verification will happen. Maybe that's what you want, but from your description earlier it didn't sound like it was, so I wanted to make sure you realized it had this behavior.

     

     

    As for the second thing, making sure you only get the "zoneId" parameter and not something like "myDummyzoneId", you'd have to include the character that appears before "zoneId". There are two possibilities here. Either the query string starts with "zoneId" (first parameter), in which case there is no starting parameter. To detect this you'd use starts_with on the query string. The other possibility is that zoneId is preceeded by a "&" (second or later query parameter). For this you'd use findstr and search for "&zoneId=".

     

     

    Here's the code I threw together earlier, but I'll give a bit more explanation this time.

     

     

    
    when HTTP_REQUEST {
      set zoneId ""
      if { [HTTP::path] contains "/zone/" } {
        if { [HTTP::query] starts_with "zoneId=" } {
           We know the first entry is the correct one, so we don't need to
           worry about other params that end in "zoneId"
          set zoneId [findstr [HTTP::query] "zoneId=" 7 "&"]
        }
        else {
          set zoneId [findstr [HTTP::query] "&zoneId=" 8 "&"]
        }
        if { $zoneId == "" } {
          log local0. "No zoneId defined!"
           set zoneId to some default zoneId, or reject the connection if that's
           what you want to do here
        }
        else {
          if { [HTTP::query] contains "&" } {
            log local0. "Multiple parameters, need to redirect"
            HTTP::respond 301 Location "http://[HTTP::host][HTTP::path]?$zoneId"
          }
          else {
            log local0. "Only zoneId provided, no redirect"
             Do whatever you want to do when not redirecting
          }
        }
      }
      else {
         Do whatever you want to do when /zone/ isn't in the path
        pool misc_pool
      }
    }

     

     

    ----------------------

     

     

    At the very beginning we check to see if "/zone/" is in the path. If it is then we start checking for zoneId in the query params. If it isn't then we set the pool. Note that this check will only work if "zone" isn't the last part of the path. It's possible you could get a URL like "http://path/containing/zone" which has "zone" in it but doesn't have "/zone/" because the last "/" isn't always included.

     

     

    
    if { [HTTP::path] contains "/zone/" } {
       do zoneId check here.  This is gone over below
    }
    else {
       Do whatever you want to do when /zone/ isn't in the path
      pool misc_pool
    }

     

     

    We start the zoneId check by extracting zoneId. First we check to see if the query string starts with "zoneId". If it does then we use findstr to pull it out. Calling findstr with just "zoneId=" as its argument works here because we know the first instance of "zoneId=" it finds in the query string will be the one we want.

     

     

    If it doesn't start with zoneId then search for "&zoneId=". Having the "&" at the beginning makes sure that we don't accidentally grab the value for something that simply ends in "zoneId" like "myDummyzoneId".

     

     

    
    set zoneId ""
    if { [HTTP::query] starts_with "zoneId=" } {
       We know the first entry is the correct one, so we don't need to
       worry about other params that end in "zoneId"
      set zoneId [findstr [HTTP::query] "zoneId=" 7 "&"]
    }
    else {
      set zoneId [findstr [HTTP::query] "&zoneId=" 8 "&"]
    }

     

     

    Next we check to make sure the zoneId was actually defined in the query params. I don't know what you'd want to do in this case, but maybe define a default zoneId or reject the connection.

     

     

    
    if { $zoneId == "" } {
      log local0. "No zoneId defined!"
       set zoneId to some default zoneId, or reject the connection if that's
       what you want to do here
    }

     

     

    If we do have a zoneId then we check to see if there were any other query parameters. If there were we redirect. Otherwise we do whatever you want to do when the URL is valid.

     

     

    
    if { [HTTP::query] contains "&" } {
      log local0. "Multiple parameters, need to redirect"
      HTTP::respond 301 Location "http://[HTTP::host][HTTP::path]?$zoneId"
    }
    else {
      log local0. "Only zoneId provided, no redirect"
       Do whatever you want to do when not redirecting
    }

     

     

     

     

    Hopefully that made sense and didn't just completely confuse you.

     

     

    -Andy