Functions

Functions are a basic building block of programming - they are a convenient and powerful way to group statements that perform a task. They are the interface between the NetScaler appliance and extension code. For policies, you define policy extension functions. For protocols, you implement callback functions for the protocol behaviors. Functions consist of function definitions that specify what values are passed into and out of the function and what statements are executed for the function, and function calls, which execute functions with specific input data and get results from the function.

Protocol behavior callback functions

The TCP client behavior consists of a callback function (on_data) that processes TCP client data stream events. To implement Message Based Load Balancing (MBLB) for a TCP based protocol, you can add code for this callback function to process the TCP data stream from the client and parse the byte stream into protocol messages.

The callback functions in a behavior are called with a context, which is the processing module state. The context is the instance of the processing module. For example, the TCP client behavior callbacks are called with different contexts for different client TCP connections.

In addition to the context, the behavior callbacks can have other arguments. Usually the rest of the arguments are passed as payload, which is the collection of all the arguments. So, the programmable processing module instances can be seen as a combination of instance state plus event callback functions, that is, the context plus behavior. And the traffic flows through the pipeline as event payload.

Prototype of TCP client callback function:

                Function                client on_data (ctxt, payload)

                                                //.code

                end

where,

  • ctxt - TCP client processing context
  • payload – event payload
    • payload.data - TCP data received, available as a stream of bytes

Policy extension functions

Since the NetScsaler policy expression language is strongly typed, the definition of an extension function must specify the types of its inputs and its return value. The Lua function definition has been extended to include these types:

function self-type:function-name(parameter1: parameter1-type, etc.): return-type
     statements
end

where,

the types are NSTEXT, NSNUM, NSBOOL, or NSDOUBLE.

self-type is the type of the implicit self parameter that is passed into the function. When the extension function is used in an NetScaler policy expression, this is the value generated by the expression to the left of the function. Another way to view this is that the function extends that type in the NetScaler policy language.

The parameter-types are the types of each parameter specified in the extension function call in the policy expression. An extension function can have zero or more parameters.

return-type is the type of the value returned by the extension function call. It will be the input to the part of the policy expression, if any, to the right of the function, or else is the value of the expression result.

Example:

function NSTEXT:COMBINE_HEADERS() : NSTEXT

Use of the extension function in a policy expresssion:

HTTP.REQ.FULL_HEADER.AFTER_STR("HTTP/1.1rn").COMBINE_HEADERS()

Here the self parameter is the result of HTTP.REQ.FULL_HEADER.AFTER_STR(“HTTP/1.1rn”), which is a text value. The result of the COMBINE_HEADERS() call is text, and since there is nothing to the right of this call, the result of the entire expression is text.

Local function definition

Besides extension functions, no global functions can be defined in an extension file. But local functions can be defined within extension functions using the normal Lua function statement. This declares the name of the function and the names of its parameters (also known as arguments), and like all declarations in Lua, does not specify any types. The syntax for this is:

local function function-name(parameter1-name, parameter2-name, etc.)
     statements
end

The function and parameter names are all identifiers. (The function name is actually a variable and the function statement is shorthand for local function-name = function(parameter1, etc.), but you don’t have to understand this subtlety to use functions.)

Note that etc. is used here for continuation of the pattern of parameter names instead of the usual … . This is because … itself actually means a variable parameter list, which will not be discussed here.

Function body and return

The block of statements between the function and end statements is the function body. In the function body, the function parameters act like local variables, with values supplied by the function calls, as described previously.

The return statement supplies values to be returned to the caller of the function. It must appear at the end of a block (in a function, if then, for loop, and so on; It can be in its own block do return … end). It may specify no, one, or more than one return values:

return -- returns nil
return expression -- one return value
return expression1, expression2, ... -- multiple return values

Examples:

local function fsum(a)
     local sum = 0
     for i = 1, #a do
          sum = sum + a[i]
     end
     return sum
end

local function fsum_and_average(a)
     local sum = 0
     for i = 1, #a do
          sum = sum + a[i]
     end
     return sum, sum/#a
end

Function calls

A function call executes the body of a function, supplying values for its parameters, and receiving results. The syntax for a function call is function-name(expression1, expression2, etc.), where the function parameters are set to the corresponding expressions. The number of expressions and parameters need not be the same. If there are fewer expressions than parameters, the remaining parameters are set to nil. So you can make one or more parameters at the end of the call optional, and your function can check if they are specified by checking if they are not nil. A common way to do this is with the or operation:

function f(p1, p2) -- p2 is optional
     p2 = p2 or 0 -- if p2 is nil, set to a default of 0
     . . .
end

If there are more expressions than parameters, the remaining expression values are ignored.

As noted previously, functions can return multiple values. These returns can be used in a multiple assignment statement. Example:

local my_array = {1, 2, 3, 4}
local my_sum, my_ave = sum_and_average(my_array)

Iterator functions and generic for loops

Now that we have introduced functions, we can talk about generic for loops. The syntax for the generic for loop (with one variable) is:

for variable in iterator(parameter1, parameter2, etc.) do
     statements in the for loop body
end

where iterator() is a function with zero or more parameters that provides a value for variable on each iteration of the loop body. The iterator function keeps track of where it is in the iteration using a technique called closure, which you don’t have to worrry about here. It signals the end of the iteration by returning nil. Iterator functions can return more than one value, for use in a multiple assignment.

Writing an iterator function is beyond the scope of this paper, but there are a number of useful built-in iterators that illustrate the concept. One is the pairs() iterator, which iterates through the entries in a table and returns two values, the key and the value of the next entry.

Example:

local t = {k1 = "v1", k2 = "v2", k3 = "v3"}
local a = {} -- array to accumulate key-value pairs
local n = 0 -- number of key-value pairs
for key, value in pairs(t) do
     n = n + 1
     a[n] = key .. " = " .. value -- add key-value pair to the array
end
local s = table.concat(a, "; ") -- concatenate all key-value pairs into one string

Another useful iterator is the string.gmatch() function, which will be used in the following COMBINE_HEADERS() example.