Learn F5 Technologies, Get Answers & Share Community Solutions Join DevCentral

Filter by:
  • Solution
  • Technology
code share

ProxyPass v10/v11

Problem this snippet solves:

iRule to replace the functionality of Apache Webserver ProxyPass and ProxyPassReverse functions. Allows you to do hostname and path name modifications as HTTP traffic passes through the LTM. This optimized version requires TMOS v10 or higher. If you are using APM for authentication on the virtual server, please use the ProxyPass_for_use_with_APM iRule instead.

(Full documentation follows the iRule.)

For use with TMOS v9, see ProxyPass v8.2

Note: In 10.1+ you can use an internal data group to store the ProxyPass configuration. Ignore the comments to the contrary in the instructions as these were for pre-10.1 releases.

Please post questions or fixes for this iRule in the iRules forum to get the fastest response. Thanks Aaron.

Introduction Sometimes it is desirable to have a different look and feel to your website on the outside than you have in the inside. You may want www.company.com/usa/ to internally go to the server usa.company.com. You may want support.company.com to go internally to abc123.company.com/web/support. This can create a few issues - sometimes the web server expects to see a certain hostname (i.e. for name-based virtual hosting) or will use the internal hostname and/or path when sending a redirect to the clients.

These problems can be overcome with the Apache webserver using the ProxyPass module which can translate the URL on the way into the server, and the ProxyPassReverse module which can un-translate header fields such as Location in the case of a redirect.

Now you can accomplish this with an iRule. The ProxyPass iRule translates incoming requests in a flexible manner and untranslates the Location, Content-Location, and/or URI headers in the response back to the client. ProxyPass also rewrites the domain and path in cookies set by the server.

Page Content Modification In addition, this rule will perform basic page modification as needed (this feature is disabled by default but can be enabled in the RULE_INIT event). Using the example from the introduction, if the page content contains a link to http://abc123.company.com/web/support/viewticket.html, the iRule will modify that to be http://support.company.com/viewticket.html. Here are some examples:

< a href=http://www.domain.com/path/file.html> will be modified assuming the incoming request was matched by the ProxyPass iRule and the inside hostname was www.domain.com and the inside path was /path.

< a href="page2.html"> will not need to be modified as this is a URL already relative to the path. Even relative URLs such as < a href="../page2.html"> will work as long as it does not try to go above the top-level directory defined by the ProxyPass rules.

You must have a stream profile defined on any virtual servers the rule is applied to in order to enable the page modification feature.

