File Coverage

blib/lib/CGI/WebOut.pm
Criterion Covered Total %
statement 135 263 51.3
branch 41 120 34.1
condition 10 21 47.6
subroutine 31 56 55.3
pod 14 25 56.0
total 231 485 47.6


line stmt bran cond sub pod time code
1             #
2             # Гарантирует, что вывод через print можно безопасно направить в браузер.
3             # То есть, буферизует его, следит за заголовками, а также за тем, чтобы
4             # заголовок Content-type всегда выводился перед текстом документа.
5             # Помимо всего этого, следит за ошибками, возникающими в скрипте, и
6             # перенаправляет их в браузер (в виде комментариев или видимого текста).
7             # Также позволяет выборочно перехватывать выходной поток скрипта для
8             # последующей обработки: $text = grab { print "Hello" }.
9             # В общем, полная эмуляция поведения PHP.
10             package CGI::WebOut;
11             our $VERSION = "2.25";
12              
13 1     1   2174 use strict;
  1         1  
  1         34  
14 1     1   5 use Exporter; our @ISA=qw(Exporter);
  1         1  
  1         5590  
15             our @EXPORT=qw(
16             ER_NoErr
17             ER_Err2Browser
18             ER_Err2Comment
19             ER_Err2Plain
20             ErrorReporting
21             grab
22             echo
23             SetAutoflush
24             NoAutoflush
25             UseAutoflush
26             Header
27             HeadersSent
28             Redirect
29             ExternRedirect
30             NoCache
31             Flush
32             try catch warnings throw
33             );
34              
35              
36             ##
37             ## Константы
38             ##
39 0     0 0 0 sub ER_NoErr { 0 } # Запретить вывод об ошибках
40 0     0 0 0 sub ER_Err2Browser { 1 } # Ошибки и предупреждения направить в браузер
41 0     0 0 0 sub ER_Err2Comment { 2 } # То же, но в виде -комментариев
42 0     0 0 0 sub ER_Err2Plain { 3 } # То же, но в виде plain-текста
43              
44              
45             ##
46             ## Внутренние переменные
47             ##
48             #our $DEBUG = "/wo"; # отладочный режим - задает имя файла.
49             our $DEBUG = undef; # отладочный режим - задает имя файла.
50             our $UseAutoflush = 1; # Режим автосброса включен
51             our $HeadersSent = 0; # признак: заголовки уже посланы
52             our @Headers = (); # заголовки ответа
53             our $NoCached = 0; # документ не кэшируется
54             our $Redirected = 0; # была переадресация
55             our $ErrorReporting = 1; # вывод ошибок в браузер включен
56             our @Errors = (); # здесь накапливаются ошибки
57             our @Warns = (); # предупреждения
58              
59             # К сожалению, вместо того, чтобы хранить текущий и корневой ОБЪЕКТЫ вывода,
60             # приходится хранить лишь СТРОКОВЫЕ БУФЕРА. Дело в том, что хранение объекта
61             # увеличивает его счетчик ссылок, а значит, если в программе встретится:
62             # $b = new CGI::WebOut();
63             # ...
64             # $b = undef;
65             # то деструктор для $b вызван не будет (т.к. ссылка на него записана в $CurObj).
66             # Если же хранить в объектах ссылки на строковые буфера, а ссылки на сами
67             # объекты дополнительно НЕ хранить, деструктор вызывается, как надо.
68             #
69             # Это все также означает, что получить в этом модуле ссылку на текущий
70             # ОБЪЕКТ ВЫВОДА нельзя никак. Можно лишь получить ссылку на его буфер.
71             # Таким образом, echo всегда работает со строковым буфером, но НЕ объектом.
72             our $rRootBuf = \(my $s=""); # главный буфер вывода
73             our $rCurBuf; # текущий буфер вывода
74              
75             #
76             # Algorythm is:
77             # 1. Tie STDOUT to newly created CGI::WebOut::Tie.
78             # 2. Constructor CGI::WebOut::Tie->RIEHANDLE creates new objecc CGI::WebOut
79             # and stores its reference in its property. It is IMPORTANT that there
80             # are NO other reserences to this object stored in some other place.
81             # 3. That's why, when STDOUT is untied (in END or during global destruction)
82             # CGI::WebOut object is destroyed too.
83             # 4. In destructor CGI::WebOut->DESTROY works code: if this object is the
84             # first (root), Flush() is called and errors are printed.
85             #
86              
87              
88             # Synopsis: use CGI::WebOut($restart=0)
89             # При подключении проверяет связывание STDOUT и, если свящанности нет или
90             # она поменялась, устанавливает ее на себя.
91             #
92             # Вниманию пользователей FastCGI: import работает не так, как хотелось бы.
93             # Например, если в цикле с двумя итерациями написать use CGI::WebOut, то
94             # реально import будет вызван только 1 раз. Зато если дважды написать эту же
95             # команду, то import вызовется дважды. Гарантированно import запускается
96             # следующей конструкцией: eval("use CGI::WebOut(1)"). Ее рекомендуется
97             # вставлять внутрь цикла обработки подключений FastCGI.
98             #
99             # Если параметр $restart равен true, то все происходит так, будто бы
100             # заголовки ответа еще никогда не посылались.
101              
102              
103             ##
104             ## Общедоступные статические функции.
105             ##
106              
107             # void retieSTDOUT($restart=false)
108             # Запоминаем старый STDOUT (длинное название - специально, чтобы не
109             # злоупотребляли!) и устанавливает свой перехватчик на STDOUT. В случае,
110             # если свой перехватчик уже установлен, ничего не делает.
111             my $numReties;
112             sub retieSTDOUT
113 2     2 0 4 { my ($needRestart) = @_;
114 2   66     12 $needRestart ||= !$numReties++;
115             # Handle all warnings and errors.
116 2 0   0   14 $SIG{__WARN__} = sub { &Warning(($_[0] !~ /^\w+:/ ? "Warning: " : "").shift) };
  0         0  
117 2 50   3   13 $SIG{__DIE__} = sub { return if ref $_[0]; &Warning(($_[0] !~ /^\w+:/ ? "Fatal: " : "").shift) };
  3 50       13  
  3         26  
118             # Если начат новый скрипт, сбрасываем признак отсылки заголовков.
119 2 50       8 if ($needRestart) {
120 2         4 $HeadersSent = $Redirected = $NoCached = 0;
121 2         3 @Headers = ();
122 2         5 $$rRootBuf = '';
123             }
124             # Если ничего не изменилось, выходим
125 2 50 66     21 return if tied(*STDOUT) && ref tied(*STDOUT) eq __PACKAGE__."::Tie";
126 2         13 tie(*STDOUT, __PACKAGE__."::Tie", \*STDOUT, tied(*STDOUT));
127             }
128              
129              
130             # Проверяет, используется ли библиотека Web-скриптом или обычным
131             sub IsWebMode() {
132 19 50   19 0 74 return $ENV{SCRIPT_NAME}? 1 : 0
133             }
134              
135              
136             # Посланы ли заголовки?
137             sub HeadersSent {
138 0     0 0 0 return $HeadersSent;
139             }
140              
141              
142             # static int echo(...)
143             # Выводит список агрументов в ТЕКУЩИЙ активный буфер. Если этот
144             # буфер направлен непосредственно в браузер, вызывает Flush().
145             # Возвращает число выведенных символов.
146             sub echo {
147             # В случае наличия undef-значений в списке делаем то же,
148             # что и print.
149 21 50   21 0 24 if(grep {!defined $_} @_) {
  21         57  
150             # Если модуля нет - не страшно, просто ничего не печатается.
151 0 0       0 eval { require Carp }
  0         0  
152             and Carp::carp("Use of uninitialized value in print");
153             }
154 21 50       23 my $txt = join("", map { defined $_? $_:"" } @_);
  21         80  
155 21 50       42 return if $txt eq "";
156 21         28 $$rCurBuf .= $txt;
157 21 100 66     87 Flush() if $UseAutoflush && $rCurBuf == $rRootBuf;
158 21         2409 return length($txt);
159             }
160              
161              
162             # Перехват выходного потока. Использование:
163             # $grabbed = grab {
164             # print 'Hello!'
165             # } catch {
166             # die "An error occurred while grabbing the output: $@";
167             # };
168             # или то же, но без catch:
169             # $grabbed = grab { print 'Hello!' };
170             sub grab(&@)
171 3     3 1 17 { my ($func, $catch)=@_;
172 3         12 my $Buf = CGI::WebOut->new;
173 3         4 $@ = undef; eval { &$func() };
  3         4  
  3         8  
174 3 50 33     10 if ($@ && $catch) { chomp($@); local $_ = $@; &$catch; }
  0         0  
  0         0  
  0         0  
175 3         8 return $Buf->buf;
176             }
177              
178              
179             # static Header($header)
180             # Устанавливает заголовок ответа.
181             sub Header($)
182 0     0 1 0 { my ($head)=@_;
183 0 0       0 if ($HeadersSent) {
184 0 0       0 eval { require Carp }
  0         0  
185             and Carp::carp("Oops... Header('$head') called after content had been sent to browser!\n");
186 0         0 return undef;
187             }
188 0         0 push(@Headers, $head);
189 0         0 return 1;
190             }
191              
192              
193             # Сбрасывает содержимое главного буфера в браузер.
194             sub Flush() {
195             # Отключаем внутреннюю буферизацию Perl-а
196 18     18 1 42 local $| = 1;
197             # Если заголовки еще не отосланы, отослать их
198 18 50 33     43 if (!$HeadersSent && IsWebMode()) {
199 0         0 my $ContType="text/html";
200 0         0 unshift(@Headers,"X-Powered-By: CGI::WebOut v$VERSION (http://www.dklab.ru/chicken/4.html), (C) by Dmitry Koterov");
201             # Ищем Content-type, чтобы потом отправить его в конце
202 0         0 for (my $i=0; $i<@Headers; $i++) {
203 0 0       0 if ($Headers[$i]=~/^content-type: *(.*)$/i) {
204 0         0 $ContType = $1; splice(@Headers, $i, 1); $i--;
  0         0  
  0         0  
205 0         0 next;
206             }
207 0 0       0 if ($Headers[$i]=~m/^location: /i) {
208 0         0 $Redirected = 1;
209             }
210             }
211 0 0       0 if (!$Redirected) {
212 0         0 push(@Headers, "Content-type: $ContType");
213 0         0 my $headers = join("\n",@Headers)."\n\n";
214             # Prepend the output buffer with headers data.
215             # So we output the buffer and headers in ONE print call (it is
216             # more transparent for calling code if it ties STDOUT by himself).
217 0         0 $$rRootBuf = $headers.$$rRootBuf;
218             } else {
219             # Only headers should be sent.
220 0         0 my $headers = join("\n",@Headers)."\n\n";
221 0         0 _RealPrint($headers);
222             }
223 0         0 $HeadersSent = 1;
224             }
225             # Отправить буфер и очистить его
226 18         33 _Debug("Flush: len=%d", length($$rRootBuf));
227 18 50       31 if (!$Redirected) {
228 18         27 _RealPrint($$rRootBuf);
229             }
230 18         46 $$rRootBuf = "";
231 18         34 return 1;
232             }
233              
234              
235             # constructor new($refToNewBuf=undef)
236             # Делает текущим новый буфер вывода.
237             sub new
238 5     5 0 8 { my ($class, $rBuf)=@_;
239 5 100       15 $rBuf = \(my $b="") if !defined $rBuf;
240 5         16 my $this = bless {
241             rPrevBuf => $rCurBuf,
242             rCurBuf => $rBuf,
243             }, $class;
244 5         7 $rCurBuf = $rBuf;
245 5         24 _Debug("[%s] New: prevSt=%s, curSt=%s", $this, $this->{rPrevBuf}, $this->{rCurBuf});
246 5         19 return $this;
247             }
248              
249              
250             # Восстанавливает предыдущий активный объект вывода
251             sub DESTROY
252 5     5   7 { my ($this)=@_;
253 5         11 _Debug("[%s] DESTROY: prevSt=%s, curSt=%s", $this, $this->{rPrevBuf}, $this->{rCurBuf});
254              
255             # Если это последний объект, то выполняем действия, которые нужно обязательно
256             # закончить к моменту завершения программы. То есть, этот участок кода выполняется
257             # тогда и только тогда, когда вызывается DESTROY для объекта, связанного с
258             # STDOUT, то есть перед самым завершением программы (по ошибке или нет - не важно).
259             # Все эти сложности нужны только потому, что, оказывается, в Perl нельзя
260             # объявить функцию, которая будет гарантировано вызываться в конце, особенно при
261             # фатальной ошибке... Однако можно создать некоторый объект, который при уничтожении
262             # вызовет свой деструктор. Таким объектом для нас будет объект, связанный
263             # с STDOUT. Нам это жизненно необходимо, потому что нужно любой ценой вывести
264             # заголовки и, возможно, сообщения о возникших ошибках. Это, собственно, и
265             # делается здесь.
266 5 100       11 if ($rCurBuf == $rRootBuf) {
267             # Вызываемая отсюда функция НЕ МОЖЕТ использовать print и STDOUT, потому что
268             # в момент прохождения этой точки STDOUT ни к чему не "привязан", но
269             # Perl-у кажется, что привязан, поэтому генерируется GP Fault.
270 2 100       7 &__PrintAllErrors() if @Errors;
271 2         3 Flush();
272 2         9 return;
273             }
274 3         10 $rCurBuf = $this->{rPrevBuf};
275             }
276              
277              
278             # string method buf()
279             # Вызывается для получения данных из буфера вывода.
280             sub buf {
281 3     3 0 8 return ${$_[0]->{rCurBuf}};
  3         10  
282             }
283              
284              
285              
286             ##
287             ## Служебные функции и методы.
288             ##
289              
290             # constructor _newRoot()
291             # Creates the new ROOT (!!!) buffer. Called internally while tying STDOUT.
292             sub _newRoot {
293 2     2   5 $$rRootBuf = "";
294 2         2 $rCurBuf = undef;
295 2         8 goto &new;
296             }
297              
298              
299             # Package import.
300             sub import {
301 2     2   92 my ($pkg, $needRestart)=@_;
302 2         5 retieSTDOUT($needRestart);
303 2         328 goto &Exporter::import;
304             }
305              
306              
307             # Деструктор пакета. Следит за тем, чтобы все объекты были удалены в
308             # правильном порядке. Вызывается ДО фазы "global destruction", что
309             # нам и нужно. Правда, есть сведения, что иногда END НЕ вызывается
310             # (в случае каких-то ошибок), однако и в этом случае все будет работать
311             # корректно (см. _RealPrint).
312             sub END {
313 1 50 33 1   0 return if !tied(*STDOUT) || ref tied(*STDOUT) ne __PACKAGE__."::Tie";
314 0         0 CGI::WebOut::_Debug("CGI::WebOut::END");
315 0         0 my $this = tied(*STDOUT);
316 0         0 my ($handle, $obj) = ($this->{handle}, $this->{prevObj});
317 0         0 CGI::WebOut::Tie::tieobj(*$handle, $obj)
318             }
319              
320              
321             # static _RealPrint()
322             # Prints the data to "native" STDOUT handler.
323             sub _RealPrint {
324 18     18   24 my $obj = tied(*STDOUT);
325 18         21 _Debug("_RealPrint: STDOUT tied: %s", $obj);
326 18         24 my $txt = join("", @_);
327 18 100       28 return if $txt eq "";
328 16 100       37 if ($obj) {
329 14 50       22 if (ref $obj eq "CGI::WebOut::Tie") {
330 14         28 return $obj->parentPrint(@_)
331             } else {
332 0         0 print STDOUT @_;
333             }
334             } else {
335             # Sometimes, during global destruction, STDOUT is already untied
336             # but print still does not work. I don't know, why. This workaround
337             # works always.
338 2         32 open(local *H, ">&STDOUT");
339 2         69 return print H @_;
340             }
341             }
342              
343              
344             # Для отладки - выводит сообщение в файл
345             my $opened;
346             sub _Debug {
347 79 50   79   157 return if !$DEBUG;
348 0         0 my ($msg, @args) = @_;
349              
350             # Detect "global destruction" stage.
351 0         0 my $gd = '';
352             {
353 0     0   0 local $SIG{__WARN__} = sub { $gd .= $_[0] };
  0         0  
  0         0  
354 0         0 warn("test");
355 0         0 $gd =~ s/^.*? at \s+ .*? \s+ line \s+ \d+ \s+//sx;
356 0         0 $gd =~ s/^\s+|[\s.]+$//sg;
357 0 0       0 $gd = undef if $gd !~ /global\s*destruction/i;
358             }
359 0         0 local $^W;
360 0 0       0 open(local *F, ($opened++? ">>" : ">").$DEBUG); binmode(F);
  0         0  
361 0 0       0 print F sprintf($msg, map { defined $_? $_ : "undef" } @args) . ($gd? " ($gd)" : "")."\n";
  0 0       0  
362             }
363              
364              
365             ##
366             ## Для перехвата вывода print-а
367             ##
368              
369             {{{
370             ##
371             ## This class is used to tie some Perl variable to specified $object
372             ## WITHOUT calling TIE* method of ref($object). Unfortunately Perl
373             ## does not support
374             ## tied(thing) = something;
375             ## construction. Instead of this use:
376             ## tie(thing, "CGI::WebOut::TieMediator", something).
377             ## See tieobj() below.
378             ##
379             package CGI::WebOut::TieMediator;
380             #sub TIESCALAR { return $_[1] }
381             #sub TIEARRAY { return $_[1] }
382             #sub TIEHASH { return $_[1] }
383 16     16   32 sub TIEHANDLE { return $_[1] }
384             }}}
385              
386              
387             {{{
388             ##
389             ## This class is used to tie objects to filehandle.
390             ## Synopsis:
391             ## tie(*STDOUT, "CGI::WebOut::Tie", \*STDOUT, tied(*STDOUT));
392             ## All the parent methods is virtually inherited. So you
393             ## may call print(*FH, ...), close(*FH, ...) etc.
394             ## All the output is redirected to current CGI::WebOut object.
395             ## This class is used internally by the main module.
396             ##
397             package CGI::WebOut::Tie;
398              
399             # The same as tie(), but ties existed object to the handle.
400             sub tieobj {
401             # return $_[1]? tie($_[0], "CGI::WebOut::TieMediator", $_[1]) : untie($_[0]);
402 16     16   54 return tie($_[0], "CGI::WebOut::TieMediator", $_[1]);
403             }
404              
405             ## Fully overriden methods.
406 0     0   0 sub WRITE { shift; goto &CGI::WebOut::echo; }
  0         0  
407 19     19   88 sub PRINT { shift; goto &CGI::WebOut::echo; }
  19         39  
408 0     0   0 sub PRINTF { shift; @_ = sprintf(@_); goto &CGI::WebOut::echo; }
  0         0  
  0         0  
409              
410             # Creates the new tie. Saves the old object and handle reference.
411             # See synopsis above.
412             sub TIEHANDLE
413 2     2   5 { my ($cls, $handle, $prevObj) = @_;
414 2         5 CGI::WebOut::_Debug("TIEHANDLE(%s, %s, %s)", $cls, $handle, $prevObj);
415 2         7 return bless {
416             handle => $handle,
417             prevObj => $prevObj,
418             outObj => CGI::WebOut->_newRoot($rRootBuf),
419             }, $cls;
420             }
421              
422             sub DESTROY {
423 2     2   18 CGI::WebOut::_Debug("[%s] DESTROY", $_[0]);
424             }
425              
426             ## Methods, inherited from parent.
427             sub CLOSE
428 0     0   0 { my ($this) = @_;
429 0         0 CGI::WebOut::Flush();
430 0     0   0 $this->parentCall(sub { close(*{$this->{handle}}) });
  0         0  
  0         0  
431             }
432             sub BINMODE
433 0     0   0 { my ($this) = @_;
434 0     0   0 $this->parentCall(sub { binmode(*{$this->{handle}}) });
  0         0  
  0         0  
435             }
436             sub FILENO
437 0     0   0 { my ($this) = @_;
438             # Do not call Flush() here, because it is incompatible with CGI::Session.
439             # E.g. the following code will not work if Flush() is uncommented:
440             # use CGI::WebOut;
441             # use CGI::Session;
442             # my $session = new CGI::Session(...);
443             # SetCookie(...); # says that "headers are already sent"
444             #CGI::WebOut::Flush();
445 0     0   0 $this->parentCall(sub { return fileno(*{$this->{handle}}) });
  0         0  
  0         0  
446 0         0 return 0;
447             }
448              
449             # Untie process is fully transparent for parent. For example, code:
450             # tie(*STDOUT, "T1");
451             # eval "use CGI::WebOut"; #***
452             # print "OK!";
453             # untie(*STDOUT);
454             # generates EXACTLY the same sequence of call to T1 class, as this
455             # code without ***-marked line.
456             # Unfortunately we cannot retie CGI::WebOut::Tie back to the object
457             # in UNTIE() - when the sub finishes, Perl hardly remove tie.
458             our $doNotUntie = 0;
459             sub UNTIE
460 14     14   16 { my ($this, $nRef) = @_;
461 14 100       41 return if $doNotUntie;
462 1         2 my $handle = $this->{handle};
463 1         507 CGI::WebOut::_Debug("UNTIE prev=%s, cur=%s", $this->{prevObj}, tied(*$handle));
464             # Destroy output object BEFORE untie parent.
465 1         1 $this->{outObj} = undef;
466             # Untie parent object.
467 1 50       3 if ($this->{prevObj}) {
468 1         2 tieobj(*$handle, $this->{prevObj});
469 1         2 $this->{prevObj} = undef; # release ref
470 1         3 untie(*$handle); # call parent untie
471 1         7 $this->{prevObj} = tied(*$handle);
472             }
473             }
474              
475             # void method parentPrint(...)
476             # Prints using parent print method.
477             sub parentPrint
478 14     14   13 { my $this = shift;
479 14         16 my $params = \@_;
480 14         30 CGI::WebOut::_Debug("parentPrint('%s')", join "", @$params);
481 14     14   47 $this->parentCall(sub { print STDOUT @$params });
  14         338  
482             }
483              
484             # void method parentCall($codeRef)
485             # Calls $codeRef in the context of object, previously tied to handle.
486             # After call context is switched back, as if nothing has happened.
487             # Returns the same that $codeRef had returned.
488             sub parentCall
489 14     14   17 { my ($this, $sub) = @_;
490 14         24 my ($handle, $obj) = ($this->{handle}, $this->{prevObj});
491 14         15 my $save = tied(*$handle);
492 14 100       32 if ($obj) {
    50          
493 1         3 tieobj(*$handle, $obj)
494             } elsif ($save) {
495 13         13 local $doNotUntie = 1;
496 13         30 local $^W;
497 13         29 untie(*$handle);
498             }
499 14         22 CGI::WebOut::_Debug("parentCall for STDOUT=%s", $obj);
500 14 50       16 my @result = eval { wantarray? $sub->() : scalar $sub->() };
  14         23  
501 14 50       29 if ($save) {
    0          
502 14         22 tieobj(*$handle, $save);
503             } elsif ($obj) {
504 0         0 local $doNotUntie = 1;
505 0         0 local $^W;
506 0         0 untie(*$handle);
507             }
508 14 50       49 return wantarray? @result : $result[0];
509             }
510             }}}
511              
512              
513             # Since v2.0 AutoLoader is not used.
514             #use AutoLoader 'AUTOLOAD';
515             #return 1;
516             #__END__
517              
518              
519             # Использование try-catch-throw:
520             # try {
521             # код, который может вылететь по ошибке
522             # или который генерирует исключение с помощью throw
523             # } catch {
524             # имя исключения или сообщение об ошибке - в $_
525             # } warnings {
526             # список произошедших ошибок и предупреждений в @_
527             # }
528             # Блоки catch и warnings выполняются в порядке их появления и могут отсутствовать.
529             sub try (&;@)
530 1     1 1 2 { my ($try,@Hand) = @_;
531             # Мы НЕ можем использовать local для сохранения @Errors по следующей присине.
532             # Если в &$try выведутся предупреждения, а потом будет вызван exit(),
533             # то до конца try() управление так и не дойдет. Если бы мы использовали
534             # local, то эти предупреждения в @Errors потерялись бы. Так как используется
535             # сохранение во временной переменной, предупреждения в @Errors останутся на
536             # месте и выведутся на экран.
537 1         4 my @SvErrors = @Errors;
538             # Запускаем try-блок
539 1         2 my @Result = eval { &$try };
  1         2  
540             # Управление попало сюда, если внутри кода не было вызова exit().
541             # В противном случае происодит вылет из функции и из программы.
542             # Получаем все возникшие предупреждения. Причем записываем их в
543             # переменную типа local, чтобы эта переменныя была видна внутри
544             # warnings-функции (см. ниже).
545 1 50       7 local @Warns = @Errors>@SvErrors? @Errors[@SvErrors..$#Errors] : ();
546             # Восстанавливаем сообщения об ошибках
547 1         2 @Errors = @SvErrors;
548             # Запускаем обработчики в порядке их появления
549 1         3 map { &$_() } @Hand;
  1         3  
550             # Возвращаем значение, которое вернул try-блок
551 1 50       10 return wantarray? @Result: $Result[0];
552             }
553              
554             # Возвращает функцию-замыкание, которая вызывает тело catch-блока.
555             sub catch(&;@)
556 1     1 1 11 { my ($body, @Hand)=@_;
557 1 50   1   5 return (sub { if($@) { chomp($@); local $_=$@; &$body($_) } }, @Hand);
  1         4  
  1         3  
  1         8  
  1         3  
558             }
559              
560             # Возвращает функцию-замыкание, которая вызывает тело warnings-блока.
561             sub warnings(&;@)
562 0     0 1 0 { my ($body,@Hand)=@_;
563 0     0   0 return (sub { &$body(@Warns) }, @Hand);
  0         0  
564             }
565              
566             # Выбрасывает исключение.
567             sub throw($) {
568 1 50   1 1 11 die(ref($_[0])? $_[0] : "$_[0]\n")
569             }
570              
571              
572             # bool SetAutoflush([bool $mode])
573             # Устанавливает режим сброса буфера echo: если $mode=1, то разрешает его автосброс после
574             # каждого вывода print или echo, иначе - запрещает (сброс должен производиться по Flush()).
575             # Возвращает предыдущий установленный режим автосброса.
576             sub SetAutoflush(;$)
577 0     0 1 0 { my ($mode)=@_;
578 0         0 my $old = $UseAutoflush;
579 0 0       0 if (defined $mode) { $UseAutoflush = $mode; }
  0         0  
580 0         0 return $old;
581             }
582              
583             # bool NoAutoflush()
584             # Запрещает сбрасывать буфер после каждого echo.
585             # Возвращает предыдущий статус автосброса.
586             sub NoAutoflush() {
587 0     0 1 0 return SetAutoflush(0);
588             }
589              
590              
591             # bool UseAutoflush()
592             # Разрашает сбрасывать буфер после каждого echo.
593             # Возвращает предыдущий статус автосброса.
594             sub UseAutoflush() {
595 0     0 1 0 return SetAutoflush(1);
596             }
597              
598              
599             # Перенаправляет на другой URL (может быть внутренним редиректом)
600             sub Redirect($)
601 0     0 1 0 { my ($url) = @_;
602 0         0 $Redirected = Header("Location: $url");
603 0         0 exit;
604             }
605              
606              
607             # Перенаправляет БРАУЗЕР на другой URL
608             sub ExternRedirect($)
609 0     0 1 0 { my ($url) = @_;
610 0 0       0 if ($url !~ /^\w+:/) {
611             # Относительный адрес.
612 0 0       0 if ($url !~ m{^/}) {
613 0         0 my $sn = $ENV{SCRIPT_NAME};
614 0         0 $sn =~ s{/+[^/]*$}{}sg;
615 0         0 $url = "$sn/$url";
616             }
617             # Добавить имя хоста.
618 0         0 $url = "http://$ENV{SERVER_NAME}$url";
619             }
620 0         0 $Redirected = Header("Location: $url");
621 0         0 exit;
622             }
623              
624              
625             # Запрещает кэширование документа браузером
626             sub NoCache()
627 0 0   0 1 0 { return 1 if $NoCached++;
628 0 0       0 Header("Expires: Mon, 26 Jul 1997 05:00:00 GMT") or return undef;
629 0 0       0 Header("Last-Modified: ".gmtime(time)." GMT") or return undef;
630 0 0       0 Header("Cache-Control: no-cache, must-revalidate") or return undef;
631 0 0       0 Header("Pragma: no-cache") or return undef;
632 0         0 return 1;
633             }
634              
635              
636             # int ErrorReporting([int $level])
637             # Устанавливает режим вывода ошибок:
638             # 0 - ошибки не выводятся
639             # 1 - ошибки выводятся в браузер
640             # 2 - ошибки выводятся в браузер в виде комментариев
641             # Если параметр не задан, режим не меняется.
642             # Возвращает предыдущий статус вывода.
643             sub ErrorReporting(;$)
644 0     0 1 0 { my ($lev)=@_;
645 0         0 my $old = $ErrorReporting;
646 0 0       0 $ErrorReporting = $lev if defined $lev;
647 0         0 return $old;
648             }
649              
650              
651             # Добавляет сообщение об ошибке к массиву ошибок.
652             sub Warning($)
653 3     3 0 7 { my ($msg)=@_;
654 3         30 push(@Errors, $msg);
655             }
656              
657              
658             # Печатает все накопившиеся сообщения об ошибках.
659             # Эта функция вызывается в момент, когда STDOUT находится в "подвешенном" состоянии,
660             # поэтому использование print ЗАПРЕЩЕНО!!!
661             sub __PrintAllErrors()
662 1     1   3 { local $^W = undef;
663             # http://forum.dklab.ru/perl/symbiosis/Fastcgi+WeboutUtechkaPamyati.html
664 1 50 33     8 if(!@Errors || !$ErrorReporting){
665 0         0 @Errors=();
666 0         0 return ;
667             }
668 1 50       3 if (IsWebMode) {
669 0 0       0 if ($ErrorReporting == ER_Err2Browser) {
670             # мало ли, какие там были таблицы...
671 0         0 echo "","","","
"x6,""x2,""x2,""x2,""x10,"\n"; 672             } 673 0         0 my %wasErr=(); 674 0         0 for (my $i=0; $i<@Errors; $i++) { 675 0         0 chomp(my $st = $Errors[$i]); 676             # Исключаем дублирующиеся сообщения о ФАТАЛЬНЫХ ошибках. 677 0 0       0 next if $wasErr{$st}; 678 0 0       0 $wasErr{$st}=1 if $st =~ /^Fatal:/; 679             # Выводим сообщение. 680 0 0       0 if ($ErrorReporting == ER_Err2Browser) {     0               0           681 0         0 $st=~s/>/>/sg; 682 0         0 $st=~s/ 683 0         0 $st=~s|^([a-zA-Z]+:)|$1|mgx; 684 0         0 $st=~s|\n|
\n    |g; 685 0         0 my $s=$i+1; 686 0         0 for(my $i=length($s); $i   0         0   687 0         0 echo "$s) $st
\n"; 688             } elsif ($ErrorReporting == ER_Err2Comment) { 689 0         0 echo "\n"; 690             } elsif ($ErrorReporting == ER_Err2Plain) { 691 0         0 echo "\n$st"; 692             } 693             } 694             } else { 695 1         3 foreach my $st (@Errors) { chomp($st); echo "\n$st" }   2         4     2         15   696             } 697 1         3 @Errors=(); 698             } 699               700             return 1; 701             __END__