iRule Recipe 1: Single URL Explicit Redirect
Series Introduction
Let's face it: there aren't many people out there who have extensive experience with Tcl. Since iRules is a Tcl dialect, that means that finding a solid iRules solution can be challenging, even for otherwise experienced coders. And many times, those who architect, configure, troubleshoot and manage BIG-IPs don't normally code as part of their day job. For both groups, most problems can be solved by finding iRules code that tackles a problem similar to one they face, modifying it just a bit to suit the specific need. This series hopes to provide that sort of code: "recipes" that capture patterns used by BIG-IP practitioners to solve common problems.
Each article is a single recipe. The article begins by explaining a problem you might be trying to solve. It then presents an iRule that can solve it. After that, it provides an analysis of the iRule. Finally, it provides a more detailed background section to elaborate on how the supporting protocols work and any nuances regarding the problem or solution.
In some cases, there is a non-iRules method for solving a problem. When this the case, there will be one more section, called "How Else Could I Have Solved This?" This section outlines the alternative solution or solutions.
Recipe 1: Single URL Explicit Redirect
The Problem
You want to respond to a specific HTTP URL with an explicit HTTP Redirect, using a 301 Status Code and the Location header.
The Code
when HTTP_REQUEST { if { [string tolower [HTTP::host]] eq "www.example.com" and [HTTP::path] eq "/path1/abc" } { HTTP::respond 301 Location "http://www.new-example.com/abcd" } }
Analysis
When this rule is applied to a Local Traffic Virtual Server with the http profile applied, then any request for
www.example.com/path1/abc
will be explicitly redirected to http://www.new-example.com/abcd
. If the Virtual Server includes the clientssl profile -- which means the Virtual Server is offloading TLS/SSL -- then the Location value in this iRule should be changed to https://www.new-example.com/abcd
. This is a simple iRule, and is intended to solve a simple problem.
iRules are triggered by events. The set of profiles applied to a Virtual Server determines the events that will fire. If a Virtual Server has the http profile applied, then -- among others -- the HTTP_REQUEST event will fire. It happens when an HTTP Request message is received, and the BIG-IP has completely parsed the Request message headers. Code attached to the HTTP_REQUEST event usually reads headers or the HTTP start-line, and may modify either of them.
HTTP::host
retrieves the value of the Host header (if one isn't present, then it is the empty string), while HTTP::path
returns the path part of the Request Target (see below for a bit more detail on this). The string tolower
is needed for the Host header, because hostnames are case-indifferent. That is, www.example.com is the same name as wWw.ExAmPlE.cOm and the name can be expressed either way (and, of course, many other ways besides). string tolower
transforms all letters in the text that follows (HTTP::host
, in this case) to all lower-case. By normalizing case, we can reliably compare its value.
Notice that I do not use
string tolower
on the HTTP::path
The HTTP RFC says that the Request Target (including the path) is case-sensitive. This can get a bit messy because many web servers simply pass the path to the underlying filesystem, which may or may not be case-sensitive. However, assuming that your web server honors this requirement, then /some/path
is not the same as /SOME/path
, for example, so normalizing case is not only not valuable, it is not correct.
One more interesting point: there is an
HTTP::redirect
iRules command. Why didn't I just use that instead of HTTP::respond
? HTTP::redirect
uses a 302 Response Code. 302 is a Temporary Redirect, which encourages the user-agent to try and use the original URL on subsequent requests. Moreover, many user-agents improperly implemented the 301 (transforming any HTTP method to a GET on the redirect). Most of the time, what you mean is a Permanent Redirect, which tells the user-agent not to try the original URL again. Perhaps it would have been better if HTTP::redirect
used 301, or at least allowed you to specify which Response Code you wished, but alas, it does neither.
Elaboration
Remember! This section goes into more details about HTTP and how it relates to iRules. I believe it can be quite useful, but if all you needed was the recipe, feel free to skip this section.
HTTP transactions are stateless, and always consist of exactly two messages: a Request message followed by a Response message. Even if more than one transaction is conducted on a single TCP connection (called HTTP KeepAlive), each transaction is, at the level of HTTP, independent from all others.
An HTTP message consists of a start-line, zero or more headers, and a body. For some request message types, the body is empty. With HTTP/1.0, the header set can be empty, but with HTTP/1.1, at least the Host header is required in Request messages, and generally, Response messages always have at least one header.
When a request is made from a user-agent that looks like this:
http://www.example.com/some/path?here=there&this=that#section1
the http is the "scheme" and tells the user-agent how to treat the rest of the URL. www.example.com is sent as the value of the Host header.
/some/path
is the HTTP Request path, here=there&this=that
is the HTTP query parameter set, and #section1
is the fragment. This is transformed into an HTTP Request message that starts like this:
GET /some/path?here=here&this=that HTTP/1.1 Host: www.example.com
The second element of the start-line (that is,
/some/path?here=&there;this=that
) is called the Request Target. Originally, the RFCs called it the Request URI. This is confusing because a fully-qualified URL is an instance of a URI, but subsets of a URL are not. So really, while http://www.example.com/some/path
is a URI (and a URL), /some/path
is not. iRules uses HTTP::uri
to retrieve the Request Target, because again, that part used to be called the Request URI. But don't get caught: HTTP::uri
does not return the complete URL/URI.
Notice that the fragment (
#section1
) is not transmitted. A fragment has meaning only to the user-agent, and is a hint about where to center a rendered document.
How Else Could I Have Solved This?
Starting in BIG-IP version 11.4, Local Traffic Policies were introduced. Describing this feature is -- as they say -- outside the scope of this article, but in brief: it's a really cool way to do common HTTP transforms, including redirects. It has the advantage of not being code, and it is part of the built-in feature set. Local Traffic Policies can be modified using the BIG-IP web UI or
tmsh
.- N__Tariq_149828Nimbostratus
Great article. Thanks heaps!!!
I believe there is an extra bracket at following location and this is causing syntax error:
[HTTP::path] eq "/path1/abc" ] <<< may need to be removed??
- VernonWellsEmployee
You are naturally quite right. That must have been a cut-and-paste error. The extra square brace has been removed!
- ManishvashishtaNimbostratus
Can someone please validate below iRule? when HTTP_REQUEST { if { [string tolower [HTTP::host]] ends_with ".abc.com" } { HTTP::respond 301 Location "https://eservices-t.abc.com/irj/portal/abc[HTTP::uri]" } if { [string tolower [HTTP::host]] ends_with ".def.com" } { HTTP::respond 301 Location "https://eservices-t.def.com/irj/portal/def[HTTP::uri]" }
- VernonWellsEmployee
How to you mean? The rule looks sensible to me, depending on what you want to achieve. If the Host header contains a name in the abc.com domain, then it will send a redirect to the client to the first request target, appending the original request target to the end. If the Host header contains a name in the def.com domain, then it redirects to the second request target, appending the original again.
- rsbs01Nimbostratus
Hi Vernon.
Can I use something similar to redirect multiple URL to one specific URL, let's say https://aaa.bbb.com?
- VernonWellsEmployee
Absolutely. In that case, you could use a
or a data-group (usingswitch
). I'd recommend, however, that you look into using a Local Traffic Policy. A policy is easy to set up and doesn't require any iRules.class
Anyhow, a
based approach might look something like this:switch
when HTTP_REQUEST { switch [string tolower [HTTP::host]] { "abc.com" - "def.com" { switch -glob [HTTP::path] { "/first/second" - "/first/second/*" { HTTP::respond 301 Location "https://aaa.bbb.com/" } } } } }
This would redirect any request for abc.com or def.com that has a URI Target path starting with /first/second to ";.
- rsbs01Nimbostratus
Thanks, Vernon.
- Anthony_1648Nimbostratus
Thanks for the article! It was helpful, but I'm still having trouble getting an iRule to work for http redirects. If a user comes with a specific path /newpath I need it redirected to a different site, if the user does not specify the path I need it redirected to another site. This is what I have, but it's not working properly. Any help would be appreciated.
when HTTP_REQUEST { if { (([HTTP::host] equals "site.com/newpath") or ([HTTP::host] equals ";)) } { HTTP::redirect "; }
else {(([HTTP::host] equals "site.com") or ([HTTP::host] equals ";)) } HTTP::redirect "; }
- VernonWellsEmployee
retrieves the value of the Host header, while the Request-Target path is part of the Request-Target in the HTTP Request Start-Line. Consider a request in your browser URL bar:HTTP::host
http://site.com/newpath
This would produce an HTTP Request message similar to the following (I omit a number of likely headers that aren't relevant to this example):
GET /newpath HTTP/1.1 Host: site.com
Notice that the path element (i.e.,
) is in the Request-Line. To extract that, you should use/newpath
. Thus, to match the URL above, you would do this:HTTP::path
when HTTP_REQUEST { switch [string tolower [HTTP::host]] { "site.com" - "www.site.com" { if { [HTTP::path] eq "/newpath" } { HTTP::respond 301 Location "https://www.site2.com/newpath" } } } }
I use
to reduce the number of evaluations ofswitch
andHTTP::host
, but this could also be written:string tolower
if { [string tolower [HTTP::host]] eq "site.com" or [string tolower [HTTP::host]] eq "www.site.com" } { ... }
Having said all of this, I strongly recommend that you consider using a Local Traffic Policy.
- Anthony_1648Nimbostratus
Thank you for the response! I am not familiar with using a LTP. I will research further. Thank you!