策略扩展 - 用例
某些客户应用程序的需求无法通过现有策略和表达式来满足。策略扩展功能使客户能够向其应用程序添加自定义功能以满足其需求。
以下用例说明了在 NetScaler 设备上使用策略扩展功能添加新功能的情况。
- 案例 1:自定义哈希
- 案例 2:折叠 URL 中的双斜杠
- 案例 3:合并标题
案例 1:自定义哈希
CUSTOM_HASH 函数提供了一种在发送给客户端的响应中插入任何类型的哈希值的机制。在此用例中,哈希函数用于计算重写 HTTP 请求的查询字符串的哈希值,并插入带有计算值的名为 CUSTOM_HASH 的 HTTP 标头。CUSTOM_HASH 函数实现了 DJB2 哈希算法。
CUSTOM_HASH 的用法示例:
> add rewrite action test_custom_hash insert_http_header "CUSTOM_HASH" "HTTP.REQ.URL.QUERY.CUSTOM_HASH"
<!--NeedCopy-->
CUSTOM_HASH () 的示例定义:
-- Extension function to compute custom hash on the text
-- Uses the djb2 string hash algorithm
function NSTEXT:CUSTOM_HASH() : NSTEXT
local hash = 5381
local len = string.len(self)
for i = 1, len do
hash = bit32.bxor((hash * 33), string.byte(self, i))
end
return tostring(hash)
end
<!--NeedCopy-->
以上样本的逐行描述:
function NSTEXT:CUSTOM_HASH() : NSTEXT
Defines the CUSTOM_HASH() function, with text input and a text return value.
local hash = 5381
local len = string.len(self)
Declares two local variables:
- hash. Accumulates the compute hash value and is seeded with the number 5381
- len. Sets to the length of the self input text string, using the built-in string.len() function.
for i = 1, len do
hash = bit32.bxor((hash * 33), string.byte(self, i))
end
Iterates through each byte of the input string and adds the byte to the hash. It uses the built-in string.byte() function to get the byte and the built-in bit32.bxor() function to compute the XOR of the existing hash value (multiplied by 33) and the byte.
return tostring(hash)
Calls the built-in tostring() function to convert the numeric hash value to a string and returns the string as the value of the function.
<!--NeedCopy-->
案例 2:折叠 URL 中的双斜杠
在 URL 中折叠双斜杠可以缩短网站呈现时间,因为浏览器解析单斜杠 URL 的效率更高。单斜杠 URL 也是为了保持与不接受双斜杠的应用程序的兼容性。策略扩展功能允许客户添加一项功能,将 URL 中的双斜杠替换为单斜杠。以下示例说明了添加的策略扩展函数,该函数可折叠 URL 中的双斜杠。
COLLAPSE_DOUBLE_SLASHES () 的示例定义:
-- Collapse double slashes in URL to a single slash and return the result
function NSTEXT:COLLAPSE_DOUBLE_SLASHES() : NSTEXT
local result = string.gsub(self, "//", "/")
return result
end
<!--NeedCopy-->
以上样本的逐行描述:
function NSTEXT:COLLAPSE_DOUBLE_SLASHES() : NSTEXT
Declares the COLLAPSE_DOUBLE_SLASHES() function with text input and return.
local result = string.gsub(self, "//", "/")
Declares a local variable named result and uses the built-in string.gsub() function to replace all double slashes with single slashes in the self input text.
The second parameter of string.gsub() is actually a regular expression pattern, although here a simple string is used for the pattern.
return result
Returns the resulting string.
<!--NeedCopy-->
案例 3:合并标题
某些客户应用程序无法处理请求中的多个标头。此外,解析具有相同标头值的重复标头,或者在请求中解析多个名称相同但值不同的标头,会消耗时间和网络资源。策略扩展功能允许客户添加一个函数,将这些标头合并为单个标头,其值与原始值相结合。例如,合并标题 H1 和 H2 的值。
原始请求:
GET /combine_headers HTTP/1.1
User-Agent: amigo unit test
Host: myhost
H2: h2val1
H1: abcd
Accept: \*/\*
H2: h2val2
Content-Length: 0
H2: h2val3
H1: 1234
<!--NeedCopy-->
修改后的请求:
GET /combine_headers HTTP/1.1
User-Agent: amigo unit test
Host: myhost
H2: h2val1, h2val2, h2val3
H1: abcd, 1234
Accept: \*/\*
Content-Length: 0
<!--NeedCopy-->
通常,这种类型的请求修改是使用重写功能完成的,使用策略表达式来描述要修改的请求部分(目标)和要执行的修改(字符串生成器表达式)。但是,策略表达式无法遍历任意数量的标头。
要解决这个问题,就需要扩大策略工具。为此,我们将定义一个名为 COMBINE_HEADERS 的扩展函数。使用此函数,我们可以设置以下重写操作:
> add rewrite action combine_headers_act replace 'HTTP.REQ.FULL_HEADER.AFTER_STR("HTTP/1.1rn")' 'HTTP.REQ.FULL_HEADER.AFTER_STR("HTTP/1.1rn").COMBINE_HEADERS'
这里,重写目标是 HTTP.REQ.FULL_HEADER.AFTER_STR(“HTTP/1.1rn”)。AFTER_STR(“HTTP/1.1rn”) 是必需的,因为 FULL_HEADER 包含 HTTP 请求的第一行(例如 GET /combine_headers HTTP/1.1)。
字符串生成器表达式是 HTTP.REQ.FULL_HEADER.AFTER_STR(“HTTP/1.1rn”).COMBINE_HEADERS,其中标头(减去第一行)被输入到 COMBINE_HEADERS 扩展函数,该函数合并并返回标头的值。
COMBINE_HEADERS () 的示例定义:
-- Extension function to combine multiple headers of the same name into one header.
function NSTEXT:COMBINE_HEADERS(): NSTEXT
local headers = {} -- headers
local combined_headers = {} -- headers with final combined values
-- Iterate over each header (format "name:valuer\r\n")
-- and build a list of values for each unique header name.
for name, value in string.gmatch(self, "([^:]+):([^\r\n]*)\r\n") do
if headers[name] then
local next_value_index = #(headers[name]) + 1
headers[name][next_value_index] = value
else
headers[name] = {name .. ":" .. value}
end
end
-- iterate over the headers and concat the values with separator ","
for name, values in pairs(headers) do
local next_header_index = #combined_headers + 1
combined_headers[next_header_index] = table.concat(values, ",")
end
-- Construct the result headers using table.concat()
local result_str = table.concat(combined_headers, "\r\n") .. "\r\n\r\n"
return result_str
end
<!--NeedCopy-->
以上样本的逐行描述:
function NSTEXT:COMBINE_HEADERS(): NSTEXT
Defines the COMBINE_HEADERS extension function, with the text input into the function from the policy expression and a text return type to the policy expression.
local headers = {} -- headers
local combined_headers = {} -- headers with final combined values
Declares local variables headers and combined_headers and initialize these variables to empty tables. headers will be a table of arrays of strings, where each array holds one or more values for a header. combined_headers will be an array of strings, where each array element is a header with its combined values.
for name, value in string.gmatch(self, "([^:]+):([^\r\n]*)\r\n") do
. . .
end
<!--NeedCopy-->
这个通用的 for 循环会解析输入中的每个标头。迭代器是内置的 string.gmatch () 函数。这个函数有两个参数:一个用于搜索的字符串和一个用于匹配字符串片段的模式。要搜索的字符串由隐式 self 参数提供,该参数是输入到函数的标头的文本。
该模式使用正则表达式(简称 regex)表示。此正则表达式匹配每个标头的标头名称和值,HTTP 标准将其定义为 *name*:值\ r\ n。正则表达式中的括号指定要提取的匹配部分,因此正则表达式示意图为 (匹配*名称):( 匹配值)\r\ n。**匹配名称模* 式需要匹配除冒号之外的所有字符。这是写的 [^:]+
。 [^:]
是除 :
和 +
为一次或多次重复之外的任何字符。同样, 匹配值 模式必须匹配除字符之外的任何字符 \r\n
,因此可以编写出来。 [^\r\n]*
[^\r\n]
匹配除 \r
\n
和之外的任何字符,重复次数 *
为零或以上。这就形成了完整的正则表达式 ([^:]+):([^\r\n]*)\r\n
。
for 语句使用多重赋值为 string.gmatch () 迭代器返回的两个匹配项设置名称和值。它们在 for 循环主体中被隐式声明为局部变量。
if headers[name] then
local next_value_index = #(headers[name]) + 1
headers[name][next_value_index] = value
else
headers[name] = {name .. ":" .. value}
end
<!--NeedCopy-->
for 循环中的这些语句将标头名称和值放入标头表中。第一次解析标头名称时(比如示例输入中的 H2: h2val1),名称中没有标题条目,标头 [name] 为 nil。
由于 nil 被视为 false,else 子句被执行。这将名称的标头条目设置为具有一个字符串值 名称:value 的数组。
注意: else 循环中的数组构造函数等同于{[1] = name .. “:” .. value},用于设置数组的第一个元素。)对于第一个 H2 标头,它设置标题 [“H2”] = {“H2:h2val1”}。
在标头的后续实例上(比如示例输入中的 H2: h2val2)。headers[name] 不是 nil,因此执行 then 子句。这将确定标题 [name] 的数组值中的下一个可用索引,并将标头值放入该索引中。对于第二个 H2 标头,它设置标头 [“H2”] = {“H2:h2val1”, “h2val2”}。
for name, values in pairs(headers) do
local next_header_index = #combined_headers + 1
combined_headers[next_header_index] = table.concat(values, ",")
end
<!--NeedCopy-->
在解析了原始标题并填充了标题表之后,此循环会构建 combined_headers 数组。它使用 pairs () 函数作为 for 循环迭代器。
每次调用 pairs () 都会返回标题表中下一个条目的名称和值。
下一行确定 combined_headers 数组中的下一个可用索引,下一行将该数组元素设置为组合标头。它使用内置的 table.concat () 函数,该函数将字符串数组和用作分隔符的字符串作为其参数,并返回一个由分隔符分隔的数组字符串的串联字符串。
例如,对于值 = {“H2:h2val1”, “h2val2”},,这会生成“H2:h2val1, h2val2”
local result_str = table.concat(combined_headers, "\r\n") .. "\r\n\r\n"
<!--NeedCopy-->
构建 combined_headers 数组后,它将元素连接成一个字符串,并添加一个用于终止 HTTP 标头的双\ r\ n。
return result_str
<!--NeedCopy-->
返回一个字符串作为 COBINE_HEST 扩展函数的结果。