Certaines applications client ne peuvent pas gérer plusieurs en-têtes dans une demande. En outre, l'analyse des en-têtes en double avec les mêmes valeurs d'en-tête, ou de plusieurs en-têtes avec le même nom mais des valeurs différentes dans une requête, consomme du temps et des ressources réseau. La fonction d'extension de stratégie permet aux clients d'ajouter une fonction pour combiner ces en-têtes en en-têtes simples avec une valeur combinant les valeurs d'origine. Par exemple, en combinant les valeurs des en-têtes H1 et H2.
Demande originale :
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
Demande modifiée :
GET /combine_headers HTTP/1.1
User-Agent: amigo unit test
Host: myhost
H2: h2val1, h2val2, h2val3
H1: abcd, 1234
Accept: \*/\*
Content-Length: 0
En général, ce type de modification de demande est effectué à l'aide de la fonction Réécriture, à l'aide d'expressions de stratégie pour délimiter la partie de la demande à modifier (la cible) et la modification à effectuer (l'expression du générateur de chaîne). Cependant, les expressions de stratégie n'ont pas la possibilité d'itérer sur un nombre arbitraire d'en-têtes.
La solution à ce problème nécessite une extension de la fonction de stratégie. Pour ce faire, nous allons définir une fonction d'extension, appelée COMBINE_HEADERS. Avec cette fonction, nous pouvons configurer l'action de réécriture suivante :
> 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'
Ici, la cible de réécriture est HTTP.REQ.FULL_HEADER.AFTER_STR("HTTP/1.1rn"). AFTER_STR("HTTP/1.1rn") est requis car FULL_HEADER inclut la première ligne de la requête HTTP (par exemple GET /combine_headers HTTP/1.1).
L'expression du générateur de chaîne est HTTP.REQ.FULL_HEADER.AFTER_STR("HTTP/1.1rn").COMBINE_HEADERS, où les en-têtes (moins la première ligne) sont introduits dans la fonction d'extension COMBINE_HEADERS, qui combine et renvoie les valeurs des en-têtes.
Exemple de définition de 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
Description ligne par ligne de l'échantillon ci-dessus :
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
Cette boucle générique pour analyse chaque en-tête dans l'entrée. L'itérateur est la fonction string.gmatch() intégrée. Cette fonction prend deux paramètres : une chaîne à rechercher et un modèle à utiliser pour faire correspondre des morceaux de la chaîne. La chaîne à rechercher est fournie par le paramètre self implicite, qui est le texte des en-têtes entrés dans la fonction.
Le motif est exprimé en utilisant une expression régulière (regex pour abrégé). Cette regex correspond au nom et à la valeur d'en-tête de chaque en-tête, que la norme HTTP définit comme
nom :
valeur\r\n. Les parenthèses dans la regex spécifient les parties correspondantes à extraire, de sorte que le schéma regex est (
match-name) : (
match-value)\r\n. Le modèle de
nom de correspondance doit correspondre à tous les caractères sauf les deux-points. Ceci est écrit
+ (
n'importe quel caractère sauf : et + est une ou plusieurs répétitions). De même, le modèle
de valeur de correspondance doit correspondre à tous les caractères sauf le \ r \ n, donc il est écrit
( correspond à n'importe quel caractère sauf \ r et \ n et correspond à zéro ou plus de répétitions). Cela rend la regex complète (
+):(
*)\r\n.
L'instruction for utilise une affectation multiple pour définir le nom et la valeur des deux correspondances renvoyées par l'itérateur string.gmatch (). Ceux-ci sont implicitement déclarés comme variables locales dans le corps de la boucle for.
if headers[name] then
local next_value_index = #(headers[name]) + 1
headers[name][next_value_index] = value
else
headers[name] = {name .. ":" .. value}
end
Ces instructions dans la boucle for placent les noms et les valeurs d'en-tête dans la table des en-têtes. La première fois qu'un nom d'en-tête est analysé (par exemple H2 : h2val1 dans l'exemple d'entrée), il n'y a pas d'entrée d'en-tête pour le nom et le
des en-têtes est nul.
Puisque nil est traité comme false, la clause else est exécutée. Cela définit l'entrée des en-têtes pour name à un tableau avec une valeur de chaîne name :value.
Remarque : Le constructeur de tableau dans la boucle else est équivalent à {
= name.. « : ».. value}, qui définit le premier élément du tableau.) Pour le premier en-tête H2, il définit les en-têtes
= {" H2:H2Val1"}.
Sur les instances suivantes d'un en-tête, (disons, H2: h2val2 dans l'exemple d'entrée). en-têtes n'
est pas nul, donc la clause then est exécutée. Cela détermine l'index disponible suivant dans la valeur du tableau pour les en-têtes
et place la valeur d'en-tête dans cet index. Pour le deuxième en-tête H2, il définit les en-têtes
= {"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
Une fois les en-têtes d'origine analysés et la table d'en-têtes remplie, cette boucle construit le tableau combined_headers. Il utilise la fonction pairs() comme itérateur de boucle.
Chaque appel à pairs() renvoie le nom et la valeur de l'entrée suivante dans la table des en-têtes.
La ligne suivante détermine l'index disponible suivant dans le tableau combined_headers, et la ligne suivante définit cet élément de tableau à l'en-tête combiné. Il utilise la fonction table.concat() intégrée, qui prend comme arguments un tableau de chaînes et une chaîne à utiliser comme séparateur, et renvoie une chaîne qui est la concaténation des chaînes de tableau, séparées par le séparateur.
Par exemple, pour les valeurs = {"H2:h2val1", "h2val2"}, cela produit "H2:h2val1, h2val2"
local result_str = table.concat(combined_headers, "\r\n") .. "\r\n\r\n"
Après la construction du tableau combined_headers, il concatène les éléments en une chaîne et ajoute un double rn qui termine les en-têtes HTTP.
return result_str
Renvoie une chaîne comme résultat de la fonction d'extension COMBINE_HEADERS.