ProWeb studio. Студия Web-дизайна, хостинга и т.д.

  Главная
  Домены
  Хостинг


  Документация
  Статьи

      Главная
      Web-Дизайн
      Web-Программир.
      Программирование
      Железо
      Сети
      Заработок
      Разное



  WebFTP клиент
  Каталог ссылок

Реклама

Платежная система RUpay - E-Gold, WebMoney, MoneyBookers, PayCash

  Самые низкие цены!

 



Реклама:

Статья

Клиентов - 1544       Телефоны Отправить SMS E-Mails msn контакт support@pwstudio.ru

Борьба с 500-й Ошибкой закончилась ее полной капитуляцией. Для начинающих любителей Perl.



Автор: Дмитрий Котеров
Сайт: http://dklab.ru/
Дата добавления: 2005-02-09
Выплачено автору: $0.05
Просмотров: 2215



Борьба с 500-й Ошибкой закончилась  ее полной капитуляцией, или почему я так люблю связывать потоки

[21 августа 2001 г.]

Вы когда-нибудь пробовали унять головную боль без таблетки?.. Тогда вы должны по достоинству оценить проблему, которая возникает при программировании на «чистом» Perl. Ничто так не отпугивает человека, начинающего изучать Perl, как постоянные надоедливые сообщения Apache о 500-й ошибке, как только человек допускает малейшую неточность в своем скрипте.
 

Вот что говорят программисты, всю жизнь использующие Perl для CGI-скриптов: «500-я ошибка?.. Ну и что? Я лично всегда держу в соседнем окне терминала запущенную программу Unix tail -f имя_лога_Apache и чувствую себя прекрасно — туда выводятся все сообщения об ошибках».

Да, есть такой способ, но если мы отлаживаем скрипты под Windows, это не так уж и удобно. В самом деле, приходится все время вертеть головой: редактор — браузер — окно ошибок. Но вспомним, как поступает PHP, когда в скрипте обнаруживается ошибка? Он просто выводит ее текст в браузер. И это удобно. Бесспорно удобно — не нужно дополнительное окно, а также исчезает постоянная головная боль с 500-й ошибкой.
 

В этом месте любители PHP обычно злорадствуют и потирают верхние конечности, а знатоки Perl — беспомощно разводят руками, делая вид, что ничего особенного не произошло.

Но сейчас мы сделаем так, чтобы и в Perl сообщение об ошибках выводилось в браузер, а не в логи. Вначале разберемся, почему вообще возникает сообщение о 500-й ошибке. А дело все в стандарте на протокол HTTP. Он требует, чтобы перед тем, как браузер получит тело документа-ответа, ему прислали так называемый заголовок типа документа. Например:

#!/usr/bin/perl -w
# Обратите внимание на эту строчку!
print "Content-type: text/html\n\n";
# Дальше выводим тело документа.
print "It works!";

Попробуйте убрать строку, которая выводит заголовок Content-type, и 500-я ошибка вам обеспечена. Таким образом, мы должны всегда выводить заголовок типа документа перед любым текстом. Этого можно достичь, например, так:

#!/usr/bin/perl -w
# Обратите внимание на эту строчку!
BEGIN { print "Content-type: text/html\n\n"; }
# Дальше выводим тело документа.
print "It works!";

Теперь 500-я ошибка никогда не появится, зато в случае синтаксической ошибки в скрипте перед пользователем предстанет пустая страница, что еще хуже. Кроме того, такой подход отвратителен: выводя первой строкой скрипта заголовок Content-type, мы тем самым лишаемся права выводить какие-нибудь другие заголовки в браузер — в том числе, устанавливать Cookies. Но как же быть? Ведь нам, с одной стороны, нужно сначала запустить скрипт, перехватить все сообщения об ошибках и вывести тело документа (в котором также могут выводиться и некоторые заголовки). С другой — в самом начале выводить в браузер Content-type. Головоломка, однако.

Решение лишь одно. Придется буферизовать весь вывод скрипта (записывать его в переменную-буфер). Затем, в самом конце, когда скрипт уже лежит на смертном одре, еле дышит и готов завершиться, выводить по порядку: накопленные заголовки; содержимое буфера; наконец, сообщения об ошибках.
 