Virtual Server The first step to using the ProxyPass iRule is to define the rule on your BIG-IP and associate it with one or more Virtual Servers. Note that each virtual server MUST have an HTTP profile defined (doesn't matter which one). I also highly recommend applying a OneConnect profile, especially if you will be choosing pools with ProxyPass. It also must have a stream profile associated with it if you want to uncomment the page modification code. The rule will work on HTTP sites as well as HTTPS sites where the SSL is terminated on the BIG-IP (i.e. a client-side SSL profile is defined).

Data Groups You can apply the ProxyPass rule to any virtual server that you want to do translations on. But just applying the rule will have no effect unless you define the translations you want done. This is done by defining specific Data Groups.

The ProxyPass iRule uses Data Groups which are created and managed by going to Local Traffic / Virtual Servers / iRules on the left menu bar in the BIG-IP GUI. Then choose the "Data Group List" tab at the top of the screen. Here you can create the data groups used by this rule. For 10.0.x, the data groups must be External, type "String", and Read-only. For 10.1 and higher, the data group can be internal or external with name=value pairings.

In order to use this rule on a virtual server you must apply the rule to the virtual server and create a data group named ProxyPassXYZ where "XYZ" needs to be the name of the virtual server. If both of these conditions are not met then the rule will not work for that virtual server.

ProxyPassXYZ Data Groups If your virtual server is named XYZ and has the ProxyPass iRule associated with it, it will look for a data group named ProxyPassXYZ. Assuming that class is found, for each new HTTP request, the rule will find the one row that matches the hostname/path used in the request. For example, the data group may contain 4 entries (each line below is one string in the data group):

"www.usa.company.com/support" := "support.company-internal.com:8080/usa",
"www.usa.company.com/" := "www.company-internal.com:8080/usa",
"www.japan.company.com/" := "www.company-internal.com:8080/japan",
"/" := "www.company-internal.com:8080/others",

A request need not match any entries - if no entries match then the iRule will have no effect. But each request will only match at most one entry and that will be the entry with the most specific left-hand-side. Entries with hostnames specified on the left-hand-side will be matched before entries without hostnames. If multiple entries match then the entry with the longest path name on the left-hand-side will be used. The example above lists entries from most-specific to least-specific, just as the rule will process them, but in your actual data group the order of the entries does not matter.

In the example above, requests to http://www.usa.company.com/support will match the first entry and have the host header changed to "support.company-internal.com:8080" and the URI will be rewritten so that the string /support at the beginning of the URI will be changed to /usa.

Furthermore, requests to http://www.usa.company.com/ will match the second entry as long as the URI does not begin with "/support" in which case it would match the first entry. In that case the Host header will be changed to "www.company-internal.com:8080" and whatever URI the client sends in will be prepended with "/usa". Likewise all requests to http://www.japan.company.com/ will match the third entry and have the Host header changed to "www.company-internal.com:8080" and the URIs would be prepended with "/japan".

Finally, all other requests that hit this example virtual server would match the least-specific rule which is simply "/" -- all URIs begin with "/" and thus all requests will match the fourth entry if they did not match any others. Remember that a catch-all entry is not required, but in this example we want to prepend the URI of all other requests with "/others".

Note that the ProxyPass iRule does not actually alter the destination of the requests by default. In the examples above all of the requests would go to the default pool regardless of the entries they match. The hostnames and ports specified in the right-hand entry is only used to modify the Host header. To alter the destination pool see the next section.

Dynamic Pool Selection You may also specify an alternate pool as a second item in the right-hand value of the entry. This is optional any items in the list without a pool name will just use the default pool associated with the virtual server. For example:

"/support" := "support.company.com/ SupportPool",
"/downloads" := "downloads.company.com/ DownloadPool",
If the pool name is not valid the user will get an error and you should see an error message in /var/log/ltm.

Dynamic SNAT You can optionally define a ProxyPassSNATs data group. This will allow you to use different SNAT IP based on which pool you send traffic to. The ProxyPassSNATs data group is shared by all virtual servers but will only have an effect if the selected pool is listed in the data group. The format of this data group (internal string type) is:

Pool1 W.X.Y.Z
Pool2 automap

Dynamic ServerSSL Profiles You can optionally define a ProxyPassSSLProfiles data group and apply a generic serverssl profile to the virtual server. This will allow you to use different serverssl profiles based on which pool you send traffic to. The ProxyPassSSLProfiles data group is shared by all virtual servers but will only have an effect if the selected pool is listed in the data group and a generic serverssl is applied to the virtual server. The format of this data group (internal string type) is:

Pool1 ServerSSLProfile1
Pool2 ServerSSLProfile2

Regular Expressions New in v10: you may also use regular expressions and backreferences when building your rule set.

"/" := "=www.company.com/(.*?)/=$1.company.com/=",

As you see, instead of a regular server-side entry, we have "=regex=replace=". Basically, in order for the regex to be run, the left-hand side must match the client host/path (just "/" in this case, which will always match unless something more specific matches). Once this happens, the client host and URI are combined into a form similar to www.domain.com/path and the regular expression is run a against it. If the regular expression does not match, ProxyPass does not alter the request. If it does match, the clientside path becomes the match string and the serverside path becomes the replace string. Within this replace string you can use $1 through $9 as back-references to grouped items in the original regular expression. So, the example above, entry is equivalent to all of these entries:

"www.company.com/sales" := "sales.company.com/",
"www.company.com/support" := "support.company.com/",
"www.company.com/employment" := "employment.company.com/",

Debugging You can debug your ProxyPass rules by setting the static::ProxyPassDebug variable at the top of the rule to 1 (or 2 for more verbose debugging). Once you do this you can SSH to the BIG-IP and run the command "tail -f /var/log/ltm" to see what ProxyPass is doing to your requests.

Tested on Version:
10.0
Comments on this Snippet
Comment made 04-Feb-2016 by Ross Johnson 1
Hi, I've just started using this iRule to move a number of Apache mod_proxy reverse proxies to our F5 HA pair. The first site moved was a simple HTTP clientside to HTTP serverside with no payload rewriting and it worked perfectly as documented. Then I started to see things that could be improved and a few things that didn't work and could be fixed. 1. The static::ProxyPassDebug and static::RewriteResponsePayload are inconvenient when the iRule is used by multiple virtual servers, so I've localised these for each virtual server in a new Data Group "ProxyPassOptionsVIRTUAL". 2. We offload SSL to some of our proxies so I found the request header and referer rewriting wasn't rewriting the protocol, resulting in the browser getting connection errors at least, I think, unless the virtual server accepts both HTTP and HTTPS in which case the iRule appears to trap the unchanged protocol in the new request and rewrites and redirects it back to itself. I have fixed this by inferring the serverside protocol for each pool from the ProxyPassSSLProfile information if it exists. There are a couple of assumptions/requirements supporting this method: a. If any pool, default or alternative, requires SSL it must have a server profile entry in this data group. b. The clientside protocol is detected via the CLIENTSSL_HANDSHAKE event. 3. Payload rewriting doesn't rewrite the protocol. I have fixed this also following from (2). 4. I think payload rewriting is incomplete if it only rewrites matching the current request URL components. That is, currently for a request rewrite e.g. "http://www.company.com/some/path" to "http://backend.internal/other/path", the response payload will reverse only this URL. If the payload includes other absolute URLs e.g. "http://backend.internal/another/path", this will be missed. So the iRule needs to be modified to rewrite just the "protocol://host_serverside" part for all URLs starting with that string, but it also needs to rewrite any path components that start with any path component in "ProxyPassVIRTUAL" that match the serverside host. For example, given the following "ProxyPathVIRTUAL": "www.company.com/some/path" := "backend.internal/other/path" "www.company.com/some/other/path" := "backend.internal/yet/another/path" Given a request "http://www.company.com/some/path", all of the following rewrites need to occur if found in the response payload: "http://backend.internal/other/path" -> "http://www.company.com/some/path" "http://backend.internal/yet/another/path" -> "http://www.company.com/some/other/path" "http://backend.internal/unchanged/path" -> "http://www.company.com/unchanged/path" "/other/path" -> "/some/path" "/yet/another/path" -> "/some/other/path" I haven't implemented (4) yet but I'm happy to provide the modified iRule code when I have if someone can tell me how I can upload it. I don't have any dev points to upload it directly and I tried posting mods (1), (2) and (3) to the iRules forum but exceeded the word count and was rejected. Regards.
2
Comment made 01-Mar-2016 by Rene Geile 12
Today I experienced a bug related to cookies, using v10.9 of the iRule. This bug was mentioned in https://devcentral.f5.com/questions/cookie-bug-in-proxypass-v10-irule- Regarding v10.9: v10.9 Line 429 is wrong: append elementvalue "." v10.9 Line 429 corrected: set elementvalue ".$elementvalue"
0
Comment made 01-Mar-2016 by Rene Geile 12
@Ross, your remark (4) is right, the iRule seems to rewrite only URLs in responses if the same URL appeared in the request. This means the purpose of the iRule is to provide rewriting for independent virtual hosts/paths which are not referrring each other, if they do they would need to use external URLs not internal references. I would suggest using APM portal access mode instead of trying to extend this irule code. Portal access also provides additional features for URLs in CSS and JS code.
0
Comment made 06-Apr-2016 by Hajar 4
Hello, Today I tried your solution. 1- I created a VS called test. 2- I created Data group called ProxyPasstest. for now only entry of string= test.com value= cloud-test.com 3- I cerated irule called ProxyPass and copied your code without any change, then applied the irule to the VS, test. I was hoping this will point users requesting test.com to cloud-test site but will keep the URL as test.com. I guess I am doing something wrong. It doesn't get to cloud-test at all. I can go to cloud-test.com if I have irule redirect, but this is not we want since it changes the URL to cloud-test. Any idea what I have done incorrectly?
0
Comment made 11-Apr-2016 by Jerry Lees
When using this irule, as it currently is displayed (until corrected), it yields the following error when saving the irule or loading the configuration: "@" unexpected argument This is an issue with the first line in the iRule that contains a single "@" symbol. Remove this and it should load correctly.
0
Comment made 13-Apr-2016 by Hajar 4
Thanks Jerry, I removed @ sign, no problem on loading the iRule. I created data group as instructed, I have added entries as I needed to the data group, but for some reason, it is not working. No matter if I apply the irule or not it always goes to the default pool and main page. My data group has the entries a.com/x:=b.com/x and a.com:=c.com and a.com/y:=b.com/y. it always goes to default pool c.com no matter what I type. I have logging enabled. When I type a.com/x all I see stops at ending: Rule ProxyPass <HTTP_RESPONSE>: VS=test, Host=a.com, URI=/x: Checking Content-Location=, $protocol= and the next line is Rule ProxyPass <HTTP_RESPONSE>: VS=test, Host=a.com, URI=/x: Checking Location=, $protocol= and stops there and I only get the default c.com
0
Comment made 18-Apr-2016 by John Wagner 0
When doing "clientside" := "serverside poolname" (I've got 5 pools that create the single site image), how do I handle SSL vs non-SSL. The pools are specified as either http or https, so I can't give one pool name for both http and https. Is this a problem with definition of the pools or do I have a situation not handled by the ProxyPass rule?
0
Comment made 19-Apr-2016 by Rene Geile 12
@John, server SSL profile selection is independent from the rewrite and pool selection. You can create a datagroup named "ProxyPassSSLProfiles" which contains a list of pools and the SSL profile which should be used to access the pool. This datagroup is a single common list valid for all virtual server using ProxyPass iRule. There should be one entry for each HTTPS pool in the list. If your virtual server is using mixed backends (https and http) you need to attach a server ssl profile to the VS because without a profile there is nothing the ProxyPass iRule can switch because server ssl processing is "not loaded/ disabled." If you create a server ssl profile with "Mode = Disabled" and attach it to the virtual server you have a default of "HTTP to the pool" and with entries in the ProxyPassSSLProfiles datagroup you can select different encrypting profiles for HTTPs pools.
0
Comment made 02-Jun-2016 by AlexLP 3
I upgraded to 12.1 from 12.06. I removed the ProxyPass v10/v11 rule from iApp and then deleted the rule. I backed up the device using the archive. I upgraded to 12.1. I restored the configuration I installed the ProxyPass Rule v10/v11. I had two issues with lines 250 and 486. To resolve the issue, I created two blank data groups with strings, ProxyPassSNATs, and ProxyPassSSLProfiles. I then added the ProxyPass rule back into the iApp and it seems to be working. Thanks for providing this code! Thanks, Alex
0
Comment made 10-Jun-2016 by Thomas 0
It might be worth to mention that when you use the ProxyPassSSLProfiles data group in combination with partitions you must specify the full "path", ie /partition-name/pool ssl-server-profile
1
Comment made 22-Jun-2016 by AlexLP 3
I did not have time to make this change in the most efficient manner. However, I made a quick change to this code which causes the serverside host to be updated, but makes the server side and client side path the original URI. set path_serverside $orig_uri set path_clientside $orig_uri if {$host_serverside eq ""} { set host_serverside $host_clientside } # At this point $host_serverside is the server hostname, and $path_serverside # is the server-side path as specified in the data group # In order for directory redirects to work properly we have to be careful with slashes # if {$path_clientside equals "/"} { # # Make sure serverside path ends with / if clientside path is "/" # if {!($path_serverside ends_with "/")} { # append path_serverside "/" # } # } else { # # Otherwise, neither can end in a / (unless serverside path is just "/") # if {!($path_serverside equals "/")} { # if {$path_serverside ends_with "/"} { # set path_serverside [string trimright $path_serverside "/"] # } # if {$path_clientside ends_with "/"} { # set path_clientside [string trimright $path_clientside "/"] # } # } # }
0
Comment made 15-Jul-2016 by James_359 0

Hello, we are running version 12. Is this code still compatible? Receiving the following error when applying the proxypass to VIP.

01070151:3: Rule [/Common/ProxyPass] error: Unable to find value_list (ProxyPassSNATs) referenced at line 250: [findclass $newpool ProxyPassSNATs " "]

Many thanks

0
Comment made 18-Jul-2016 by GavinW 156

James_359 - I hit the same issue when upgrading a unit the other day, and logged it with F5 support.

The suggested fix is to create 2 blank DG's, as advised by AlexLP 3 comments previous... They also advised that 'findclass' is now deprecated in favour of 'class', as doc'd here: https://devcentral.f5.com/wiki/iRules.findclass.ashx

So looks like this iRule could do with a couple of tweaks to fully support 12.1...

HTH
Gav

0
Comment made 22-Jul-2016 by James_359 0

Thanks for your time GavinW. I have created the blank DG's and changed 'findclass' to just 'class' but still getting the following error:

01070151:3: Rule [/Common/ProxyPass] error: Unable to find value_list (ProxyPassSSLProfiles) referenced at line 486: [class $pool ProxyPassSSLProfiles " "]

Do you mind copying in your modified code?

Thanks again. James

0
Comment made 07-Sep-2016 by John Beckmann

Just replace the findclass with the following:-

Find:

set snat [findclass $newpool ProxyPassSNATs " "]

Replace:

set snat [class match -value $newpool equals /Common/ProxyPassSNATs]

Find:

set profilename [findclass $pool ProxyPassSSLProfiles " "]

Replace:

set profilename [class match -value $pool equals /Common/ProxyPassSSLProfiles]

5
Comment made 27-Sep-2016 by James_359 0

Great thanks! After creating the data group with the required string this works.

Here is the full code for proxypass working on version 12:

Code
`</pre>

# ProxyPass iRule, Version 10.9, JC Mod

# Nov 26 2012

# THIS VERSION REQUIRES TMOS v10 or higher. Use ProxyPass v8.2 for TMOS 9.x.

# This version does not work with APM-enabled virtual servers, please

# download ProxyPass 10.2APM for this use case.

# Created by Kirk Bauer

# [https://devcentral.f5.com/wiki/default.aspx/iRules/ProxyPassV10.html](https://devcentral.f5.com/wiki/default.aspx/iRules/ProxyPassV10.html)

# (please see end of iRule for additional credits)

# Purpose:

# iRule to replace the functionality of Apache Webserver ProxyPass and

# ProxyPassReverse functions.  It allows you to perform host name and path name

# modifications as HTTP traffic passes through the LTM.  In other words, you

# can have different hostnames and directory names on the client side as you

# do on the server side and ProxyPass handles the necessary translations.

# NOTE: You should not need to modify this iRule in any way except the settings

# in the RULE_INIT event.  Just apply the iRule to the virtual server and

# define the appropriate Data Group and you are done.  If you do make any

# changes to this iRule, please send your changes and reasons to me so that

# I may understand how ProxyPass is being used and possibly incorporate your

# changes into the core release.

# Configuration Requirements

# 1) The ProxyPass iRule needs to be applied to an HTTP virtual server or

# an HTTPS virtual server with a clientssl profile applied to it.

# 2) A data group (LTM -&gt; iRules -&gt; Data Groups tab) must be defined with

# the name "ProxyPassVIRTUAL" where VIRTUAL is the name of the virtual server

# (case-sensitive!).  See below for the format of this data group (class).

# For 10.0.x, you must use an EXTERNAL data group.

# 3) You must define a default pool on the virtual server unless you specify

# a pool in every entry in the data group.

# 4) If you are using ProxyPass to select alternate pools, you must define

# a OneConnect profile in most cases!

# 5) ProxyPass does not rewrite links embedded within pages by default, just

# headers.  If you want to change this, edit the $static::RewriteResponsePayload variable in RULE_INIT

# and apply the default stream profile to the virtual server.

# Data Group Information

# For 10.0.x, you must define an external data group (type=String, read-only) which loads

# from a file on your BIG-IP.  For 10.1 and higher you can use an internal string data group with name=value pairings.

# The format of the file is as follows:

# "clientside" := "serverside",

# or

# "clientside" := "serverside poolname",

# The clientside and serverside fields must contain a URI (at least a "/") and

# may also contain a hostname.  Here are some examples:

# "/clientdir" := "/serverdir",

# "[www.host.com/clientdir"](//www.host.com/clientdir) := "internal.company.com/serverdir",

# "[www.host.com/"](//www.host.com/) := "internal.company.com/serverdir/",

# Notes:

# 1) You can optionally define a ProxyPassSNATs data group to SNAT based

# on the pool selected.

# 2) You can optionally define a ProxyPassSSLProfiles data group to select

# a serverssl profile based on the pool selected.

# 3) You can also use regular expressions which is documented on DevCentral.

when RULE_INIT {
    # Enable to debug ProxyPass translations via log messages in /var/log/ltm
    # (2 = verbose, 1 = essential, 0 = none)
    set static::ProxyPassDebug 2

<pre>`# Enable to rewrite page content (try a setting of 1 first)
# (2 = attempt to rewrite host/path and just /path, 1 = attempt to rewrite host/path)
set static::RewriteResponsePayload 0
`</pre>

}

when CLIENT_ACCEPTED {
    # Get the default pool name.  This is used later to explicitly select 
    # the default pool for requests which don't have a pool specified in 
    # the class.
    set default_pool [LB::server pool]

<pre>`# The name of the Data Group (aka class) we are going to use. 
# Parse just the virtual server name by stripping off the folders (if present)
set clname "ProxyPass[URI::basename [virtual name]]"

if { $static::ProxyPassDebug > 1 } {
    log local0. "[virtual name]: [IP::client_addr]:[TCP::client_port] -> [IP::local_addr]:[TCP::local_port]"
}
`</pre>

}

when HTTP_REQUEST {
    # "bypass" tracks whether or not we made any changes inbound so we
    # can skip changes on the outbound traffic for greater efficiency.
    set bypass 1

<pre>`# Initialize other local variables used in this rule
set orig_uri "[HTTP::uri]"
set orig_host "[HTTP::host]"
set log_prefix "VS=[virtual name], Host=$orig_host, URI=$orig_uri"
set clientside ""
set serverside ""
set newpool ""
set ppass ""

if {! [class exists $clname]} {
    log local0. "$log_prefix: Data group $clname not found, exiting."
    pool $default_pool
    return
} else {
    set ppass [class match -element "$orig_host$orig_uri" starts_with $clname]
    if {$ppass eq ""} {
        # Did not find with hostname, look for just path
        set ppass [class match -element "$orig_uri" starts_with $clname]
    }
    if {$ppass eq ""} {
        # No entries found
        if { $static::ProxyPassDebug > 0 } {
            log local0. "$log_prefix: No rule found, using default pool $default_pool and exiting"  
        }
        pool $default_pool
        return
    }
}

# Store each entry in the data group line into a local variable
set clientside [getfield $ppass " " 1]
set serverside [string trimleft [getfield $ppass " " 2 ] "{" ]
set newpool [string trimright [getfield $ppass " " 3 ] "}" ]

# If serverside is in the form =match=replace=, apply regex
if {$serverside starts_with "="} {
    set regex [getfield $serverside "=" 2]
    set rewrite [getfield $serverside "=" 3]
    if {[regexp -nocase $regex "$orig_host$orig_uri" 0 1 2 3 4 5 6 7 8 9]}{
        # The clientside becomes the matched string and the serverside the substitution
        set clientside $0
        set serverside [eval set X $rewrite]
    } else {
        pool $default_pool
        return
    }
}

if {$clientside starts_with "/"} {
    # No virtual hostname specified, so use the Host header instead
    set host_clientside $orig_host
    set path_clientside $clientside
} else {
    # Virtual host specified in entry, split the host and path
    set host_clientside [getfield $clientside "/" 1]
    set path_clientside [substr $clientside [string length $host_clientside]]
}
# At this point $host_clientside is the client hostname, and $path_clientside
# is the client-side path as specified in the data group

set host_serverside [getfield $serverside "/" 1]
set path_serverside [substr $serverside [string length $host_serverside]]
if {$host_serverside eq ""} {
    set host_serverside $host_clientside
}
# At this point $host_serverside is the server hostname, and $path_serverside
# is the server-side path as specified in the data group

# In order for directory redirects to work properly we have to be careful with slashes
if {$path_clientside equals "/"} {
    # Make sure serverside path ends with / if clientside path is "/"
    if {!($path_serverside ends_with "/")} {
        append path_serverside "/"
    }
} else {
    # Otherwise, neither can end in a / (unless serverside path is just "/")
    if {!($path_serverside equals "/")} {
        if {$path_serverside ends_with "/"} {
            set path_serverside [string trimright $path_serverside "/"]
        }
        if {$path_clientside ends_with "/"} {
            set path_clientside [string trimright $path_clientside "/"]
        }
    }
}

if { $static::ProxyPassDebug } {
    log local0. "$log_prefix: Found Rule, Client Host=$host_clientside, Client Path=$path_clientside, Server Host=$host_serverside, Server Path=$path_serverside"  
}

# If you go to http://www.domain.com/dir, and /dir is a directory, the web
# server will redirect you to http://www.domain.com/dir/.  The problem is, with ProxyPass, if the client-side
# path is http://www.domain.com/dir, but the server-side path is http://www.domain.com/, the server will NOT
# redirect the client (it isn't going to redirect you to http://www.domain.com//!).  Here is the problem with
# that.  If there is an image referenced on the page, say logo.jpg, the client doesn't realize /dir is a directory
# and as such it will try to load http://www.domain.com/logo.jpg and not http://www.domain.com/dir/logo.jpg.  So
# ProxyPass has to handle the redirect in this case.  This only really matters if the server-side path is "/",
# but since we have the code here we might as well offload all of the redirects that we can (that is whenever
# the client path is exactly the client path specified in the data group but not "/").
if {$orig_uri eq $path_clientside} {
    if {([string index $path_clientside end] ne "/") and not ($path_clientside contains ".") } {
        set is_https 0
        if {[PROFILE::exists clientssl] == 1} {
            set is_https 1
        }
        # Assumption here is that the browser is hitting http://host/path which is a virtual path and we need to do the redirect for them
        if {$is_https == 1} {
            HTTP::redirect "https://$orig_host$orig_uri/"
            if { $static::ProxyPassDebug } {
                log local0. "$log_prefix: Redirecting to https://$orig_host$orig_uri/"
            }
        } else {
            HTTP::redirect "http://$orig_host$orig_uri/"
            if { $static::ProxyPassDebug } {
                log local0. "$log_prefix: Redirecting to http://$orig_host$orig_uri/"
            }
        }
        return
    }
}

if {$host_clientside eq $orig_host} {
    if {$orig_uri starts_with $path_clientside} {
        set bypass 0
        # Take care of pool selection
        if {$newpool eq ""} {
            pool $default_pool
            if { $static::ProxyPassDebug > 1 } {
                log local0. "$log_prefix: Using default pool $default_pool"
            }
            set newpool $default_pool
        } else {
            pool $newpool
            if { $static::ProxyPassDebug > 0 } {
                log local0. "$log_prefix: Using parsed pool $newpool (make sure you have OneConnect enabled)"
            }
        }
    }
}

# If we did not match anything, skip the rest of this event
if {$bypass} {
    return
}

# The following code will look up SNAT addresses from 
# the data group "ProxyPassSNATs" and apply them. 
# 
# The format of the entries in this list is as follows: 
# 
# <pool name> <SNAT IP|automap> 
# 
# All entries are separated by spaces, and both items 
# are required. 
set class_exists_cmd "class exists ProxyPassSNATs"
if {! [eval $class_exists_cmd]} {
    return
}

set snat [class match -value $newpool equals /Common/ProxyPassSNATs]

if {$snat eq ""} { 
    # No snat found, skip rest of this event
    return 
}

if { $static::ProxyPassDebug > 0 } { 
    log local0. "$log_prefix: SNAT address $snat assigned for pool $newpool"  
} 

snat $snat
`</pre>

}

when HTTP_REQUEST_SEND {
    # If we didn't match anything, skip the rest of this event
    if {$bypass} {
        return
    }

<pre>`# The following code does the actual rewrite on its way TO 
# the backend server. It replaces the URI with the newly 
# constructed one and masks the "Host" header with the FQDN 
# the backend pool server wants to see. 
# 
# If a new pool or custom SNAT are to be applied, these are 
# done here as well. If a SNAT is used, an X-Forwarded-For 
# header is attached to send the original requesting IP 
# through to the server. 

if {$host_clientside eq $orig_host} {
    if {$orig_uri starts_with $path_clientside} {
        if { $static::ProxyPassDebug > 1 } {
            log local0. "$log_prefix: New Host=$host_serverside, New Path=$path_serverside[substr $orig_uri [string length $path_clientside]]"
        }
        clientside { 
            # Rewrite the URI
            HTTP::uri $path_serverside[substr $orig_uri [string length $path_clientside]]
            # Rewrite the Host header
            HTTP::header replace Host $host_serverside
            # Now alter the Referer header if necessary
            if { [HTTP::header exists "Referer"] } {
                 set protocol [URI::protocol [HTTP::header Referer]]
                 if {$protocol ne ""} {
                      set client_path [findstr [HTTP::header "Referer"] $host_clientside [string length $host_clientside]]
                      if {$client_path starts_with $path_clientside} {
                            if { $static::ProxyPassDebug > 1 } {
                                 log local0. "$log_prefix: Changing Referer header: [HTTP::header Referer] to $protocol://$host_serverside$path_serverside[substr $client_path [string length $path_clientside]]"
                            }
                            HTTP::header replace "Referer" "$protocol://$host_serverside$path_serverside[substr $client_path [string length $path_clientside]]"
                      }
                 }
            }
      }
    }
}

# If we're rewriting the response content, prevent the server from using
#   compression in its response by removing the Accept-Encoding header
#   from the request.  LTM does not decompress response content before
#   applying the stream profile.  This header is only removed if we're
#   rewriting response content.
clientside {
    if { $static::RewriteResponsePayload } {
        if { [HTTP::header exists "Accept-Encoding"] } {
            HTTP::header remove "Accept-Encoding"
            if { $static::ProxyPassDebug > 1} {
                log local0. "$log_prefix: Removed Accept-Encoding header"
            }
        }
    }
    HTTP::header insert "X-Forwarded-For" "[IP::remote_addr]"
}   
`</pre>

}

when HTTP_RESPONSE {
    if { $static::ProxyPassDebug &gt; 1 } {
        log local0. "$log_prefix: [HTTP::status] response from [LB::server]"
    }

<pre>`if {$bypass} {
    # No modification is necessary if we didn't change anything inbound so disable the stream filter if it was enabled

    # Check if we're rewriting the response
    if {$static::RewriteResponsePayload} {
        if { $static::ProxyPassDebug > 1 } {
            log local0. "$log_prefix: Rewriting response content enabled, but disabled on this response."
        }

        # Need to explicity disable the stream filter if it's not needed for this response
        # Hide the command from the iRule parser so it won't generate a validation error
        #   when not using a stream profile
        set stream_disable_cmd "STREAM::disable"

        # Execute the STREAM::disable command.  Use catch to handle any errors. Save the result to $result
        if { [catch {eval $stream_disable_cmd} result] } {
            # There was an error trying to disable the stream profile.
            log local0. "$log_prefix: Error disabling stream filter ($result). If you enable static::RewriteResponsePayload, then you should add a stream profile to the VIP.  Else, set static::RewriteResponsePayload to 0 in this iRule."
        }
    }

    # Exit from this event.
    return
}

# Check if we're rewriting the response
if {$static::RewriteResponsePayload} {
    # Configure and enable the stream filter to rewrite the response payload
    # Hide the command from the iRule parser so it won't generate a validation error
    #   when not using a stream profile
    if {$static::RewriteResponsePayload > 1} {
        set stream_expression_cmd "STREAM::expression \"@$host_serverside$path_serverside@$host_clientside$path_clientside@ @$path_serverside@$path_clientside@\""
    } else {
        set stream_expression_cmd "STREAM::expression \"@$host_serverside$path_serverside@$host_clientside$path_clientside@\""
    }
    set stream_enable_cmd "STREAM::enable"
    if { $static::ProxyPassDebug > 1 } {
        log local0. "$log_prefix: \$stream_expression_cmd: $stream_expression_cmd, \$stream_enable_cmd: $stream_enable_cmd"
    }

    # Execute the STREAM::expression command. Use catch to handle any errors. Save the result to $result
    if { [catch {eval $stream_expression_cmd} result] } {
        # There was an error trying to set the stream expression.
        log local0. "$log_prefix: Error setting stream expression ($result). If you enable static::RewriteResponsePayload, then you should add a stream profile to the VIP.  Else, set static::RewriteResponsePayload to 0 in this iRule."
    } else {
        # No error setting the stream expression, so try to enable the stream filter
        # Execute the STREAM::enable command.  Use catch to handle any errors. Save the result to $result
        if { [catch {eval $stream_enable_cmd} result] } {
            # There was an error trying to enable the stream filter.
            log local0. "$log_prefix: error enabling stream filter ($result)"
        } else {
            if { $static::ProxyPassDebug > 1 } {
                log local0. "$log_prefix: Successfully configured and enabled stream filter"
            }
        }
    }
}

# Fix Location, Content-Location, and URI headers
foreach header {"Location" "Content-Location" "URI"} {
    set protocol [URI::protocol [HTTP::header $header]]
    if { $static::ProxyPassDebug > 1 } {
        log local0. "$log_prefix: Checking $header=[HTTP::header $header], \$protocol=$protocol"
    }
    if {$protocol ne ""} {
        set server_path [findstr [HTTP::header $header] $host_serverside [string length $host_serverside]]
        if {$server_path starts_with $path_serverside} {
            if { $static::ProxyPassDebug } {
                log local0. "$log_prefix: Changing response header $header: [HTTP::header $header] with $protocol://$host_clientside$path_clientside[substr $server_path [string length $path_serverside]]"
            }
            HTTP::header replace $header $protocol://$host_clientside$path_clientside[substr $server_path [string length $path_serverside]]
        }
    }
}

# Rewrite any domains/paths in Set-Cookie headers
if {[HTTP::header exists "Set-Cookie"]}{
    array unset cookielist
    foreach cookievalue [HTTP::header values "Set-Cookie"] {
        set cookiename [getfield $cookievalue "=" 1]
        set namevalue ""
        set newcookievalue ""
        foreach element [split $cookievalue ";"] {
            set element [string trim $element]
            if {$namevalue equals ""} {
                set namevalue $element  
            } else {
                if {$element contains "="} {
                    set elementname [getfield $element "=" 1]
                    set elementvalue [getfield $element "=" 2]
                    if {[string tolower $elementname] eq "domain"} {
                        set elementvalue [string trimright $elementvalue "."]
                        if {$host_serverside ends_with $elementvalue} {
                            if {$static::ProxyPassDebug > 1} {
                                log local0. "$log_prefix: Modifying cookie $cookiename domain from $elementvalue to $host_clientside"
                            }
                            set elementvalue $host_clientside
                        }
                        append elementvalue "."
                    }
                    if {[string tolower $elementname] eq "path"} {
                        if {$elementvalue starts_with $path_serverside} {
                            if {$static::ProxyPassDebug > 1} {
                                log local0. "$log_prefix: Modifying cookie $cookiename path from $elementvalue to $path_clientside[substr $elementvalue [string length $path_serverside]]"
                            }
                            set elementvalue $path_clientside[substr $elementvalue [string length $path_serverside]]
                        }
                    }
                    append newcookievalue "; $elementname=$elementvalue"
                } else {
                    append newcookievalue "; $element"
                }
            }
        } 
        set cookielist($cookiename) "$namevalue$newcookievalue"
    }
    HTTP::header remove "Set-Cookie"
    foreach cookiename [array names cookielist] {
        HTTP::header insert "Set-Cookie" $cookielist($cookiename)
        if {$static::ProxyPassDebug > 1} {
            log local0. "$log_prefix: Inserting cookie: $cookielist($cookiename)"
        }
    }
}
`</pre>

}

# Only uncomment this event if you need extra debugging for content rewriting.

# This event can only be uncommented if the iRule is used with a stream profile.

# when STREAM_MATCHED {

# if { $static::ProxyPassDebug } {

# log local0. "$log_prefix: Rewriting match: [STREAM::match]"

# }

# }

# The following code will look up SSL profile rules from

# the Data Group ProxyPassSSLProfiles" and apply

# them.

# 

# The format of the entries in this list is as follows:

# 

# <pool name> <serverssl profile name></serverssl></pool>

# 

# All entries are separated by spaces, and both items

# are required.  The virtual server also will need to

# have any serverssl profile applied to it for this to work.

when SERVER_CONNECTED { 
    if {$bypass} {
        return
    }

<pre>`set class_exists_cmd "class exists ProxyPassSSLProfiles"
if {! [eval $class_exists_cmd]} {
    return
}

set pool [LB::server pool]  
set profilename [class match -value $pool equals /Common/ProxyPassSSLProfiles]

if {$profilename eq ""} { 
    if { [PROFILE::exists serverssl] == 1} {
        # Hide this command from the iRule parser (in case no serverssl profile is applied) 
        set disable "SSL::disable serverside" 
        catch {eval $disable}
    }
    return 
}

if { $static::ProxyPassDebug > 0 } { 
    log local0. "$log_prefix: ServerSSL profile $profilename assigned for pool $pool"  
} 
if { [PROFILE::exists serverssl] == 1} {
    # Hide these commands from the iRule parser (in case no serverssl profile is applied)
    set profile "SSL::profile $profilename"
    catch {eval $profile}
    set enable "SSL::enable serverside" 
    catch {eval $enable}
} else {
    log local0. "$log_prefix: ServerSSL profile must be defined on virtual server to enable server-side encryption!"  
}

}

2
Comment made 17-Oct-2016 by cathy_123 76

Just a note I repeat Thomas comment for when you input datagroup for ProxyPassSSLProxile always indicate the path so revising above instruction (On my end we only use 1 partition - Common)

this will be the sample output from your datagroup

/Common/Pool1 := ServerSSLProfile1
/Common/Pool2 := ServerSSLProfile2

Also change the line

set profilename [findclass $pool ProxyPassSSLProfiles " "]

to

set profilename [class match -value -- $pool equals ProxyPassSSLProfiles]

(saw this line change from Deon credits to him :) )

