Все задачи

Perl + Javascript = ?

25 Mar 2014

Программист разрабатывал веб-сервис, и столкнулся с тяжелым выбором: в какой момент валидировать формы, отправляемые на сервер?

Валидация на клиентской стороне (js в браузере) удобна тем, что без отправки данных на сервер можно показать пользователю, что с введенными значениями что-то не так.

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

Программировать одну и ту же валидацию дважды (на js для браузера и на Perl для сервера) – досадная трата времени, да и две отдельные проверки обязательно разойдутся со временем.

Программист решил изящно обойти эти противоречия, реализовав проверку один раз – на javascript, и используя ее и на клиентской, и на серверной стороне.

Для проверки концепции (вызов js-функций из perl-кода) программист набросал Mojo-приложение

metro.pl:

#!/usr/bin/env perl                                            
                                                               
use Mojolicious::Lite;                                         
use JavaScript::V8;                                            
use File::Slurp;                                               
                                                               
get '/' => sub {                                               
  my $self = shift;                                            
                                                               
  my $form = $self->req->params->to_hash;                      
                                                               
  my $result = metro_validate($form);                          
                                                               
  $self->render(text => $result);                              
};                                                             
                                                               
app->start;                                                    

sub metro_validate 
{   
    my ($form) = @_;
    # текст js-программы 
    my $js_text = read_file("metro_validation.js", binmode => ':utf8');
    #контекст выполнения js
    my $cx = JavaScript::V8::Context->new();
    # загружаем js-программу
    $cx->eval($js_text);
    # получаем ссылку на функцию валидации
    my $v = $cx->eval('metro_validate');

    return $v->($form->{length}, $form->{width}, $form->{height});
}

И js-ную функцию проверки

metro_validation.js:

function metro_validate(length, width, height){
    var sum = length + width + height;
    if (sum <= 150){
        return 'OK';
    } else {
        return 'Сумма измерений по длине, высоте и ширине не должна превышать 150 см!';
    }
}

И сделал тестовые запросы:

> ./metro.pl get '/?length=1&height=1&width=1'
> ./metro.pl get '/?length=1&height=1&width=5'

Проверка определенно что-то проверяла, но определенно не то, что хотел программист. Что же случилось?

Подсказка

Показать

> ./metro.pl get '/?length=1&height=1&width=1'
OK
> ./metro.pl get '/?length=1&height=1&width=5'
Сумма измерений по длине, высоте и ширине не должна превышать 150 см!

Подсказка-2

Показать

В Perl есть операция ., а в javascript – нет.

Подсказка-3

Показать

Зато в javascript строки конкатенируются с помощью +.

Разоблачение

Показать

Для perl’а почти нет разницы между числами и строками, можно пользоваться теми и другими, не задумываясь, когда происходят неявные преобразования. Для javascript’а разница есть. Например, операция + для чисел действует как сложение, а для строк – как конкатенация.

В нашем веб-приложении параметры в хеше $form – строки (неудивительно), и как строки они попадают в js-функцию metro_validate. В результате в переменной sum оказывается не сумма измерений, а их конкатенация, и результат сравнения получается совсем не тот, что ожидался.

Для решения проблемы можно было бы принудительно нумерифицировать параметры при передаче в js:

return $v->($form->{length}+0, $form->{width}+0, $form->{height}+0);