Назад | Перейти на главную страницу

Varnish VCL - Оценка регулярных выражений

Последние несколько дней я боролся с этой проблемой:

По сути, я хочу отправить клиентскому браузеру файл cookie в форме foo[sha1oftheurl]=[randomvalue] тогда и только тогда, когда cookie еще не установлен.

например Если клиентский браузер запрашивает "/page.html", ответ HTTP будет таким: resp.http.Set-Cookie = "foo4c9ae249e9e061dd6e30893e03dc10a58cc40ee6=ABCD;"

тогда, если тот же клиент запросит "/index.html", ответ HTTP будет содержать заголовок: resp.http.Set-Cookie = "foo14fe4559026d4c5b5eb530ee70300c52d99e70d7=QWERTY;"

В итоге в клиентском браузере будет 2 файла cookie: foo4c9ae249e9e061dd6e30893e03dc10a58cc40ee6=ABCD foo14fe4559026d4c5b5eb530ee70300c52d99e70d7=QWERTY

Это не сложно само по себе. Следующий код делает это:

import digest;
import random; ##This vmod does not exist, it's just for the example.


sub vcl_recv()
{
    ## We compute the sha1 of the requested URL and store it in req.http.Url-Sha1
    set req.http.Url-Sha1 = digest.hash_sha1(req.url);
    set req.http.random-value = random.get_rand();
}

sub vcl_deliver()
{
    ## We create a cookie on the client browser by creating a "Set-Cookie" header
    ## In our case the cookie we create is of the form foo[sha1]=[randomvalue]
    ## e.g for a URL "/page.html" the cookie will be foo4c9ae249e9e061dd6e30893e03dc10a58cc40ee6=[randomvalue]
    set resp.http.Set-Cookie = {""} + resp.http.Set-Cookie + "foo"+req.http.Url-Sha1+"="+req.http.random-value;
}

Однако этот код не учитывает случай, когда Cookie уже существует. Мне нужно проверить, что Cookie не существует, прежде чем генерировать случайное значение. Итак, я подумал об этом коде:

import digest;
    import random;


sub vcl_recv()
{
    ## We compute the sha1 of the requested URL and store it in req.http.Url-Sha1
    set req.http.Url-Sha1 = digest.hash_sha1(req.url);
    set req.http.random-value = random.get_rand();

    set req.http.regex = "abtest"+req.http.Url-Sha1;

    if(!req.http.Cookie ~ req.http.regex)
    {
        set req.http.random-value = random.get_rand();
    }
}

Проблема в том, что Varnish не вычисляет регулярное выражение во время выполнения. Что приводит к этой ошибке, когда я пытаюсь скомпилировать:

Message from VCC-compiler:
Expected CSTR got 'req.http.regex'
(program line 940), at
('input' Line 42 Pos 31)
        if(req.http.Cookie !~ req.http.regex) {
------------------------------##############---

Running VCC-compiler failed, exit 1

VCL compilation failed

Можно предложить решить мою проблему, сопоставив «abtest» часть файла cookie или даже «abtest [a-fA-F0-9] {40}»:

if(!req.http.Cookie ~ "abtest[a-fA-F0-9]{40}")
{
    set req.http.random-value = random.get_rand();
}

Но этот код соответствует любому cookie, начинающемуся с abtest и содержащему шестнадцатеричную строку из 40 символов. Это означает, что если клиент сначала запрашивает «/page.html», а затем «/index.html», условие будет оцениваться как истинное, даже если файл cookie для «/index.html» не был установлен.

Я нашел в отчете об ошибке phk или у кого-то еще, что вычисление регулярных выражений было чрезвычайно дорогостоящим, поэтому они оцениваются во время компиляции. Учитывая это, я считаю, что нет способа достичь того, чего я хочу, так, как я пытался.

Есть ли способ решить эту проблему, кроме написания vmod?

Спасибо за вашу помощь!

-Hugues

Есть уловка!

Измените условие

if(!req.http.Cookie ~ req.http.regex)

кому:

if(!req.http.Cookie ~ {"" + req.http.regex + ""})

Это переключает его на CSTR.

Вот ответ, который я получил из списка рассылки Varnish:

TL; DR; Невозможно без vmod или встроенного C.

Все, что вы сказали, правда. Без встроенного C или vmod единственной известной мне альтернативой для этого было бы динамическое создание частей вашей конфигурации, которые зависят от sha1 URL-адреса.

Например, вы можете использовать что-то похожее на следующее:

#!/usr/bin/perl -w

use strict;
use Digest::SHA qw(sha1_hex);

my @files = qw!/index.html /homepage.html!;

my $output;
my $seen = 0;
foreach(@files){
  if($seen++){
    $output .= "else";
  }
  $output .= "if(req.url == \"$_\"){\n";
  $output .= "  set resp.http.Set-Cookie = \"foo" . sha1_hex($_) . "=\" + random.get_rand();\n";
  $output .= "}\n"
}

print $output;

Очевидно, изменено по своему вкусу, что дает следующий результат:

if(req.url == "/index.html"){
  set resp.http.Set-Cookie = "foo14fe4559026d4c5b5eb530ee70300c52d99e70d7=" + random.get_rand();
}
elseif(req.url == "/homepage.html"){
  set resp.http.Set-Cookie = "foo6593125bb8fade312b1081d4ee1998f316aa4081=" + random.get_rand();
}

Это имеет то преимущество, что все, кроме случайного числа, вычисляется заранее, но имеет недостаток, заключающийся в необходимости использования в этом случае другого скрипта для поддержки списка доступных файлов для генерации их хэшей.

Честно говоря, не уверен, поможет это или нет, но, возможно, это даст вам другие идеи для изучения.

~ Пол