this works on v11.6.1

Cheers!

0
Comment made 09-Nov-2016 by S Blakely

The following lines cause a warning during config verification following an upgrade

# Store each entry in the data group line into a local variable
set clientside [getfield $ppass " " 1]
set serverside [string trimleft [getfield $ppass " " 2 ] "{" ]
set newpool [string trimright [getfield $ppass " " 3 ] "}" ]

The curly brace literals need to be escaped to stop the verification from flagging the warnings:

# Store each entry in the data group line into a local variable
set clientside [getfield $ppass " " 1]
set serverside [string trimleft [getfield $ppass " " 2 ] "\{" ]
set newpool [string trimright [getfield $ppass " " 3 ] "\}" ]
1
Comment made 12-Apr-2017 by Denny Payne

You should no longer need to use the proxypass iRule in v12. The same functionality can be natively accomplished with a Rewrite profile that does URI translation.

https://support.f5.com/kb/en-us/products/big-ip_ltm/manuals/product/ltm-implementations-11-4-0/21.html

0
Comment made 08-May-2017 by Jason Adams

@James_359 -- This is your version of the v12 Compatible ProxyPass iRule, however the formatting on yours was not copy/past-able.

Hope this helps.

# ProxyPass iRule, Version 10.9, JC Mod
# Nov 26 2012
# THIS VERSION REQUIRES TMOS v10 or higher. Use ProxyPass v8.2 for TMOS 9.x.
# This version does not work with APM-enabled virtual servers, please
# download ProxyPass 10.2APM for this use case.
# Created by Kirk Bauer
# [https://devcentral.f5.com/wiki/default.aspx/iRules/ProxyPassV10.html](https://devcentral.f5.com/wiki/default.aspx/iRules/ProxyPassV10.html)
# (please see end of iRule for additional credits)
# Purpose:
# iRule to replace the functionality of Apache Webserver ProxyPass and
# ProxyPassReverse functions.  It allows you to perform host name and path name
# modifications as HTTP traffic passes through the LTM.  In other words, you
# can have different hostnames and directory names on the client side as you
# do on the server side and ProxyPass handles the necessary translations.
# NOTE: You should not need to modify this iRule in any way except the settings
# in the RULE_INIT event.  Just apply the iRule to the virtual server and
# define the appropriate Data Group and you are done.  If you do make any
# changes to this iRule, please send your changes and reasons to me so that
# I may understand how ProxyPass is being used and possibly incorporate your
# changes into the core release.
# Configuration Requirements
# 1) The ProxyPass iRule needs to be applied to an HTTP virtual server or
# an HTTPS virtual server with a clientssl profile applied to it.
# 2) A data group (LTM -> iRules -> Data Groups tab) must be defined with
# the name "ProxyPassVIRTUAL" where VIRTUAL is the name of the virtual server
# (case-sensitive!).  See below for the format of this data group (class).
# For 10.0.x, you must use an EXTERNAL data group.
# 3) You must define a default pool on the virtual server unless you specify
# a pool in every entry in the data group.
# 4) If you are using ProxyPass to select alternate pools, you must define
# a OneConnect profile in most cases!
# 5) ProxyPass does not rewrite links embedded within pages by default, just
# headers.  If you want to change this, edit the $static::RewriteResponsePayload variable in RULE_INIT
# and apply the default stream profile to the virtual server.
# Data Group Information
# For 10.0.x, you must define an external data group (type=String, read-only) which loads
# from a file on your BIG-IP.  For 10.1 and higher you can use an internal string data group with name=value pairings.
# The format of the file is as follows:
# "clientside" := "serverside",
# or
# "clientside" := "serverside poolname",
# The clientside and serverside fields must contain a URI (at least a "/") and
# may also contain a hostname.  Here are some examples:
# "/clientdir" := "/serverdir",
# "[www.host.com/clientdir"](//www.host.com/clientdir) := "internal.company.com/serverdir",
# "[www.host.com/"](//www.host.com/) := "internal.company.com/serverdir/",
# Notes:
# 1) You can optionally define a ProxyPassSNATs data group to SNAT based
# on the pool selected.
# 2) You can optionally define a ProxyPassSSLProfiles data group to select
# a serverssl profile based on the pool selected.
# 3) You can also use regular expressions which is documented on DevCentral.

