One of the often overlooked features of iRules is the ability to use dynamic variables in assignments.  This can make for some interesting iRules, but it can also get you into trouble at runtime when your variables are not defined in the current configuration.  This tech tip will illustrate how to take input from an HTTP request and use that as the criteria for which pool of servers the connection is sent to, all while maintaining runtime error checking using the TCL catch command.
 
iRules有一个常常被忽视的特点是能够在执行任务时使用动态变量,这可以有助于编写一些有趣的iRules ,但如果你在程序运行时没有在当前配置中定义其中一些变量,它也可以给你带来运行时麻烦。本章技术技巧将说明如何从HTTP请求中找出输入内容,并使用它做为向哪个pool发送数据的标准,同时,使用TCL语言的catch命令维护系统运行的错误的捕捉。
 
The "pool" command

The pool command is used to assign a connection to a specified pool name (and optionally a specific pool member).  The syntax for the pool command is:
Pool 命令
pool命令用于指定连接到一个指定的pool名字(和随意的一个特有的pool member)。
Pool命令的语法规则是:
pool <pool_name>
指定发送流量到pool。
pool <pool_name> ?member <addr> ?<port>??
指定直接发送流量到pool member。

很多时候,我们的iRules是这样写的(使用pool名字进行编码) 

when HTTP_REQUEST {
  switch -glob [HTTP::uri] {
    "*.gif" -
    "*.jpg" -
    "*.png" {
      pool images_pool
    }
    "*.asp" {
      pool ms_app_pool
    }
    "*.jsp" {
      pool java_app_pool
    }
  }
}

 

When this iRule is created, the pool commands are syntax checked and validated and since the pool name arguments are literals, the save logic will validate whether the given pools exist in the configuration.  If they do not exist, a save error will occur and you will have to configure the pools before you save your iRule.  In this case, no runtime exception will occur (unless you remove your pools out from under your iRule, that is).
 
当iRules建立时,pool命令会被系统进行语法检查并且确认,pool是照字面意思命名,所以保存逻辑将会确认特定的pool是否存在于配置中。如果不存在,将会出现一个保存错误,因此你在保存iRule前必须先配置pools。在这种情况下,没有运行时执行异常会发生(除非你必须把创建好的iRule里关联的Pool 移除)。
 
But, let's say you want to build a more dynamic solution that can expand over time.  For this example, let's say that you want to allow your client to determine which pool of servers it is sent to.  This could be the server specifying a pool name in an HTTP cookie, or simply appending it as a GET parameter on the URI.  We'll use the later scenario for this example.
但是,假设您想要建立可以随着时间推移扩展的一种更加动态的解决方案。在此示例中,假设您想要客户端来确定将流量发送到那些Pool. 这可能是服务器指定一个在HTTP cookie中的Pool名称 ,或者是一个简单的在URI上附加的GET参数。我们在这个将使用后一种场景举例。
For this example, we'll define the following GET parameters that can control pool direction.
pool=name

So an example URI could be
http://somedomain.com/somefile.ext?param1=val1&pool=pool_name&pararm2=val2...

We will interrogate the URI's GET parameters in search of the "pool" parameter, extract the pool_name value and use that variable as the argument in the pool command.  For newer versions of BIG-IP, one could use the URI::query command to extract values, but this implementation should work all the way back to BIG-IP v9.0.
在这个例子里,我们将定义以下的GET参数来控制Pool分配。
pool=name

这样一个完整的URI例子可能是这样的:
http://somedomain.com/somefile.ext?param1=val1&pool=pool_name&pararm2=val2...

我们将询问URI的GET参数来寻找“pool”参数,提取出Pool名称,并使用这个变量作为pool命令的参数。在BIG-IP新的版本里,我们可以使用的URI::query 查询命令来提取参数, 但这一功能应当返回去在BIGIP V9.0所有的版本里实现。

 

when HTTP_REQUEST {
  set namevals [split [HTTP::query] "&"]
  set pool_name ""
  for {set i 0} {$i < [llength $namevals]} {incr i} {
    set params [split [lindex $namevals $i] "="]
    set name [lindex $params 0]
    set val [lindex $params 1]
    switch $name {
      "pool" {
         set pool_name $val
      }
    }
  }
  if { "" ne $pool_name } {
    pool $pool_name
  }
}

 

What's wrong with this implementation?  Nothing if your configuration has the given value in the $pool_name variable.  What happens if that value doesn't exist in the configuration?  The answer is that you will get a runtime error and the given connection will be broken. This is not an ideal solution and the simple use of the catch command can avoid runtime connection termination and allow the request to continue on through to a default pool of servers.
这样部署会出现什么问题?如果在配置中已经包含有$ pool_name变量所代表的Pool,则不会有任何错误。那如果在配置中不存在这个Pool会发生什么?答案是,你将会得到一个运行时错误并且当前的连接将会被中断。这不是一个理想的解决方案,简单使用的catch命令可避免运行时连接中断,并允许请求持续进行发送到默认pool。
 
 
The syntax for the catch command is as follows:
 
