Here is an example I wrote for a customer for 9.4.7, based on preliminary project plans. The iRule was never used in production, so I didn't fully test or performance tune it. I've anonymized the rule, but not tested it, so try this on a test unit or test VS first.
If you're on 10.x you would want to replace the class references with class commands and the global variables in RULE_INIT to static::variables.
Name: customer_HTTPS_redirects_rewrites_rule
HTTPS Redirects and Rewrites iRule
BIG-IP ASM
Tested on version 9.4.7
Current version:
v1.2 2009 Sept 10 - Aaron Hooley
Version: 1.0 - 2009 Aug 14 - Initial version tested
Version: 1.1 - 2009 Aug 27 - Updates to query string logic
Version: 1.2 - 2009 Sept 10 - Logging updates
Description:
This rule performs HTTP redirection or host/URI rewriting based on the configuration in a datagroup.
Functionality:
1. This iRule can be used on an HTTP and/or HTTPS virtual server.
A single configuration datagroup is referenced regardless of the client side HTTP or HTTPS setup.
2. The requested host and URI are checked against the source field of the configuration datagroup.
The longest matching configuration line is used.
The check is done explicitly using the requested path (/path/to/file.ext in the example URI /path/to/file.ext?param=value),
logically stripping the query string from the requested URI.
3. Query string handling:
The requested query string is not evaluated in the matching, but is preserved in rewrites and redirects.
If the original request has a query string, it will be preserved. If the original request has no query string and the destination does,
the destination query string is used.
Configuration requirements:
1. A datagroup named customer_redirects_rewrites_class must be configured using the following space delimited format:
source (host and/or URI) | destination (URL for redirects or host and/or URI for rewrites | action (rewrite or redirect)
---------------------------|----------------------------------------------------------------|-----------------------------
www.example.co.uk/dir/ | https://www.example.co.uk/new/directory/ | redirect
www.example.co.uk/rewrite | www.example.co.uk/cs/rewritten | rewrite
The Host portion of any entry must be set to lower case.
The fields must use URL encoding for spaces or the parsing will fail.
The destination must not include the protocol if the action is 'rewrite'
2. The virtual server must have this iRule added as a resource.
when RULE_INIT {
Log debug messages to /var/log/ltm? (0=none, 1=minimal, 2=verbose)
set ::customer_HTTPS_debug 1
if {$::customer_HTTPS_debug > 0}{log local0. "Debug set to $::customer_HTTPS_debug"}
}
when HTTP_REQUEST {
if {$::customer_HTTPS_debug > 0}{
set log_prefix "[IP::client_addr]:[TCP::client_port] ([TCP::local_port])"
log local0. "$log_prefix: -------------------------------------------------------------------------"
log local0. "$log_prefix: New request to [HTTP::host][HTTP::uri] with UA: [HTTP::header User-Agent]"
}
Check requested host/URI against the datagroup to see if there are any matching rules
for redirecting or rewriting the request.
Look for the most specific (longest) match by searching first for the requested host/URI and then just requested URI.
Evaluate all rules to find the longest match.
Reset the longest match
set longest_match 0
set matching_line ""
Loop through each datagroup line (need to update [set classname] to [class get classname] for 10.x)
foreach line [set ::customer_HTTPS_redirects_rewrites_class] {
if {$::customer_HTTPS_debug > 1}{log local0. "$log_prefix: Checking [string tolower [HTTP::host]][HTTP::path] against [getfield $line " " 1]"}
Check if the current line matches the host/URI
if {[string match [string tolower [HTTP::host]][HTTP::path] [getfield $line " " 1]]}{
if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: Matched: $line"}
Found a match, so save the length of the current source token
set current_length [string length [getfield $line " " 1]]
if {$current_length > $longest_match}{
This is the longest match so far
set matching_line $line
Update the length of the current match
set longest_match $current_length
if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: New longest match: $matching_line"}
}
}
}
Check if we haven't found a host/URI match.
if {$matching_line eq ""}{
Loop through each datagroup line
foreach line [set ::customer_HTTPS_redirects_rewrites_class] {
if {$::customer_HTTPS_debug > 1}{log local0. "$log_prefix: Checking [HTTP::path] against [getfield $matching_line " " 1]"}
Check if the current line matches the host/URI
if {[string match [getfield $matching_line " " 1] [HTTP::path]]}{
if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: Matched: $matching_line"}
Found a match, so save the length of the current source token
set current_length [string length [getfield $matching_line " " 1]]
if {$current_length > $longest_match}{
This is the longest match so far
set matching_line $line
Update the length of the current match
set longest_match $current_length
if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: New longest match: $line"}
}
}
}
}
if {$matching_line eq ""}{
if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: No match for [HTTP::host][HTTP::uri], exiting."}
If there still isn't a match then exit this event in this rule
return
} else {
There was a matching line in the config datagroup so save the line
set destination [getfield $matching_line " " 2]
if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: Processing matching line: $matching_line"}
Check for a query string in the original request as we'll need to include it in the redirect
As there is a bug with HTTP::query (CR96326) parse HTTP::uri to get the query string
set request_query_string [getfield [HTTP::uri] ? 2]
Check if the source and destination both have a query string
if {$request_query_string != "" && [scan $destination {%[^?]?%[^?]} destination_path destination_query_string]==2}{
set destination [string map -nocase "$destination_query_string $request_query_string" $destination]
if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: Replaced destination query string with original query string.\
Orig: $destination_query_string Dest: $request_query_string"}
}
Check the action from the rule
switch [string tolower [getfield $matching_line " " 3]] {
"redirect" {
Redirect to the new location
HTTP::respond 301 noserver Location [getfield $matching_line " " 2]
if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: Redirecting to [getfield $matching_line " " 2]"}
}
"rewrite" {
Rewrite the host and URI
Need to determine if destination is a host plus URI or an absolute URI
The first character for a URI is a /, and a host plus URI is an alphanumeric
switch [string range $destination 0 0] {
"/" {
Destination is a URI so just rewrite the URI
HTTP::uri $destination
if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: Rewriting URI to $destination"}
}
default {
Destination is a host and URI
Save the host and URI separately using scan to save everything up to the first / as the host
and everything including and after as the URI
scan $destination {%[^/]%s} host uri
if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: Parsed host: $host, URI: $uri"}
HTTP::header replace Host $host
if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: Rewrote host to $host"}
Rewrite the URI if the destination URI is not empty or /
if {[string length $uri] > 1}{
HTTP::uri $uri
if {$::customer_HTTPS_debug > 0}{log local0. "$log_prefix: Rewrote URI to $uri"}
}
}
}
}
}
}
}
when HTTP_REQUEST priority 501 {
This event is for debugging only and can be commented out or removed after testing is complete
if {$::customer_HTTPS_debug > 0 && [getfield $matching_line " " 3] ne "redirect"}{
log local0. "$log_prefix: 501: Host: [HTTP::host], URI: [HTTP::uri]"
}
}
Aaron