when RULE_INIT {
    # Enable to debug ProxyPass translations via log messages in /var/log/ltm
    # (2 = verbose, 1 = essential, 0 = none)
    set static::ProxyPassDebug 2

    # Enable to rewrite page content (try a setting of 1 first)
    # (2 = attempt to rewrite host/path and just /path, 1 = attempt to rewrite host/path)
    set static::RewriteResponsePayload 0

}

when CLIENT_ACCEPTED {
    # Get the default pool name.  This is used later to explicitly select
    # the default pool for requests which don't have a pool specified in
    # the class.
    set default_pool [LB::server pool]

    # The name of the Data Group (aka class) we are going to use.
    # Parse just the virtual server name by stripping off the folders (if present)
    set clname "ProxyPass[URI::basename [virtual name]]"

    if { $static::ProxyPassDebug > 1 } {
        log local0. "[virtual name]: [IP::client_addr]:[TCP::client_port] -> [IP::local_addr]:[TCP::local_port]"
    }


}

when HTTP_REQUEST {
    # "bypass" tracks whether or not we made any changes inbound so we
    # can skip changes on the outbound traffic for greater efficiency.
    set bypass 1

    # Initialize other local variables used in this rule
    set orig_uri "[HTTP::uri]"
    set orig_host "[HTTP::host]"
    set log_prefix "VS=[virtual name], Host=$orig_host, URI=$orig_uri"
    set clientside ""
    set serverside ""
    set newpool ""
    set ppass ""

    if {! [class exists $clname]} {
        log local0. "$log_prefix: Data group $clname not found, exiting."
        pool $default_pool
        return
    } else {
        set ppass [class match -element "$orig_host$orig_uri" starts_with $clname]
        if {$ppass eq ""} {
            # Did not find with hostname, look for just path
            set ppass [class match -element "$orig_uri" starts_with $clname]
        }
        if {$ppass eq ""} {
            # No entries found
            if { $static::ProxyPassDebug > 0 } {
                log local0. "$log_prefix: No rule found, using default pool $default_pool and exiting"
            }
            pool $default_pool
            return
        }
    }

    # Store each entry in the data group line into a local variable
    set clientside [getfield $ppass " " 1]
    set serverside [string trimleft [getfield $ppass " " 2 ] "{" ]
    set newpool [string trimright [getfield $ppass " " 3 ] "}" ]

    # If serverside is in the form =match=replace=, apply regex
    if {$serverside starts_with "="} {
        set regex [getfield $serverside "=" 2]
        set rewrite [getfield $serverside "=" 3]
        if {[regexp -nocase $regex "$orig_host$orig_uri" 0 1 2 3 4 5 6 7 8 9]}{
            # The clientside becomes the matched string and the serverside the substitution
            set clientside $0
            set serverside [eval set X $rewrite]
        } else {
            pool $default_pool
            return
        }
    }

    if {$clientside starts_with "/"} {
        # No virtual hostname specified, so use the Host header instead
        set host_clientside $orig_host
        set path_clientside $clientside
    } else {
        # Virtual host specified in entry, split the host and path
        set host_clientside [getfield $clientside "/" 1]
        set path_clientside [substr $clientside [string length $host_clientside]]
    }
    # At this point $host_clientside is the client hostname, and $path_clientside
    # is the client-side path as specified in the data group

    set host_serverside [getfield $serverside "/" 1]
    set path_serverside [substr $serverside [string length $host_serverside]]
    if {$host_serverside eq ""} {
        set host_serverside $host_clientside
    }
    # At this point $host_serverside is the server hostname, and $path_serverside
    # is the server-side path as specified in the data group

    # In order for directory redirects to work properly we have to be careful with slashes
    if {$path_clientside equals "/"} {
        # Make sure serverside path ends with / if clientside path is "/"
        if {!($path_serverside ends_with "/")} {
            append path_serverside "/"
        }
    } else {
        # Otherwise, neither can end in a / (unless serverside path is just "/")
        if {!($path_serverside equals "/")} {
            if {$path_serverside ends_with "/"} {
                set path_serverside [string trimright $path_serverside "/"]
            }
            if {$path_clientside ends_with "/"} {
                set path_clientside [string trimright $path_clientside "/"]
            }
        }
    }

    if { $static::ProxyPassDebug } {
        log local0. "$log_prefix: Found Rule, Client Host=$host_clientside, Client Path=$path_clientside, Server Host=$host_serverside, Server Path=$path_serverside"
    }

    # If you go to http://www.domain.com/dir, and /dir is a directory, the web
    # server will redirect you to http://www.domain.com/dir/.  The problem is, with ProxyPass, if the client-side
    # path is http://www.domain.com/dir, but the server-side path is http://www.domain.com/, the server will NOT
    # redirect the client (it isn't going to redirect you to http://www.domain.com//!).  Here is the problem with
    # that.  If there is an image referenced on the page, say logo.jpg, the client doesn't realize /dir is a directory
    # and as such it will try to load http://www.domain.com/logo.jpg and not http://www.domain.com/dir/logo.jpg.  So
    # ProxyPass has to handle the redirect in this case.  This only really matters if the server-side path is "/",
    # but since we have the code here we might as well offload all of the redirects that we can (that is whenever
    # the client path is exactly the client path specified in the data group but not "/").
    if {$orig_uri eq $path_clientside} {
        if {([string index $path_clientside end] ne "/") and not ($path_clientside contains ".") } {
            set is_https 0
            if {[PROFILE::exists clientssl] == 1} {
                set is_https 1
            }
            # Assumption here is that the browser is hitting http://host/path which is a virtual path and we need to do the redirect for them
            if {$is_https == 1} {
                HTTP::redirect "https://$orig_host$orig_uri/"
                if { $static::ProxyPassDebug } {
                    log local0. "$log_prefix: Redirecting to https://$orig_host$orig_uri/"
                }
            } else {
                HTTP::redirect "http://$orig_host$orig_uri/"
                if { $static::ProxyPassDebug } {
                    log local0. "$log_prefix: Redirecting to http://$orig_host$orig_uri/"
                }
            }
            return
        }
    }

    if {$host_clientside eq $orig_host} {
        if {$orig_uri starts_with $path_clientside} {
            set bypass 0
            # Take care of pool selection
            if {$newpool eq ""} {
                pool $default_pool
                if { $static::ProxyPassDebug > 1 } {
                    log local0. "$log_prefix: Using default pool $default_pool"
                }
                set newpool $default_pool
            } else {
                pool $newpool
                if { $static::ProxyPassDebug > 0 } {
                    log local0. "$log_prefix: Using parsed pool $newpool (make sure you have OneConnect enabled)"
                }
            }
        }
    }

    # If we did not match anything, skip the rest of this event
    if {$bypass} {
        return
    }

    # The following code will look up SNAT addresses from
    # the data group "ProxyPassSNATs" and apply them.
    #
    # The format of the entries in this list is as follows:
    #
    # <pool name> <SNAT IP|automap>
    #
    # All entries are separated by spaces, and both items
    # are required.
    set class_exists_cmd "class exists ProxyPassSNATs"
    if {! [eval $class_exists_cmd]} {
        return
    }

    set snat [class match -value $newpool equals /Common/ProxyPassSNATs]

    if {$snat eq ""} {
        # No snat found, skip rest of this event
        return
    }

    if { $static::ProxyPassDebug > 0 } {
        log local0. "$log_prefix: SNAT address $snat assigned for pool $newpool"
    }
    snat $snat

}

