HTML page
19.5. Повышение эффективности сценариев CGI
Проблема
От частых вызовов вашего сценария CGI снижается производительность сервера. Вы хотите уменьшить нагрузку, связанную с работой сценария.Решение
Используйте модуль mod_perl Web-сервера Apache и включите в файл httpd.conf следующую секцию:Alias /perl/ /real/path/to/perl/scripts/
PerlModule Apache::Registry PerlModule CGI PerlSendHeader On
Комментарий
Модуль mod_perl Web-сервера Apache позволяет писать код Perl, который может выполняться па любой стадии обработки запроса. Вы можете написать своп собственные процедуры регистрации и аутентификации, определить виртуальные хосты и их конфигурацию и написать собственные обработчики для некоторых типов запросов.Приведенный выше фрагмент сообщает, что URL, начинающиеся с /perl/, в действительности находятся в /real/path/to/perl/scripts и обрабатываются Apache:: Registry. В результате они будут выполняться в среде CGI. Строка PerlModule CGI выполняет предварительную загрузку модуля CGI, a PerlSendHandler On позволяет большинству сценариев CGI работать с mod_perl. /perl/ работает аналогично /cgi-bin/. Чтобы суффикс .perl являлся признаком сценариев CGI mod_perl, подобно тому, как суффикс .cgi является признаком обычных сценариев CGI, включите в конфигурационный файл Apache следующий фрагмент:
PerlHandler Apache::Registry
Options ExecCGI
Поскольку интерпретатор Perl, выполняющий сценарий CGI, не выгружается из памяти при завершении сценария (что обычно происходит, когда Web-сервер выполняет сценарий как отдельную программу), не следует полагаться на то, что при запуске программы глобальные переменные имеют неопределенные значения. Флаг -w и use strict проверяют многие недостатки в сценариях такого рода. Существуют и другие потенциальные ловушки - обращайтесь к странице руководства mod__perl_traps.
Не беспокойтесь о том, насколько снизится быстродействие Web-сервера от предварительной загрузки всех сценариев. Все равно когда-нибудь придется загружать их в память; желательно, чтобы это произошло до того, как Apache начнет плодить потомков. В этом случае каждый сценарий будет находиться в памяти в единственном экземпляре, поскольку в любой современной операционной системе потомки используют общие страницы памяти. Иначе говоря, предварительная загрузка только на первый взгляд увеличивает расходы памяти - на самом деле она их уменьшает!
По адресу http://www.perl.com/CPAN-local/modules/by-modules/Netscape/nsapi_ perl-0.24.tar.gz имеется интерфейс к серверу Netscape, который также повышает производительность за счет отказа от порождения новых процессов.
> Смотри также -------------------------------
Документация по модулям Bundle::Apache, Apache, Apache::Registry от CPAN; http://perl.apache.org/, http://perl.apache.org/faqa/, man-страницы mod_perl(3) и cgi_to_mod_perl( 1) (если есть).
19.6. Выполнение команд без обращений к командному интерпретатору
Проблема
Пользовательский ввод должен использоваться как часть команды, но вы не хотите, чтобы пользователь заставлял командный интерпретатор выполнять другие команды или обращаться к другим файлам. Если просто вызвать функцию system или '. . . ' с одним аргументом (командной строкой), то для выполнения может быть использован командный интерпретатор, а это небезопасно.Решение
В отличие от одноаргументной версии, списковый вариант функции system надежно защищен от обращений к командному интерпретатору. Если аргументы команды содержат пользовательский ввод от формы, никогда не используйте вызовы вида:system("command $input @files"); # НЕНАДЕЖНО
Воспользуйтесь следующей записью:
system("command", $input, (Sfiles); # НАДЕЖНЕЕ
Комментарий
Поскольку Perl разрабатывался как "язык-клей", в нем легко запустить другую программу - в некоторых ситуациях даже слишком легко. Если вы просто пытаетесь выполнить команду оболочки без сохранения ее вывода, вызвать system в многоаргументной версии достаточно просто. Но что делать, если вы используете команду в '. . . ' или она является аргументом функции open? Возникают серьезные трудности, поскольку эти конструкции в отличие. от system не позволяют передавать несколько аргументов. Возможное решение - вручную создавать процессы с помощью fork и ехес. Работы прибавится, но, по крайней мере, непредвиденные обращения к командному интерпретатору не будут портить вам настроение.Обратные апострофы используются в сценариях CGI лишь в том случае, если передаваемые аргументы генерируются внутри самой программы:
chomp($now = 'date');
Но если команда в обратных апострофах содержит пользовательский ввод - например:
@output = 'grep $input (nifties)';
приходится действовать намного осторожнее.
die "cannot fork: $!" unless defined ($pid = open(safe_kid, "|-"));
if ($pid == 0) {
exec('grep', $input, Ofiles) or die "can't exec grep: $!";
} else {
@output =
close SAFE_KID; # $? содержит информацию состояния
}
Такое решение работает, поскольку ехес, как и system, допускает форму вызова, свободную от обращений к командному интерпретатору. При передаче списка интерпретатор не используется, что исключает возможные побочные эффекты.
При выполнении команды функцией open также потребуется немного потрудиться. Начнем с открытия функцией open конвейера для чтения. Вместо ненадежного кода: open(KID_TO"READ, "$program $options @args |"); # НЕНАДЕЖНО используется более сложный, но безопасный код:
# Добавить обработку ошибок die "cannot fork: $!"
unless defined($pid = open(kid_to_read, "-!"));
if ($pid) { # Родитель while (
# Сделать что-то интересное
}
close(KID_TO_READ) or warn "kid exited $?";
} else { # Потомок
# Переконфигурировать, затем
exec($prograni, @options, @iargs) or die "can't exec program: $!";
}
Безопасный конвейерный вызов open существует и для записи. Непадежный вызов:
open(KID_TO_WRITE, "|$program $options @args");
# НЕНАДЕЖНО заменяется более сложным, но безопасным кодом:
$pid = open(kid_to_write, "|-");
die "cannot fork: $!" unless defined($pid = open(kid_to_write, "|-"));
$SIG{ALRM} = sub { die "whoops, $program pipe broke" };
if ($pid) { # Родитель
for (@data) { print KID_TO_WRITE $_ } close(KID_TO_WRITE) or warn "kid exited $?";
} else { # Потомок
# Переконфигурировать, затем
exec($program, @options, @args) or die "can't exec program: $!":
}
Там, где комментарий гласит "Переконфигурировать", предпринимаются дополнительные меры безопасности. Вы находитесь в порожденном процессе, и вносимые изменения не распространяются на родителя. Можно изменить переменные окружения, сбросить временный идентификатор пользователя или группы, сменить каталог или маску umask и т. д. Разумеется, все это не поможет в ситуации, когда вызов system запускает программу с другим идентификатором пользователя. Например, почтовая программа sendmail является setuid-программой, часто запускаемой из сценариев CGI. Вы должны хорошо понимать риск, связанный с запуском sendmail или любой другой setuid-программы.
> Смотри также --------------------------------
Описание функций system, exec и open в perlfunc{1}; perhec(1); рецепты 16.1-16.3.
19.7. Форматирование списков и таблиц средствами HTML
Проблема
Требуется сгенерировать несколько списков и таблиц. Нужны вспомогательные функции, которые бы упростили вашу работу.Решение
Модуль CGI содержит вспомогательные функции HTML, которые получают ссылку на массив и автоматически применяются к каждому элементу массива:print ol( li([ qw(red blue green)]) );
- red
- blue
- green @names = qw(larry Мое curly):
print ul( li({ -TYPE => "disc" }, \@names) );- Larry
- Moe
- Curly
Комментарий
Свойство дистрибутивности функций CGI.pm, генерирующих HTML-код, заметно упрощает процесс генерации списков и таблиц. При передаче простой строки эти функции просто выдают HTML-код для данной строки. Но при передаче ссылки на массив они применяются ко всем строкам. print li("alpha");use CGI qw(:standard :html3);
%hash = (
"Wisconsin" => [ "Superlor", "Lake Geneva", "Madison" ],
"Colorado" => [ "Denver", "Fort Collins", "Boulder" ],
"Texas" => [ "Piano", "Austin", "Fort Stockton" ],
"California" => [ "Sebastopol", "Santa Rosa", "Berkeley" ],
);
$\ = "\n":
print " TABLE> CAPTION>Cities I Have Known";
print Tr(th [qw(State Cities)]);
for $k (sort keys %hash) {
print Tr(th($k), td( [ sort @{$hash{$k}} ] ));
}
print " /TABLE>";
Генерируется следующий текст:
TABLE>
Те же результаты можно получить всего одной командой print, хотя это несколько сложнее, поскольку вам придется создавать неявный цикл с помощью тар. Следующая команда print выдает результат, идентичный приведенному выше:
print table
caption('Cities I have Known'),
Tr(th [qw(State Cities)]),
map { Tr(th($_), td( [ sort @{$hash{$_}} ] )) } sort keys %hash;
Эти функции особенно удобны при форматировании результатов запроса к базе данных, как показано в примере 19.3 (см. главу 14 "Базы данных"). Пример 19.3. salcheck
#!/usr/bin/perl
# salcheck - проверка жалованья
use DBI;
use CGI qw(:standard :html3);
$limit = param("limit");
print header(), start_html("Salary Query"), h1("Search"), start_form(),
p(Enter minimum salary", textfield("LIMIT")), submitO, end_form();
if (defined $limit) {
$dbh = dbi->connect("dbi:mysql:somedb:server.host.dom:3306",
"username", "password")
or die "Connecting: $DBI::errstr";
$sth = $dbh->prepare("SELECT name,salary FROM employees
WHERE salary > $limit")
or die "Preparing: ", $dbh->errstr;
$sth->execute
or die "Executing: ", $sth->errstr;
print h1("Results"), "
|