Программист разрабатывал веб-сервис, и столкнулся с тяжелым выбором: в какой момент валидировать формы, отправляемые на сервер?
Валидация на клиентской стороне (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 см!
В Perl есть операция .
, а в javascript – нет.
Зато в javascript строки конкатенируются с помощью +
.
Для perl’а почти нет разницы между числами и строками, можно пользоваться теми и другими,
не задумываясь, когда происходят неявные преобразования.
Для javascript’а разница есть. Например, операция +
для чисел действует как сложение,
а для строк – как конкатенация.
В нашем веб-приложении параметры в хеше $form
– строки (неудивительно),
и как строки они попадают в js-функцию metro_validate
.
В результате в переменной sum
оказывается не сумма измерений, а их конкатенация,
и результат сравнения получается совсем не тот, что ожидался.
Для решения проблемы можно было бы принудительно нумерифицировать параметры при передаче в js:
return $v->($form->{length}+0, $form->{width}+0, $form->{height}+0);