when HTTP_REQUEST_SEND {
    # If we didn't match anything, skip the rest of this event
    if {$bypass} {
        return
    }

    # The following code does the actual rewrite on its way TO
    # the backend server. It replaces the URI with the newly
    # constructed one and masks the "Host" header with the FQDN
    # the backend pool server wants to see.
    #
    # If a new pool or custom SNAT are to be applied, these are
    # done here as well. If a SNAT is used, an X-Forwarded-For
    # header is attached to send the original requesting IP
    # through to the server.

    if {$host_clientside eq $orig_host} {
        if {$orig_uri starts_with $path_clientside} {
            if { $static::ProxyPassDebug > 1 } {
                log local0. "$log_prefix: New Host=$host_serverside, New Path=$path_serverside[substr $orig_uri [string length $path_clientside]]"
            }
            clientside {
                # Rewrite the URI
                HTTP::uri $path_serverside[substr $orig_uri [string length $path_clientside]]
                # Rewrite the Host header
                HTTP::header replace Host $host_serverside
                # Now alter the Referer header if necessary
                if { [HTTP::header exists "Referer"] } {
                     set protocol [URI::protocol [HTTP::header Referer]]
                     if {$protocol ne ""} {
                          set client_path [findstr [HTTP::header "Referer"] $host_clientside [string length $host_clientside]]
                          if {$client_path starts_with $path_clientside} {
                                if { $static::ProxyPassDebug > 1 } {
                                     log local0. "$log_prefix: Changing Referer header: [HTTP::header Referer] to $protocol://$host_serverside$path_serverside[substr $client_path [string length $path_clientside]]"
                                }
                                HTTP::header replace "Referer" "$protocol://$host_serverside$path_serverside[substr $client_path [string length $path_clientside]]"
                          }
                     }
                }
            }
        }
    }

    # If we're rewriting the response content, prevent the server from using
    #   compression in its response by removing the Accept-Encoding header
    #   from the request.  LTM does not decompress response content before
    #   applying the stream profile.  This header is only removed if we're
    #   rewriting response content.
    clientside {
        if { $static::RewriteResponsePayload } {
            if { [HTTP::header exists "Accept-Encoding"] } {
                HTTP::header remove "Accept-Encoding"
                if { $static::ProxyPassDebug > 1} {
                    log local0. "$log_prefix: Removed Accept-Encoding header"
                }
            }
        }
        HTTP::header insert "X-Forwarded-For" "[IP::remote_addr]"
    }

}