Catch命令的语法如下:


catch script ?varName?

The catch command may be used to prevent errors from aborting command interpretation. The catch command calls the Tcl interpreter recursively to execute script, and always returns without raising an error, regardless of any errors that might occur while executing script.
 
Cache 命令可用于避免在放弃命令解释时的错误。Cache命令通过递归方式来调用TCL的编译器来执行脚本, 并且通常在返回时不产生错误,无论在执行脚本时会发生任何错误。
 
If script raises an error, catch will return a non-zero integer value corresponding to the exceptional return code returned by evaluation of script. Tcl defines the normal return code from script evaluation to be zero (0), or TCL_OK. Tcl also defines four exceptional return codes: 1 (TCL_ERROR), 2 (TCL_RETURN), 3 (TCL_BREAK), and 4 (TCL_CONTINUE). Errors during evaluation of a script are indicated by a return code of TCL_ERROR. The other exceptional return codes are returned by the return, break, and continue commands and in other special situations as documented. Tcl packages can define new commands that return other integer values as return codes as well, and scripts that make use of the return -code command can also have return codes other than the five defined by Tcl.
如果脚本执行出现了一个错误,Catech捡回返回一个非0的整数值来对应脚本执行的异常返回代码。TCL 定义了脚本正常执行的返回代码为零(0), 或者TCL_OK。TCL也定义了四个异常返回代码:1(TCL_ERROR), 2 (TCL_RETURN), 3 (TCL_BREAK)和4 (TCL_CONTINUE). 脚本执行错误对应返回代码TCL_ERROR。其他的异常返回代码返回return, break和continue命令, 同时定义一些其他的特殊情况。 TCL包可以同时定义新的命令和其他整数值来作为返回代码,并且脚本执行的返回代码命令和返回代码可以不是在标准的TCL定义的五种类型中。

If the varName argument is given, then the variable it names is set to the result of the script evaluation. When the return code from the script is 1 (TCL_ERROR), the value stored in varName is an error message. When the return code from the script is 0 (TCL_OK), the value stored in resultVarName is the value returned from script.
如果给出了varName参数,那么它值将会是脚本执行的结果值。当从脚本返回代码是1 ( TCL_ERROR ) ,则存储在varName里的值会是一个错误信息。当从脚本返回的代码是0 ( TCL_OK ) ,则存放在resultVarNamede 值就代表着脚本的返回值

Solution
解决方案
The following iRule is the same as above, except that it makes use of exception handling in the dynamic pool assignment.

下面的一个iRule和上面的iRule实现同样的功能,除了它使用了在动态Pool指定时的异常处理机制。

 

when HTTP_REQUEST {
  set namevals [split [HTTP::query] "&"]
  set pool_name ""
  for {set i 0} {$i < [llength $namevals]} {incr i} {
    set params [split [lindex $namevals $i] "="]
    set name [lindex $params 0]
    set val [lindex $params 1]
    switch $name {
      "pool" {
         set pool_name $val
      }
    }
  }
  if { "" ne $pool_name } {
    if { [catch { pool $pool_name } ] } {
      log local0. "ERROR: Attempting to assign traffic to non-existant pool $pool_name"
      pool default_pool
    }
  }
}

 

Now, the control of which pool the connection is directed to is completely in the hands of the request URI.  And in those rare situations where your app logic changes before the network configuration does, the connection will fallback to a default pool of servers, while logging the issue to the system log for later examination.
现在,控制连接被定向到那个Pool完全决定于请求的URI。只有在极少的情况下,当你的应用逻辑改变在网络配置改变之前发生,连接将会被fallback到default pool中的服务器上,同时,可以记录下中错误到系统log中以供后续的分析。
Conclusion
结论

The ability to use dynamic variables greatly enhances the flexibility of what you can do with iRule commands.  The catch command should be used around any iRule command that makes use of dynamic variables to catch any runtime errors that occur.  In fact, if you so desire, you and nest catch commands for a multi-layered exception handling solution, but I'll leave that one for a later article.
使用动态变量,可以极大地提高你在使用iRule命令时的灵活性,Catch 命令可以被用于包含任何使用动态变量的iRule命令并且捕获到运行时的错误。事实上,如果你需要,你可以嵌套使用Catch命令来实现一个多层的异常处理解决方案,但把这个解决方案留到了将来的文章中。