Слава всевышнему, Perl имеет для такого «финта ушами» все средства.

Сначала о том, как накапливать сообщения об ошибках. Для этого нужно присвоить адрес функций-обработчиков элементам $SIG{__WARN__} и $SIG{__DIE__} (для перехвата предупреждений и ошибок соответственно). Как только произойдет недоразумение, Perl вызовет тот или иной обработчик, который должен молча отработать и добавить в массив ошибок @Errors новое сообщение. В браузер при этом ничего не выводится.

# массив ошибок
@Errors=();
# "глушим" выходной поток STDERR, чтобы не # засорять файлы журнала сервера
open(STDERR,">/dev/null") or open(STDERR,">nul") or die "Can't find null device!";
# устанавливаем обработчики
$SIG{__WARN__}=sub { push @Errors, "Warning: ".shift) };
$SIG{__DIE__} =sub { push @Errors, "Fatal: ".shift) };

С ошибками вроде бы разобрались. Теперь о том, как накапливать заголовки. В PHP скрипт может в процессе своей работы вызвать функцию Header() и передать ей строку — заголовок, который отправится серверу перед телом документа. Поступим так и на Perl. В нашем Perl-скрипте функция Header() будет просто добавлять указанный заголовок в массив заголовков @Headers, ничего при этом не выводя.

# массив заголовков
@Headers=();
# void Header(string $head)
sub Header
{ push(@Headers,@_);
return 1;
}

И самое сложное — буферизация вывода. Perl — настолько могучий язык, что в нем имеется возможность перехватить обращение к той или иной переменной (на чтение или запись) специальной функцией-обработчиком. Процесс назначения переменной обработчиков называется «связыванием». Известно, что вывод происходит при помощи print, а она неявно использует файловую переменную STDOUT, связанную по умолчанию с браузером пользователя. Нам нужно всего лишь перехватить вывод для STDOUT.

Вот что у нас получится в результате.

#!/usr/bin/perl -w
# Все функции-обработчики имеют фиксированные имена # и должны располагаться в одном пакете. Так что
# временно переключаемся на пакет Output.
{{{
package Output;
# буфер для накопления вывода
$Buf='';
# массив ошибок
@Errors=();
# массив заголовков
@Headers=();
# "глушим" выходной поток STDERR, # чтобы не засорять файлы журнала
open(STDERR,">/dev/null") or open(STDERR,">nul") or die "Can't find null device!";
# устанавливаем обработчики
$SIG{__WARN__}=sub { push @Errors, "Warning: ".shift };
$SIG{__DIE__} =sub { push @Errors, "Fatal: ".shift };
# void Header(string $head)
sub main::Header
{ push(@Headers,@_);
return 1;
}
# функции-обработчики
sub TIEHANDLE { return bless({}); }
sub PRINT { shift; $Buf.=join('',@_); return 1; }
sub BINMODE { binmode(THE_REAL_STDOUT); }
# сохраняем статус "старого" STDOUT
*THE_REAL_STDOUT=*STDOUT;
# связываем STDOUT с обработчиками
tie(*STDOUT,'Output');
# функция-финализатор: гарантированно # вызывается перед завершением скрипта.
END {
untie *STDOUT;
# выводим заголовки
print join "\n",
(@Headers,"Content-type: text\html","") if @Headers;
# выводим документ
print "\n",$Buf;
# выводим сообщения об ошибках
print "\n<p>".join "<br>\n", map { s{^\w+:}{<b>$&</b>}s; $_ } @Errors if @Errors;
}
}}}
### ### Далее идет код тестового скрипта, который
### иллюстрирует работу всего этого механизма.
### print $a; # предупреждение
Header("X-Powered-by: test"); # заголовок
print __PACKAGE__; # тело

Код может показаться довольно внушительным, но это только кажется: нужно просто поместить его в модуль и подключать последний в скриптах при помощи use. Напоминаю, что, используя такую технологию, вы полностью избавляетесь от надоедливой 500-й ошибки, а значит, доводите комфорт своей работы почти до уровня PHP.

Дмитрий Котеров | 21 августа 2001 г. ©1999-2005
ProWeb studio
2004 - 2008
 
Rambler's Top100 Индекс популярности