when HTTP_RESPONSE {
    if { $static::ProxyPassDebug > 1 } {
        log local0. "$log_prefix: [HTTP::status] response from [LB::server]"
    }

    if {$bypass} {
        # No modification is necessary if we didn't change anything inbound so disable the stream filter if it was enabled

        # Check if we're rewriting the response
        if {$static::RewriteResponsePayload} {
            if { $static::ProxyPassDebug > 1 } {
                log local0. "$log_prefix: Rewriting response content enabled, but disabled on this response."
            }

            # Need to explicity disable the stream filter if it's not needed for this response
            # Hide the command from the iRule parser so it won't generate a validation error
            #   when not using a stream profile
            set stream_disable_cmd "STREAM::disable"

            # Execute the STREAM::disable command.  Use catch to handle any errors. Save the result to $result
            if { [catch {eval $stream_disable_cmd} result] } {
                # There was an error trying to disable the stream profile.
                log local0. "$log_prefix: Error disabling stream filter ($result). If you enable static::RewriteResponsePayload, then you should add a stream profile to the VIP.  Else, set static::RewriteResponsePayload to 0 in this iRule."
            }
        }

        # Exit from this event.
        return
    }

    # Check if we're rewriting the response
    if {$static::RewriteResponsePayload} {
        # Configure and enable the stream filter to rewrite the response payload
        # Hide the command from the iRule parser so it won't generate a validation error
        #   when not using a stream profile
        if {$static::RewriteResponsePayload > 1} {
            set stream_expression_cmd "STREAM::expression \"@$host_serverside$path_serverside@$host_clientside$path_clientside@ @$path_serverside@$path_clientside@\""
        } else {
            set stream_expression_cmd "STREAM::expression \"@$host_serverside$path_serverside@$host_clientside$path_clientside@\""
        }
        set stream_enable_cmd "STREAM::enable"
        if { $static::ProxyPassDebug > 1 } {
            log local0. "$log_prefix: \$stream_expression_cmd: $stream_expression_cmd, \$stream_enable_cmd: $stream_enable_cmd"
        }

        # Execute the STREAM::expression command. Use catch to handle any errors. Save the result to $result
        if { [catch {eval $stream_expression_cmd} result] } {
            # There was an error trying to set the stream expression.
            log local0. "$log_prefix: Error setting stream expression ($result). If you enable static::RewriteResponsePayload, then you should add a stream profile to the VIP.  Else, set static::RewriteResponsePayload to 0 in this iRule."
        } else {
            # No error setting the stream expression, so try to enable the stream filter
            # Execute the STREAM::enable command.  Use catch to handle any errors. Save the result to $result
            if { [catch {eval $stream_enable_cmd} result] } {
                # There was an error trying to enable the stream filter.
                log local0. "$log_prefix: error enabling stream filter ($result)"
            } else {
                if { $static::ProxyPassDebug > 1 } {
                    log local0. "$log_prefix: Successfully configured and enabled stream filter"
                }
            }
        }
    }

    # Fix Location, Content-Location, and URI headers
    foreach header {"Location" "Content-Location" "URI"} {
        set protocol [URI::protocol [HTTP::header $header]]
        if { $static::ProxyPassDebug > 1 } {
            log local0. "$log_prefix: Checking $header=[HTTP::header $header], \$protocol=$protocol"
        }
        if {$protocol ne ""} {
            set server_path [findstr [HTTP::header $header] $host_serverside [string length $host_serverside]]
            if {$server_path starts_with $path_serverside} {
                if { $static::ProxyPassDebug } {
                    log local0. "$log_prefix: Changing response header $header: [HTTP::header $header] with $protocol://$host_clientside$path_clientside[substr $server_path [string length $path_serverside]]"
                }
                HTTP::header replace $header $protocol://$host_clientside$path_clientside[substr $server_path [string length $path_serverside]]
            }
        }
    }

    # Rewrite any domains/paths in Set-Cookie headers
    if {[HTTP::header exists "Set-Cookie"]}{
        array unset cookielist
        foreach cookievalue [HTTP::header values "Set-Cookie"] {
            set cookiename [getfield $cookievalue "=" 1]
            set namevalue ""
            set newcookievalue ""
            foreach element [split $cookievalue ";"] {
                set element [string trim $element]
                if {$namevalue equals ""} {
                    set namevalue $element
                } else {
                    if {$element contains "="} {
                        set elementname [getfield $element "=" 1]
                        set elementvalue [getfield $element "=" 2]
                        if {[string tolower $elementname] eq "domain"} {
                            set elementvalue [string trimright $elementvalue "."]
                            if {$host_serverside ends_with $elementvalue} {
                                if {$static::ProxyPassDebug > 1} {
                                    log local0. "$log_prefix: Modifying cookie $cookiename domain from $elementvalue to $host_clientside"
                                }
                                set elementvalue $host_clientside
                            }
                            append elementvalue "."
                        }
                        if {[string tolower $elementname] eq "path"} {
                            if {$elementvalue starts_with $path_serverside} {
                                if {$static::ProxyPassDebug > 1} {
                                    log local0. "$log_prefix: Modifying cookie $cookiename path from $elementvalue to $path_clientside[substr $elementvalue [string length $path_serverside]]"
                                }
                                set elementvalue $path_clientside[substr $elementvalue [string length $path_serverside]]
                            }
                        }
                        append newcookievalue "; $elementname=$elementvalue"
                    } else {
                        append newcookievalue "; $element"
                    }
                }
            }
            set cookielist($cookiename) "$namevalue$newcookievalue"
        }
        HTTP::header remove "Set-Cookie"
        foreach cookiename [array names cookielist] {
            HTTP::header insert "Set-Cookie" $cookielist($cookiename)
            if {$static::ProxyPassDebug > 1} {
                log local0. "$log_prefix: Inserting cookie: $cookielist($cookiename)"
            }
        }
    }

}

