ProxyPass (for LTM v9 only)
Problem this snippet solves:
iRule to replace the functionality of Apache Webserver ProxyPass and ProxyPassReverse functions. Allows you to do hostname and pathname modifications as HTTP traffic passes through the LTM.
(Full documentation follows the iRule.)
For use with TMOS v10 and v11, see ProxyPass v10.9
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 command which can translate the URL on the way into the server, and the ProxyPassReverse command which can un-translate header fields such as Location in the case of a redirect. ProxyPass now handles the domain and path in any cookies set by the server.
Now you can accomplish this with an iRule. The ProxyPass iRule will translate incoming requests in a flexible manner and untranslate the Location, Content-Location, and/or URI headers in the response back to the client.
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. It will NOT modify absolute path references that are not full URLs. 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.
< a href=”/page2.html”> will not be modified and will probably cause the website to break if the user follows this link. In this case you need to modify the website and/or consider a different architecture.
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. Note that it uses data groups of type "string" in all cases.
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 the third item in 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 There is an optional 4th item for an entry to enable dynamic SNAT. If you specify a SNAT address as the 4th item for an entry then the default SNAT settings of the virtual server will be overridden such that the request will be SNATed to the specified IP address. In addition, an X-Forwarded-For header automatically inserted. For example, the entries:
/support support.company.com/ SupportPool 10.10.10.11 /downloads downloads.company.com/ DownloadPool 10.10.10.12
Will cause the requests to be SNATed to 10.10.10.11 or 10.10.10.12 depending on which URI was requested.
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 is:
Pool1 ServerSSLProfile1 Pool2 ServerSSLProfile2
Debugging You can debug your ProxyPass rules by setting the ::ProxyPassDebug variable at the top of the rule to 1 (or 2 for more verbose debugging). Once you do this you can SSH in to the BIG-IP and run the command “tail –f /var/log/ltm” to see what ProxyPass is doing to your requests.
Code :
# ProxyPass iRule, Version 8.2 # June 4, 2010 # Created and Maintained by Kirk Bauer# https://devcentral.f5.com/s/wiki/default.aspx/iRules/ProxyPass.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 unless you # are doing debugging. Just apply the iRule to the virtual server and # define the appropriate Data Group and you are done. If you make any # changes to the iRule, please send them back to me so I can understand # what you changed and why and possibly update 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). # 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 # redirects. If you want to change this, edit the proper variable in RULE_INIT # and apply the generic "stream" profile to the virtual server. # Data Group Format (must be string-type entries) # clientside serverside [pool] [SNAT IP] # Only the first two fields are required. They always contain a path and # may also contain a hostname. Here are some examples: # /clientdir /serverdir # www.host.com/clientdir internal.company.com/serverdir # www.host.com/ internal.company.com/serverdir/ # Note that the clientside entry only ends in a / if there is no directory. # The server entry only ends in a slash if the client entry does. # Notes: # 1) If you are using TMOS v10.0.0 or later, you must make two changes # below (search for TMOSv10). In addition, I highly recommend upgrading # to ProxyPass v10 if you are running TMOSv10. # 2) This iRule is *not* Cluster Multi-processing (CMP) compatible because # of the global $::ProxyPassDebug setting. In TMOS v10, you can make the # specified change in RULE_INIT and then replace all occurrences of # $::ProxyPassDebug with $static::ProxyPassDebug. In TMOS v9, you can # remove the entire RULE_INIT event and all references to $::ProxyPassDebug # and you will just lose the ability to turn on debugging. You could always # switch back to the original iRule to turn debugging back on. # 3) You can optionally define a ProxyPassSSLProfiles data group to select # a serverssl profile based on the pool selected. when RULE_INIT { # Enable to debug ProxyPass translations via log messages in /var/log/ltm # (2 = verbose, 1 = essential, 0 = none) # TMOSv10: You can optionally change this global variable to a static # to make this iRule CMP-friendly (change references within iRule too). # set static::ProxyPassDebug 0 set ::ProxyPassDebug 0 # 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) # TMOSv10: You can optionally change this global variable to a static # to make this iRule CMP-friendly (change references within iRule too). # set static::RewriteResponsePayload 0 set ::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] if { $::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 # The name of the Data Group (aka class) we are going to use # TMOSv10: use this line for TMOSv10 (and comment out the line below it) #set clname "ProxyPass[virtual name]" set clname "::ProxyPass[virtual name]" # Initialize other local variables used in this rule set orig_uri "[HTTP::uri]" set orig_host "[HTTP::host]" set log_prefix "[virtual name], Host=$orig_host, URI=$orig_uri" set clientside "" set serverside "" set newpool "" set snataddr "" set ppass "" # TMOSv10: use this line for TMOSv10 (and comment out the line below it) # (don't forget to move the curly brace to the other line) #if {! [class exists $clname]} if {! [info exists $clname]} { # Data Group not defined: do not do anything further and exit this iRule log local0. "$log_prefix: Data Group $clname not found." pool $default_pool return } set match_len 0 if { $::ProxyPassDebug > 1 } { log local0. "$log_prefix: Looking for entries matching $orig_host$orig_uri" } # TMOSv10: use this line for TMOSv10 (and comment out the line below it) # (don't forget to move the curly brace to the other line) #foreach entry [class names $clname] foreach entry [set [set clname]] { # Look through data group to find an entry where the host/uri # starts with the clientside portion of an entry. Track the # length of the match to find the longest match (more specific). if {"$orig_host$orig_uri" starts_with [getfield $entry " " 1]} { set new_len [string length [getfield $entry " " 1]] if {$new_len > $match_len} { set ppass $entry set match_len $new_len } } } if {$ppass eq ""} { # We did not find an entry with the hostname, now try just the URI. # Look through data group to find an entry where the uri # starts with the clientside portion of an entry. Track the # length of the match to find the longest match (more specific). if { $::ProxyPassDebug > 1 } { log local0. "$log_prefix: Looking for entries matching $orig_uri" } # TMOSv10: use this line for TMOSv10 (and comment out the line below it) # (don't forget to move the curly brace to the other line) #foreach entry [class names $clname] foreach entry [set [set clname]] { if {$orig_uri starts_with [getfield $entry " " 1]} { set new_len [string length [getfield $entry " " 1]] if {$new_len > $match_len} { set ppass $entry set match_len $new_len } } } } if {$ppass eq ""} { # No entries found, stop processing this request if { $::ProxyPassDebug > 1 } { log local0. "$log_prefix: No rule found" } pool $default_pool return } # Store each entry in the data group line into a local variable set clientside [getfield $ppass " " 1] set serverside [getfield $ppass " " 2] set newpool [getfield $ppass " " 3] set snataddr [getfield $ppass " " 4] if {[substr $clientside 0 1] eq "/"} { # 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 { $::ProxyPassDebug > 0 } { log local0. "$log_prefix: Found Rule, Client Host=$host_clientside, Client Path=$path_clientside, Server Host=$host_serverside, Server Path=$path_serverside" } # As you may or may not know, 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 "/"} { 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 { $::ProxyPassDebug } { log local0. "$log_prefix: Redirecting to https://$orig_host$orig_uri/" } } else { HTTP::redirect "http://$orig_host$orig_uri/" if { $::ProxyPassDebug } { log local0. "$log_prefix: Redirecting to http://$orig_host$orig_uri/" } } 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} { # Do not bypass the iRule in the response set bypass 0 if { $::ProxyPassDebug > 1 } { log local0. "$log_prefix: New Host=$host_serverside, New Path=$path_serverside[substr $orig_uri [string length $path_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 [substr [HTTP::header "Referer"] 0 $host_clientside] if {[string length $protocol] > 0} { set client_path [findstr [HTTP::header "Referer"] $host_clientside [string length $host_clientside]] if {$client_path starts_with $path_clientside} { if { $::ProxyPassDebug > 1 } { log local0. "$log_prefix: Changing Referer header: [HTTP::header Referer] with $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]] } } } # Take care of pool selection and SNAT settings if {$newpool eq ""} { pool $default_pool if { $::ProxyPassDebug > 1 } { log local0. "$log_prefix: Using default pool $default_pool" } } else { pool $newpool if { $::ProxyPassDebug > 0 } { log local0. "$log_prefix: Using parsed pool $newpool" } } if {$snataddr != ""} { snat $snataddr if { $::ProxyPassDebug > 0 } { log local0. "$log_prefix: Using SNAT address $snataddr" } HTTP::header insert "X-Forwarded-For" "[IP::remote_addr]" } } } # 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. if { $::RewriteResponsePayload } { if { [HTTP::header exists "Accept-Encoding"] } { HTTP::header remove "Accept-Encoding" if { $::ProxyPassDebug > 1} { log local0. "$log_prefix: Removed Accept-Encoding header" } } } } when HTTP_RESPONSE { if {$bypass} { # No modification is necessary if we didn't change anything inbound # Check if we're rewriting the response if {$::RewriteResponsePayload} { if { $::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 \$::RewriteResponsePayload, then you should add a stream profile to the VIP. Else, set \$::RewriteResponsePayload to 0 in this iRule." } } # Exit from this event. return } # Check if we're rewriting the response if {$::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 {$::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 { $::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 \$::RewriteResponsePayload, then you should add a stream profile to the VIP. Else, set \$::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): $result" } else { if { $::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 [substr [HTTP::header $header] 0 $host_serverside] if {$protocol ne ""} { set server_path [findstr [HTTP::header $header] $host_serverside [string length $host_serverside]] if {$server_path starts_with $path_serverside} { if { $::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 set cookielist { } # A response may have multiple Set-Cookie headers, loop through them foreach cookievalue [HTTP::header values "Set-Cookie"] { set cookiename [getfield $cookievalue "=" 1] set newcookievalue "" # Each cookie starts with name=value and then has more name/value pairs foreach element [split $cookievalue ";"] { set element [string trim $element] if {$element contains "="} { set elementname [getfield $element "=" 1] set elementvalue [getfield $element "=" 2] if {$elementname eq "domain"} { # Rewrite domain of cookie, if necessary. if {$elementvalue eq $host_serverside} { if {$::ProxyPassDebug > 1} { log local0. "Modifying cookie $cookiename domain from $elementvalue to $host_clientside" } set elementvalue $host_clientside } } if {$elementname eq "path"} { # Rewrite path of cookie, if necessary. if {$elementvalue starts_with $path_serverside} { if {$::ProxyPassDebug > 1} { log local0. "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; " } } # Store new cookie value for later re-insertion. The cookie value # string will end with an extra "; " so strip that off here. set cookielist($cookiename) [string range $newcookievalue 0 [expr {[string length $newcookievalue] - 3}]] } # Remove all Set-Cookie headers and re-add them (modified or not) HTTP::header remove "Set-Cookie" foreach cookiename [array names cookielist] { HTTP::header insert "Set-Cookie" $cookielist($cookiename) if {$::ProxyPassDebug > 1} { log local0. "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 { $::ProxyPassDebug } { # log local0. "$log_prefix: Rewriting match: [STREAM::match]" # } #} # The following code will look up SSL profile rules from # the Data Group List "ProxyPassSSLProfiles" and apply # them. # # The format of the entries in this list is as follows: # # # # All entries are separated by spaces, and both items # are required. Failure to set them will result in an # error message. when SERVER_CONNECTED { if {$bypass} { return } if {! [info exists ::ProxyPassSSLProfiles]} { return } set pool [LB::server pool] set profilename [findclass $pool 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 { $::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 # 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 06, 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
Tested this on version:
9.0