Forum Discussion

NathPras's avatar
NathPras
Icon for Cirrus rankCirrus
Sep 22, 2022

URL/URI rewrite without changing in Client browser

Hello All Experts,

Greetings!

 

I have an issue where I need to rewrite URL and as well as URIs from https://abc.com/xyz to https://pqr.com/mnp.

I found two ways to do that one I can use a profile first to match the URL and /path or URIs placed on in a data group list under iRule.

Other way looks like something following what I found in different DevCentral technical forum discussion.

 

if {[string tolower [HTTP::Host]] starts_with "www.abc.com" && [HTTP::path] eq "/"} { HTTP::header replace Host "partner.abc.com" HTTP::uri "/xyz" }

 

Now my problem is there are around 1200 URIs or paths which need to be translated from one One URL+URI to another URL+URI where the catch is URL+URI must not change in user's browser.

What would be correct way to do it? With proflies or with iRule.

In profile I saw that there is an option where I can create a rule where it states is any URL+URI matches it can be send done with HTTP_Header_replace and then I can add one more rule with HTTP_URI_Replace.

Challenge is how to match all those 1200 URL(where URL is constant) + URi(which is changing) from (example  https://abc.com/xyz to https://pqr.com/mnphttps://abc.com/xyz1 to https://pqr.com/mnp1 ...and the list grows)

Also to add one more point https://abc.com is on one F5 and https://pqr.com is on another F5.

Thanks,

Prasenjit

  • I'd maybe argue that partitions aren't create separate logical BIG-IPs. It's more about building separate administrative domains on a single BIG-IP. Further, partitions can always access the /Common partition. To answer your question though, it depends on the level of isolation, but you should simply be able to point to the VIP in the oter partition,

    virtual "/abc-app-dev/np-api-vip"
    
    or 
    
    virtual "/Common/abc-app-dev/np-api-vip"

    You don't need SNAT to communicate between VIPs on the same BIG-IP.

  • NathPras's avatar
    NathPras
    Dec 08, 2022

    Hi Kevin,

    Thanks for your guidance. Here is what I figured out and it is working now. All happened due to your idea so thanks again.

     

    IRule for same routing domain :-

     

    when HTTP_REQUEST {
    log local0. "Incoming request: [HTTP::host]:[HTTP::uri]"
    if { [set match [class match -value -- "[HTTP::host]:[HTTP::uri]" starts_with datagroup_Name]] ne "" } {
    log local0. "Datagroup match: $match"
    ## Get first URI path in client request
    set FIRSTPATH [getfield [HTTP::path] "/" 2]

    ## Split match into host and path
    set matchlist [split $match ":"]
    set MATCHHOST [lindex $matchlist 0]
    set MATCHPATH [lindex $matchlist 1]

    ## Replace FIRSTPATH with MATCHPATH (from full HTTP::uri)
    set UPDATEDPATH [string map [list "/$FIRSTPATH" "$MATCHPATH"] [HTTP::uri]]
    log local0. "Updated host: $MATCHHOST"
    log local0. "Updated path: $UPDATEDPATH"

    HTTP::host $MATCHHOST
    HTTP::uri $UPDATEDPATH

    virtual "/path/virtual_server_name"


    }
    }

     

    For different route domain 

     

    when HTTP_REQUEST {
    log local0. "Incoming request: [HTTP::host]:[HTTP::uri]"
    if { [set match [class match -value -- "[HTTP::host]:[HTTP::uri]" starts_with datagroup_name]] ne "" } {
    log local0. "Datagroup match: $match"
    ## Get first URI path in client request
    set FIRSTPATH [getfield [HTTP::path] "/" 2]

    ## Split match into host and path
    set matchlist [split $match ":"]
    set MATCHHOST [lindex $matchlist 0]
    set MATCHPATH [lindex $matchlist 1]

    ## Replace FIRSTPATH with MATCHPATH (from full HTTP::uri)
    set UPDATEDPATH [string map [list "/$FIRSTPATH" "$MATCHPATH"] [HTTP::uri]]
    log local0. "Updated host: $MATCHHOST"
    log local0. "Updated path: $UPDATEDPATH"

    HTTP::host $MATCHHOST
    HTTP::uri $UPDATEDPATH

    pool pool_name


    }
    }

34 Replies

  • In my opinion, with such a large amount of items you should use an iRule to perform this task, as it will be more beneficial for scalability and easier to manage.

    How much will the URI paths change? Make sure to use [HTTP::uri] , [HTTP::path], [HTTP::query] parameters properly in order to isolate the parts of your old URL that you want to change/keep. For example you could save the full querystring in a variable so that you can add it back to the rewritten url. 

    If you have a large list of paths that all need to be rewritten on a single new path, you can also use data groups for even better scalability and maintenance. 

     

    This might give you a couple ideas

    when HTTP_REQUEST {
      set uri [HTTP::uri]; set path [HTTP::path]; set query [HTTP::query]
      HTTP::header replace Host "pqr.com"
      if { $path starts_with "/xyz" } {
            set newpath [string map -nocase {"/xyz" "/mnp"} $path]
            HTTP::uri "$newpath$query"
        }
    }

     

    • NathPras's avatar
      NathPras
      Icon for Cirrus rankCirrus

      Hi CA_Valli,

       

      How to incoroprate the different paths with the use of datagroup in the iRule provided above. Also when the response would come back doesn't it need to be changed to original URL+URI/path as well?

      Thanks,

      Prasenjit

      • CA_Valli's avatar
        CA_Valli
        Icon for MVP rankMVP

        I've tried to modify my syntax, haven't had the chance to test it in a lab so please be careful with this and don't run it in prod without testing. 

        Code below is supposed to replace anything that matches /aaa/ /bbb/ or /ccc/ at depth1 in your path (this is driven by "starts_with" condition)  with static /mnp/ 

        so /aaa/path/to/index.jsp will change into /mnp/path/to/index.jsp/

        and i believe /bbb/bbb/file.txt should change to /mnp/bbb/file.txt 

         

        Please also integrate knowledge shared by Kevin_Stewart in his comments which is very detailed and much more reliable than my attempt 🙂 

        Lastly, I don't think you will need to change anything in HTTP response. 

         

         

        when HTTP_REQUEST {
          set uri [HTTP::uri]; set path [HTTP::path]; set query [HTTP::query]
          HTTP::header replace Host "pqr.com"
          
          if {[class match $path starts_with datagroup_rewriteURL]} {
        		
        		set dgValue [class match -value $path eq datagroup_rewriteURL] ; #i believe this should return /xyz/ if you've set as a string entry in the data group  
        		log local0. "replacing $dgValue with /mnp/"
        		
                set newpath [string map -nocase { $dgValue "/mnp/"} $path]
                HTTP::uri "$newpath$query"
            }
        }

         

         

        ltm object will be something like 

         

        ltm data-group internal datagroup_rewriteURL {
            records {
                /aaa/ {}
                /bbb/ {}
                /ccc/ {}
            }
            type string
        }

         

         

  • It would depend on how statuc or variable the host/uri strings are. If somewhat static, you could simply do a class lookup to a datagroup using the host/uri combination as a single string match. Something like this:

    when HTTP_REQUEST {
        if { [set match [class match -value -- "[HTTP::host][HTTP::uri]" equals url_datagroup]] ne "" } {
            ... match is true if the match is made
            ... value will be the matched data group value to be used in replacing HTTP:host and HTTP::uri]
        }
    }

    But curious, if abc and pqr are on separate BIG-IPs, AND you need the user's URL to not change in the browser, then one BIG-IP would have to be behind the other BIG-IP. 

    • NathPras's avatar
      NathPras
      Icon for Cirrus rankCirrus

      Hi Kevin,

      Thank you for your kind response. Here actually the existing applications of Akana is hosted on a F5 and customer wants to move from Akana to Mulesoft. Mulesoft being a cluster has virtual IP which is hosted on a different F5. These are actually API calls which presently being done on current F5 hosting the virtual server of Akana. Now customer wants to migrate these API calls from Akana to mulesoft with a phase wise migration where the primary need is till the migration is completly done URL+URI in user end should not change as it would drop the response.

      customer does not have licesne of APM so they are trying to achieve it with LTM.

      I was thinking in the following way which may not be effective at all 

      When HTTP_REQUEST {
      if {[string tolower [HTTP::host]] starts_with "https://abc.com"}{
      if {[HTTP::path] eq "/xyz"}{
      HTTP::header replace Host "https://pqr.com" HTTP::uri "/mnp"}
      }
      }

      When HTTP_RESPONSE {
      if {[string tolower [HTTP::host]] starts_with "https://pqr.com"}{
      if {[HTTP::path] eq "/mnp"}{
      HTTP::header replace host "https://abc.com" HTTP::uri "/xyz"}
      }
      }

      But If the above works at all I am struggling if there are more URI paths which need to be matched and switched with each other like "xyz1" to replace "mnp1" where the URL https://abc.com and https://pqr.com are static/constant.

      From the above solution you have provided, would be kind enough to elaborate how would I be achieve the solution?

      Thanks,

      Prasenjit

       

       

       

       

      • Kevin_Stewart's avatar
        Kevin_Stewart
        Icon for Employee rankEmployee

        Okay, so let's say you have a string datagroup that looks like this:

        ltm data-group internal url_datagroup {
            records {
                www.blah.com:/blah {
                    data www.test.com:/test
                }
                www.foo.com:/foo {
                    data www.bar.com:/bar
                }
            }
            type string
        }

        I've intentionally added a colon ":" between the host and uri to make it easier to separate them later. So then you're matching on "[HTTP::host]:[HTTP::uri]" in the following iRule. If you have a match, you'll split the corresponding value into host and uri (split on the ":" to make a list object). You'll then, on separate lines, update the HTTP Host header and replace the HTTP URI.

        when HTTP_REQUEST {
            if { [set match [class match -value -- "[HTTP::host]:[HTTP::uri]" equals url_datagroup]] ne "" } {
                log local0. "match = $match"
                set matchlist [split $match ":"]
                log local0. "match host = [lindex $matchlist 0]"
                log local0. "match uri = [lindex $matchlist 1]"
                
                HTTP::host [lindex $matchlist 0]
                HTTP::uri [lindex $matchlist 1]
            }
        }

        You can now just add all of these HOST:URI patterns to a single datagroup. Now as I was mentioning earlier, it also depends on how static or dynamic the URLs are. For example, if the URI is statically "/foo", then the above works fine. But if /foo is just the start of the URI (ex. /foo/blah?this=that...), then the above won't completely address the problem. It'll change the HTTP Host, but then you need to do a string map to replace the portion of the URI while (presumably) keeping the rest. Example:

        /foo/blah?this=that

        would be changed to:

        /bar/blah?this=that

        You're doing all of this in the HTTP request. There's no URI, path or URI values in an HTTP response.

        Point is, if you need to keep the user's browser on http://abc.com, you need to keep that traffic on the abc.com VIP. You can change the HTTP Host and URI on the abc VIP and forward the traffic to the pqr VIP, but there's no scenario where the abc VIP would change these values if the user was able to go directly to the pqr VIP directly.