# Only uncomment this event if you need extra debugging for content rewriting.
# This event can only be uncommented if the iRule is used with a stream profile.
# when STREAM_MATCHED {
# if { $static::ProxyPassDebug } {
# log local0. "$log_prefix: Rewriting match: [STREAM::match]"
# }
# }
# The following code will look up SSL profile rules from
# the Data Group ProxyPassSSLProfiles" and apply
# them.
#
# The format of the entries in this list is as follows:
#
# <pool name> <serverssl profile name></serverssl></pool>
#
# All entries are separated by spaces, and both items
# are required.  The virtual server also will need to
# have any serverssl profile applied to it for this to work.

when SERVER_CONNECTED {
    if {$bypass} {
        return
    }

    set class_exists_cmd "class exists ProxyPassSSLProfiles"
    if {! [eval $class_exists_cmd]} {
        return
    }

    set pool [LB::server pool]
    set profilename [class match -value $pool equals /Common/ProxyPassSSLProfiles]

    if {$profilename eq ""} {
        if { [PROFILE::exists serverssl] == 1} {
            # Hide this command from the iRule parser (in case no serverssl profile is applied)
            set disable "SSL::disable serverside"
            catch {eval $disable}
        }
        return
    }

    if { $static::ProxyPassDebug > 0 } {
        log local0. "$log_prefix: ServerSSL profile $profilename assigned for pool $pool"
    }
    if { [PROFILE::exists serverssl] == 1} {
        # Hide these commands from the iRule parser (in case no serverssl profile is applied)
        set profile "SSL::profile $profilename"
        catch {eval $profile}
        set enable "SSL::enable serverside"
        catch {eval $enable}
    } else {
        log local0. "$log_prefix: ServerSSL profile must be defined on virtual server to enable server-side encryption!"
    }
}
# ProxyPass Release History
#v10.9: Nov 26, 2012: Used URI::basename to get the virtual server name. Thanks to Opher Shachar for the suggestion.
#       Replaced indentations with tabs intead of spaces to save on characters
#v10.8: Oct 25, 2012: Updated the class name to remove the folder(s) (if present) from the virtual server name. 
#        This assumes the ProxyPass data group is in the same partition as the iRule.
#v10.7: Oct 24, 2012: Changed array set cookielist {} to array unset cookielist as the former does not clear the array.
#        Thanks to rhuyerman@schubergphilis.com and Simon Kowallik for pointing out the issue and this wiki page with details: http://wiki.tcl.tk/724 
#v10.6: Oct 14, 2012: Updated how the protocol is parsed from URLs in request and response headers to fix errant matches
#v10.5: Feb 2, 2012: Removed extra stream profile $result reference for debug logging.
#v10.4: Nov 23, 2011: Removed an extra colon in sever HTTP::header replace commands to prevent duplicate headers from being inserted
#v10.3: Sep 27, 2010: Moved rewrite code to HTTP_REQUEST_SEND to work with WebAccelerator
#        Fixed bug with cookie rewrites when cookie value contained an "="
#v10.2: Jun 04, 2010: Can handle individual file mappings thanks to Michael Holmes from AZDOE
#        Also fixed bug with directory slash logic
#v10.1: Oct 24, 2009: Now CMP-friendly! (NOTE: use ProxyPass v8.2 for TMOS v9.x)
#v10.0: May 15, 2009: Optimized for external classes in v10 only (use v8.2 for TMOS v9.x)
#        Added support for regular expressions and backreferences for the translations.
# v8.2: Jun 04, 2010: Fixed bug with directory slash logic
# v8.1: May 15, 2009: Added internal redirects back in (removing them was a mistake)
# v8.0: May 13, 2009: pulled in changes submitted by Aaron Hooley (hooleylists gmail com)
#        TMOS v10 support added.  Cookie domain/path rewriting added.
# v7.0: May 6, 2008: added optional serverssl contributed by Joel Moses
# v6.0: Jan 15, 2008: Small efficiency change
# v5.0: Jul 27, 2007: Added Referer header conversions
# v4.0: Jul 27, 2007: Added optional debugging flag
# v3.0: Jul 20, 2007: Added SNAT support contributed by Adam Auerbach
# v2.0: May 28, 2007: Added internal directory redirects and optional stream profile
# v1.0: Feb 20, 2007: Initial Release
0