有时iRule可以正常加载和运行而不会产生任何错误,但最后往往没有取得所期望的结果。
 
在这篇文章中我将通过一个简单的实例来验证你的iRule编写逻辑是否违背真实的数据流程:一个可以实现双向HTTP内容主机信息修改的IRule
 
我最近帮助了我的一个用户,它的配置是相当普遍的: Web服务器被配置成响应不同于向客户公开的URL里的域名: “正确”的Host Header中包含了内部的服务器名称用于Web 服务器处理请求。 另外, 无论哪个服务器响应请求中包含了内部的主机名称,都需要被替换成公开的域名。
 
下面的实例我们的主机名如下:
    公开的主机名:        easyname.domain.com
    实际的/内部主机名:      long.internal.name.domain.com
 
所有对HTTP请求,主机名"easyname.domain.com"必须被转换成"long.internal.name.domain.com".
 
而所有HTTP响应,主机名"long.internal.name.domain.com"必须转换成 "easyname.domain.com".
 

这是用户最开始写的iRule: 

when HTTP_REQUEST {
 if { [HTTP::host] equals "easyname.domain.com"} {
 HTTP::header replace Location \
[string map -nocase {easyname.domain.com long.internal.name.domain.com} [HTTP::header value Location]]
 }
}

when HTTP_RESPONSE {
 if { [HTTP::host] equals "long.internal.name.domain.com"} {
 HTTP::header replace Location \
[string map -nocase {long.internal.name.domain.com easyname.domain.com} [HTTP::header value Location]]
 }
}

 

当流量击中这条规则时,服务器与客户端之间的流量很正常并且在日志文件中没有错误信息,但是用户所期望的转换操作并没有被执行。
鉴于这些意外情况的发生,第一步需要确认的是你iRule的编写逻辑是否可以达到你预期的目的。为了达到这个目的,你可以在条件判断旁边添加一些日志记录参数。最好的方法是首先在每个决策点之前记录用来决策的变量、对象或命令,然后在决策点之后记录其他的一些信息来判断需要执行的代买是否被执行了。
 

这是修改之后的Rule,其中在条件判断语句周围包含一些Log语句:

 

when HTTP_REQUEST {
 # First we'll log the 2 header values used in the conditional code block
 log local0. "Host = [HTTP::host]"
 log local0. "Location = [HTTP::header Location]"

 if { [HTTP::host] equals "easyname.domain.com"} {

 # inside the conditional block, add another log line saying that's where you are
 log local0. "Host matched, performing replacement operation"
 HTTP::header replace Location \
"[string map -nocase {easyname.domain.com long.internal.name.domain.com} [HTTP::header value Location]]"

 # you can even log the result of the replacement operation by running it again with the log command
 log local0. "Replacement text = \
[string map -nocase {easyname.domain.com long.internal.name.domain.com} [HTTP::header value Location]]"
 }
}

when HTTP_RESPONSE {
 # For the response, we'll again log the header values used in the conditional code block
 log local0. "Host = [HTTP::host]"
 log local0. "Location = [HTTP::header Location]"

 if { [HTTP::host] equals "long.internal.name.domain.com"} {
 # inside the conditional block, add another log line saying that's where you are
 log local0. "Host matched, performing replacement operation"
 HTTP::header replace Location \
"[string map -nocase {long.internal.name.domain.com easyname.domain.com} [HTTP::header value Location]]"

 # and again, you can log the result of the replacement operation
 log local0. "Replacement text = \
[string map -nocase {long.internal.name.domain.com easyname.domain.com} [HTTP::header value Location]]"
 }
}

 

当对easyname.domain.com的请求到达这个新的iRule时,仍然得到同样的效果(没有进行替换),但是会产生下面(非常有用的!!!)日志记录:
 
HTTP_REQUEST: Host = easyname.domain.com
HTTP_REQUEST: Location =
HTTP_REQUEST: Host matched, performing replacement operation
HTTP_REQUEST: Replacement text =
 
返回的记录有时是这样:
HTTP_RESPONSE: Host = 
HTTP_RESPONSE: Location =
 
也有时是这样:
HTTP_RESPONSE: Host =
HTTP_RESPONSE: Location = http://long.internal.name.domain.com/uri
 
那么...什么被替换了? 没啥惊奇的,什么都没有(至少是错误的“什么东西”并没有被替换掉。或者它。。。?我们接下来再条论)
 
你可以说由于在“=”后面没有值,所以我们没有看到预期的结果。你也可以说由于缺少了一些日志行,所以一些预期的条件没有被满足。简而言之,现在有两个问题:
 
1.即使条件语句被完全执行,在请求数据中也没有进行替换操作
 
2.主机没有响应中没有包含主机名,因此条件语句没有被执行并且没有产生替换操作
 
 
我们再仔细分析在发送和接收端的流量(使用HTTP Watch, TCPDump或者其他你熟练使用的追踪工具)揭示一些有趣的相关细节:
 
   1. 向客户端的请求没有包含Location Header,只包含Host Header。在LTM的服务端的请求信息中同时包含了Location和Host Header。
 
   2. 响应信息没有包含主机头,只有在重定向的响应里面包含了Location Header。
 
   3. 内网主机名在HTTP的响应的内容中是清晰可见的(不仅仅是在Host和Location Header中)
 
 
这些分析导致我们需要进行以下的调整:
 
1.“替换”错误的请求header---实际上LTM在每一个请求中插入插入了一个新的”Location” header(这就是我前面提到的什么“错事”)。所以我们应当使用[HTTP::host]作为请求的条件判断并使用[HTTP::header replace]作为替换操作。
 
2.在响应中使用错误的信息作为判断条件。我们应当使用[HTTP::status]来处理重定向将处理限定在重定向的回应中,然后使用[HTTP::header replace]和[string map]只替换主机名。
 
3.在irules中完全没有对内容部分进行替换,正确的做法是:在同一个虚拟服务器中使用stream profile做来对HTTP内容中的超级链接进行替换。
 

最终的iRule,包含所有修正后的参考,修改、优化同时和stream profile的协同工作,参考如下:

 

when HTTP_REQUEST {
 if { [HTTP::host] equals "easyname.domain.com"} {
 # replace header completely if it matches
 HTTP::header replace Host "long.internal.name.domain.com"
 }
}

when HTTP_RESPONSE {
 if { [HTTP::status] starts_with "3" }{
 # replace the Location header only if the response is a redirect, 
 # since no other HTTP server responses contain the hostname in a header.
 HTTP::header replace Location \
[string map -nocase {long.internal.name.domain.com easyname.domain.com} [HTTP::header value Location]]
 # depend on stream profile to perform the hostname replacements in the HTTP payload
 }
}

 

相关的文档
 
下面有两篇关于iRule中调试日志和URI分析的文章: