The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
=encoding utf-8

=head1 NAME

PSGI::FAQ - Часто задаваемые вопросы и ответы

=head1 ВОПРОСЫ

=head2 Общие вопросы

=head3 Как произносится PSGI?

Да просто Пэ-эС-Гэ-АЙ.

=head3 Так что же это?

PSGI это интерфейс между веб серверами и Perl веб приложениями аналогично тому,
чем является CGI для серверов и CGI скриптов.

=head3 Зачем это нам нужно?

Модуль CGI поставляется вместе с Perl, что в некотором роде обеспечивает
абстракцию над CGI, mod_perl и FastCGI. Однако большинство разработчиков веб
фреймворков (например, Catalyst и Jifty) обычно не используют этот модуль для
обеспечения производительности и доступа к низкоуровневым API. Поэтому они в
итоге приходят к написанию адаптеров для всех окружений, некоторые из которых
могут быть оттестированы лучше других.

PSGI позволяет разработчикам веб фреймворков реализовывать только адаптер для
PSGI. Конечные же пользователи могут выбирать любые поддерживающие PSGI
интерфейс серверные приложения.

=head3 PSGI похож на CGI. Каким образом PSGI интерфейс отличается от CGI?

PSGI интерфейс намеренно спроектирован очень похожим на CGI, чтобы поддержка
PSGI в дополнение к CGI была как можно проще.

=over 4

=item *

В CGI серверы это фактически веб серверы написанные на любом языке, в
основном на C, и скрипт это скрипт, который также может быть написан на любом
языке таком как C, Perl, Shell скриптах, Ruby или Python.

В PSGI серверы тоже веб серверы, но это уже perl процессы, которые обычно
встраиваются в веб сервер (как mod_perl) или же perl демоны, которые вызываются
веб сервером (как FastCGI), или веб серверы полностью реализованные на Perl. И
PSGI приложение это ссылка на Perl подпрограмму.

=item *

В CGI мы используем STDIN, STDERR и переменные окружения для чтения параметров,
тела HTTP запроса и сообщений об ошибках.

В PSGI мы используем ссылки на хеш C<$env> и потоки I<psgi.input> и
I<psgi.errors> для передачи данных между серверами и приложениями.

=item *

В CGI приложения должны отдавать HTTP заголовки и тело в STDOUT, чтобы передать
это обратно веб серверу.

В PSGI приложения должны вернуть код статуса HTTP, заголовки и тело (как ссылку
на массив или filehandle объект) серверу в качестве ссылки на массив.

=back

=head3 Мой фреймворк уже поддерживает CGI, FCGI и mod_perl. Зачем мне поддерживаеть PSGI?

Есть много разных преимуществ в поддержке PSGI для веб фреймворка.

=over 4

=item *

Можно не писать код для поддержки разных окружений веб серверов.

В Plack много хорошо протестированных адаптеров для серверных окружений как,
например, CGI, FastCGI и mod_perl. Так же много новых веб серверов написанных
специально для поддержки стандартного PSGI интерфейса, как, например,
L<Starman>, L<Starlet> и L<Twiggy>. Как только фреймворк поддерживает PSGI,
больше ничего не нужно, чтобы их использовать. И это без каких бы то ни было
усилий.

Также, даже если фреймворк уже поддерживает большинство серверых окружений, как
было сказано выше, можно отказаться от этого кода в пользу PSGI. Так поступили
L<Jifly> и L<Catalyst>, когда реализовали поддержку PSGI. Меньше кода значит
меньше багов :)

=item *

Фреймворк теперь может использовать все промежуточные компоненты Plack 

Если поискать L<Plack::Middleware> на CPAN можно найти сотни PSGI совместимых
промежуточных компонентов. Это часто написанные с нуля, но также и заимствующие
код из существующих плагинов для фреймворков таких как L<Catalyst>, модули.
Поддерживая PSGI интерфейс, фреймворк может использовать эти полезные
компоненты, будь то сессии, кеширование, перепизаписывание URL или отладочная
панель, и это только несколько из них.

=item *

Можно тестировать приложения используя единообразный интерфейс L<Plack::Test>.

Любое PSGI приложение может быть протестировано с помощью L<Plack::Test>, либо
через mock объекты, либо через запущенный сервер. Есть также
L<Test::WWW::Mechanize::PSGI> для тестирования в стиле Mechanize.

=back

=head3 Я пишу веб приложение. В чем для меня преимущество использования PSGI?

Если используется фреймворк поддерживающий PSGI, приложение может быть запущено
на любой существующей или будущей PSGI реализации. Необходимо предоставить
C<.psgi> файл, который возвращает PSGI приложение, и конечные пользователи
приложения смогут сконфигурировать и запустить его различными способами.

=head3 Но я пишу приложение на CGI и оно хорошо работает. Должен ли я переключаться на PSGI?

Если пишется веб приложение с использованием CGI.pm не используя веб фреймворки,
это ограничивает приложение для запуска только в CGI окружении, вместе с
mod_perl и FastCGI с некоторыми изменениями. Если у приложения один программист
и он же пользователь, то этого может быть вполне достаточно.

Однажды может появиться необходимость использования приложения на виртуальном
хостинге, или запуска сервера в автономном режиме, а не как CGI скрипт, или
распространение приложения как open source. Лимитирование приложения CGI окружением,
при этом используя CGI.pm, может аукнуться.

Можно начать использовать один из PSGI совместимых фреймворков (либо
полнофункциональных, либо микро), или использовать L<Plack::Request> если вы
против фреймворков, чтобы сделать приложение осведомленным о PSGI на будущеe.

Даже если вместо PSGI используется CGI для написания приложения, всегда можно
переключиться на PSGI используя L<CGI::PSGI> обертку.

=head3 Что нужно сделать, чтобы поддерживать PSGI?

Разработчикам веб серверов необходимо написать PSGI реализацию, называемую
PSGI приложением. Также стоит присоединиться к разработке Plack - PSGI
инструментария и утилит - чтобы добавить адаптер сервера.

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

Разработчикам веб приложений (или пользователям веб вреймворков) необходимо
выбрать фреймворк поддерживающий PSGI или попросить автора поддерживать
этот интерфейс :). Если приложение большого размера и не использует никаких
фреймворков (как, например, WebGUI или Movable Type) стоит следовать совету
разработчикам фреймворков. В этом случае написание адаптера будет иметь больший
смысл.

=head3 Быстрее ли PSGI чем (мой фреймворк)?

Опять же, PSGI это не реализация, но есть возможность написать очень быструю
реализацию PSGI, которая загружает все модули и испольняет полностью
оптимизированный код как автономный prefork сервер с XS парсерами, или как
событийно-ориентированный легковесный веб сервер написанний на С и встроенном
perl, который поддерживает PSGI, или старый добрый основанный на CGI.pm скрипт,
который не загружает никаких модулей вобще и выполняется довольно быстро без
использования большого количества памяти в CGI окружении.

Есть реализация prefork веб серверов такие как L<Starman> и L<Starlet>, а также
полнофункциональные асинхронные событийно-ориентированные реализации такие как
L<Twiggy>, L<Corona> или L<Feersum>. Они довольно быстрые и имеют Plack
адаптеры, так что их можно запускать через утилиту L<plackup>.

Пользователи фреймворка могут выбрать какой сервер больше подходит под их нужды.
Разработчику веб фреймворка нет необходимости думать о разных пользователях с
разными запросами.

=head2 Plack

=head3 Что такое Plack? В чем отличие между PSGI и Plack?

PSGI это спецификация, поэтому нет программы или модуля называемого PSGI.
Конечным пользователям необходимо определиться с серверной реализацией PSGI, чтобы
запускать PSGI приложения. Plack это множество PSGI утилит, которое также
включает PSGI сервер L<HTTP::Server::PSGI>, а также серверные
адаптеры под CGI, FastCGI или mod_perl.

Plack также имеет множество полезных API и вспомогательного кода над PSGI, как,
например L<Plack::Request> для обеспечения объектно-ориентированного API для
запросов, L<plackup>, что позволяет запускать PSGI приложения из командной
строки и конфигурировать их с помощью C<app.psgi> (аналог Rackup из Rack), и
L<Plack::Test> что позволяет тестировать приложения используя стандартную пару
L<HTTP::Request> и L<HTTP::Response> через mock или запущенный сервер. Смотрите
L<Plack>.

=head3 Какие серверы будут доступны?

В Plack поддерживается уже большинство серверов таких как Apache2, а также те,
которые поддерживают CGI или FastCGI, есть попытки поддерживать веб сервера,
которые могут встраивать Perl, такие как Perlbal или nginx. Было бы хорошо, если
бы модуль Apache mod_perlite и Google AppEngine тоже поддерживали PSGI, чтобы
можно было запускать PSGI/Plack приложение в облаке.

=head3 Ruby это Rack, JavaScript это Jack. Почему это не называется Pack?

Да, Pack это действительно милое имя, но у Perl есть встроенная функция pack,
поэтому это может сбить с толку, особенно когда об этом рассказывают, а не
пишут.

=head3 Какой префикс стоит использовать реализовывая PSGI поддержку?

B<Не используйте PSGI:: префикс для реализаций PSGI серверов или адаптеров>.

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

Если пишется плагин или расширение для поддержки PSGI для (воображаемого) веб
фреймворка С<Camper>, назовайте модуль C<Camper::Engine::PSGI>.

Если пишется веб сервер с поддержкой интерфейса PSGI, тогда можно назвать его
как угодно. Опционально можно поддерживать абстрактный интерфейс
L<Plack::Handler> или написать адаптер, например:

  my $server = Plack::Handler::FooBar->new(%opt);
  $server->run($app);

Поддерживая C<new> и C<run>, сервер становится совместимым с C<plackup>, и
пользователи смогут запускать приложения с помощью C<plackup>. Рекомендуется, но
не требуется, следовать этому API, правда придется реализовать собственный
модуль для запуска PSGI приложений.

=head3 У меня есть CGI или mod_perl приложение, которое я хочу запусть на PSGI/Plack. Что мне следует делать?

Есть несколько вариантов:

=over 4

=item CGI::PSGI

Если есть веб приложение (или фреймворк) которое использует CGI.pm для разбора
параметров запроса, L<CGI::PSGI> поможет мигрировать на PSGI. Необходимо будет
изменить создание CGI объектов и возврат заголовков и тела ответа, остальной же
код останется без изменений.

=item CGI::Emulate::PSGI и CGI::Compile

Если есть старый мертвый CGI скрипт, который хотелось бы минимально изменить
(или не менять вообще), тогда L<CGI::Emulate::PSGI> и L<CGI::Compile>
скомпилируют и обернут скрипт как PSGI приложение.

По сравнению с L<CGI::PSGI> это не так эффективно из-за перехвата STDIN/STDOUT и
переписывания окружения, но должно работать с любой CGI реализацией, не только
CGI.pm, кроме того L<CGI::Compile> компилирует CGI скрипт в CODEREF
точно также как это делает Registry в mod_perl.

=item Plack::Request и Plack::Response

Если имеется L<HTTP::Engine> приложение (фреймворк), или необходимо написать его
с нуля, но с использованием лучшего, чем CGI, интерфейса, или нравится интерфейс
L<Apache::Request>, тогда L<Plack::Request> и L<Plack::Response> это то, что
нужно. Они предоставляют удобное Request/Response объектное API для упрощения
работы с хешом окружения и возвращают правильный массив со статусом, заголовками
и телом ответа.

=back

ПРИМЕЧАНИЕ: Не стоит забывать, что когда имеющийся CGI скрипт, который
выполняется один раз и завершается, используют как постоянный процесс, может
потребоваться очистка после каждого запроса: переменные, которые должны быть
обнулены; файлы, которые должны быть закрыты или удалены и т.д. PSGI ничего не
может сделать по этому поводу (необходима реализация), кроме как напомнить в
этом примечании.

=head2 HTTP::Engine

=head3 Почему PSGI/Plack вместо HTTP::Engine?

HTTP::Engine был хорошим экспериментом, но там совмещен интерфейс приложения
(C<request_handler> интерфейс) с реализациями, а монолитная классовая иерархия и
интерфейсы, выполненные на ролях, делают сложным написание нового сервера.
Существующий HTTP::Engine был разбит на три части: спецификация интерфейса
(PSGI), имплементация серверов (Plack::Handler) и стандартное API и утилиты
(Plack).

=head3 Умрет ли HTTP::Engine?

Он не умрет. HTTP::Engine останется таким каким есть и может быть полезным, если
необходимо написать микро вебсерверное приложение, а не фреймворк.

=head3 Должен ли я переписать HTTP::Engine приложение, чтобы следовать интерфейсу PSGI?

Нет, необходимости в переписывании HTTP::Engine приложения нет. Оно может быть
просто превращено в PSGI приложение с помощью L<HTTP::Engine::Interface::PSGI>.

В качестве альтернативы можно использовать L<Plack::Request> и
L<Plack::Response>, что предоставляет совместимое c L<HTTP::Engine::Request> и
L<HTTP::Engine::Response> API:

  use Plack::Request;
  use Plack::Response;

  sub request_handler {
      my $req = Plack::Request->new(shift);
      my $res = Plack::Response->new;
      # ...
      return $res->finalize;
  }

Метод C<request_handler> это и есть PSGI приложение.

=head2 API Дизайн

Стоит учитывать, что большинство вопросов проектирования в PSGI спецификации
обусловлены минимизацией требований к серверным приложениям, чтобы они могли
по-своему оптимизировать. Добавление модного интерфейса или излишняя гибкость на
разных уровнях PSGI может звучать притягательно для пользователей, но это только
добавит вещей, которые серверы должны поддерживать, что будет стоять на пути
оптимизации, или будет добавлять ошибки. Привлекать разработчиков с помощью
модного API должен фреймворк, а не PSGI.

=head3 Почему большой хеш окружения вместо объектов с API?

Простота интерфейса это ключевая делать, которая сделала WSGI и Rack успешными.
PSGI это низкоуровневый интерфейс между серверами и разработчиками веб
фреймворков. Если бы требовалось API какого типа объект должен передаваться и
какие методы он должен реализовывать, будет слишком много дублирующего кода в
серверных приложениях, части которых могут содержать ошибки.

Например, PSGI определяет C<< $env->{SERVER_NAME} >> как строку. Что если PSGI
спецификация требовала объект типа Net::IP? Код серверного приложения должен
будет зависеть от модуля Net::IP, или реализовывать похожий объект с таким же
интерфейсом, реализовывая ВСЕ методы Net::IP. Серверы, которые зависят от
специфических модулей или вынуждены реализовывать новые велосипеды, считаются
вредными и поэтому интерфейс остается минимальным насколько это возможно.

Разработка хорошего API для конечных пользователей должно быть работой веб
фреймворка (разработчиков адаптеров), а не чем-то, что определяется PSGI.

=head3 Почему приложение это CODEREF, а не объект с методом ->call?

Требование объекта I<в дополнение к> CODEREF сделает КАЖДОЕ серверное приложение
на несколько строчек длиннее, а требование объекта I<вместо> CODEREF будет
вынуждать разработчиков приложения реализовывать очередной класс и создавать его
экземпляр.

Другими словами, да, объект с методом C<call> будет работать, но, опять же, PSGI
был спроектирован минимальным насколько это возможно, и сделать из объекта
CODEREF совсем не сложно, что в обратном случае требует несколько дополнительных
строчек и, возможно, даже новый файл.

=head3 Почему заголовки возвращаются как ссылка на массив, а не ссылка на хеш?

Если коротко: Для поддержки заголовков с одинаковыми именами (например, C<Set-Cookie>).

Если длинно: В Python WSGI заголовки ответа это список (C<header_name>,
C<header_value>) I<кортежей>, например, C<type(response_headers) is ListType>,
поэтому у каждого заголовка может быть несколько значений. В Rack и JSGI
значение заголовка это String состоящая из строк разделенных "C<\n>".

Спецификация Python кажется наиболее приемлимой, но так как в Perl хеши не могут
содержать записи с одинаковыми ключами (исключение составляют C<tie>),
использование ссылки на массив для хранения значений
C<< [ key => value, key => value ] >> самое простое решение для обеспечения
простоты адаптеров фреймворков и серверов. Другие возможности, как ссылка на
массив или обычный скаляр, делают код излишне муторным.

=head3 Я хочу отправлять Unicode данные в HTTP ответе. Как я могу это сделать?

PSGI имитирует протокол CGI и интерфейс сильно не заботится о символьной
кодировке или строковой семантике. Это значит, что все данные в PSGI окружении,
теле ответа и пр. отправляются как байты, и это обязанность приложения правильно
декодировать или кодировать символы для отправки по HTTP.

Если есть декодированная строка в приложении и необходимо отправить данные как
C<UTF-8> в теле HTTP, нужно использовать модуль L<Encode> для кодирования в
utf-8. Если используется PSGI фреймворк, есть возможность, что он позволяет
устанавливать текст ответа в Unicode. Стоит проверить документацию фреймворка
так это или нет.

Это решение было принятно для большей гибкости PSGI приложений и фреймворков,
без добавления сложностей в PSGI серверы и в саму спецификацию интерфейса.

=head3 Нет поддержки итераторов в $body?

Мы узнали, что WSGI и Rack действительно пользуются такими преимуществами
языков Python и Ruby, как итерабельные объекты в Python и итераторы в Ruby.

Rack, например, ожидает, что body - это объект, которые отвечает на метод
C<each>, а затем создаёт буфер, так что

  body.each { |buf| request.write(buf) }

просто магическим образом сработает вне зависимости от того, является ли body
массивом, объектом FileIO или объектом, у которого есть итераторы. У Perl нет
такой красоты, встроенной в язык, если не загружен L<autobox>. PSGI не следует
делать autobox необходимым требованием, так что мы только поддерживаем простые
ссылки на массив или дескрипторы файлов.

Написание IO::Handle-подобного объекта довольно просто, так как это просто
C<getline> и C<close>. Вы можете также использовать PerlIO для написания
объекта, который ведёт себя как файловый дескриптор, хотя это может быть
сочтено несколько нестабильным.

См. также L<IO::Handle::Util> чтобы превратить любые итераторо-подобные
объекты в IO::Handle-подобные.

=head3 Как серверу следует определять возможность переключиться на использование
sendfile(2)?

Для начала, приложению СЛЕДУЕТ всегда создавать IO::Handle-подобный объект
(или массив частей), который отвечает на C<getline> и C<close> как тело ответа.
Это гарантированно работает с любыми серверами.

Опционально, если сервер написан на Perl или может передать номер файлового
дескриптора на C-сторону программы, чтобы обслужить запрос на этот файл, то
сервер МОЖЕТ проверить, является ли тело реальным файловым дескриптором
(возможно, используя функцию C<is_real_fh> из L<Plack::Util>), затем получить
файловый дескриптор при помощи C<fileno> и вызвать sendfile(2) или
эквивалентную функцию передачи данных без затрат на копирование, использующую
его.

В противном случае, если сервер не может отправить файл используя файловый
дескриптор, но нуждается в локальном пути к файлу (как mod_perl или nginx),
приложение может вернуть IO::Handle-подобный объект, который также отвечает на
метод C<path>. Этот тип IO-подобных объектов может легко быть создан при
использовании L<IO::File::WithPath>, L<IO::Handle::Util> или функции
C<set_io_path> из модуля L<Plack::Util>.

Middleware также может проверять, есть ли у body метод C<path>, и делать с ним
что-нибудь интересное - вроде установки заголовка C<X-Senfile>.

Подводя итог:

=over 4

=item *

При отдаче статических файлов приложению следует всегда возвращать реальный
дескриптор или объект IO::Handle. Это должно работать везде, и может быть
оптимизировано в некоторых средах.

=item *

Приложения могут также создавать IO::Handle-подобный объект с дополнительным
методом C<path>, после чего это также должно работать везде, и может быть
оптимизировано даже в большем количестве различных сред.

=back

=head3 Что если я хочу стримить контент или использовать long-polling Comet технологию?

Самый простой способ для реализации технологии push со стороны сервера это
вернуть из приложения IO::Handle-подобный объект как body, который
реализует метод C<getline> возвращающий push данные. Это гарантированно везде
работает, но это больше pull, чем push, и сложно делать неблокируемый
ввод/выдать, если только не используется Coro.

Если вы хотите делать push со стороны сервера, где приложение работает в цикле
обработки событий (event loop) и при готовности выдает данные клиенту, следует
вернуть callback для отложенного ответа.

  # long-poll comet подобное чат-приложение
  my $app = sub {
      my $env = shift;
      unless ($env->{'psgi.streaming'}) {
          die "Это приложение требует поддержки psgi.streaming";
      }
      return sub {
          my $respond = shift;
          wait_for_new_message(sub {
              my $message = shift;
              my $body = [ $message->to_json ];
              $respond->([200, ['Content-Type', 'application/json'], $body]);
          });
      };
  };

C<wait_for_new_message> может быть блокирующим и неблокирующим: выбор за
разработчиком. В большинстве случаев требуется неблокирующий режим, тогда
следует использовать цикл обработки событий (event loop), как L<AnyEvent>. Также
можно проверить значение C<psgi.nonblocking>, и, если это не поддерживается,
использовать блокирующий вызов.

Для того, чтобы стримить контент (для стриминга сообщений через Flash сокет или
multipart XMLHTTPRequest):

  my $app = sub {
      my $env = shift;
      unless ($env->{'psgi.streaming'}) {
          die "Это приложение требует поддержки psgi.streaming";
      }
      return sub {
          my $respond = shift;
          my $writer = $respond->([200, ['Content-Type', 'text/plain']]);
          wait_for_new_message(sub {
              my $message = shift;
              if ($message) {
                  $writer->write($message->to_json);
              } else {
                  $writer->close;
              }
          });
      };
  };

=head3 Какой же фреймворк использовать для стриминга?

Есть серверы, который поддерживают неблокирующий режим (где C<psgi.nonblocking>
имеет true значение), но проблема в том, что серверный фреймворк совсем не
обязательно поддерживает асинхронный цикл обработки событий. Например, в
Catalyst есть метод C<write> у объекта ответа:

  while ($cond) {
      $c->res->write($some_stuff);
  }

Это должно сработать с серверами поддерживающими C<psgi.streaming> даже если они
блокирующие, и если они работают в режиме нескольких процессов
(C<psgi.multiprocess> в значении true).

L<Catalyst::Engine::PSGI> также поддерживает установку IO::Handle-подобного
объекта, который имеет C<getline> метод, пример с L<IO::Handle::Util>

  my $io = io_from_getline sub {
       return $data; # or undef when done()
  };
  $c->res->body($io);

И это работает вполне нормально для стриминга, но это скорее блокирущая операция
(I<pull>), а не асинхронный push со стороны серверы, поэтому, опять же, стоит
быть осторожным при запуске приложения в неблокирующем (и не многопроцессорном)
серверном окружении.

Ожидается, что будут появляться новые фреймворки или дорабатываться
существующие, которые поддерживают асинхронный и неблокирующий стриминг
интерфейс.

=head3 Является ли интерфейс psgi.streaming необходимым для серверов?

Он указан в спецификации как B<SHOULD>, поэтому, если нет серьёзной причины
отказаться от реализации интерфейса, всем серверам рекомендуется реализовывать
этот интерфейс.

Тем не менее, если вы реализовываете PSGI сервер, используя Perl XS интерфейс
для достижения максимальной производительности или интеграции с веб-серверами
вроде Apache или nginx, или создаёте окружение типа "песочницы" (наподобие
Google AppEngine или Heroku), или распределённую платформу, используя утилиты
вроде Gearman, вы можете не захотеть реализовывать этот интерфейс.

Это прекрасно, и в этом случае приложения, основанные на потоковом интерфейсе,
всё ещё могут использовать L<Plack::Middleware::BufferedStreaming> в качастве
запасного варианта для буферизованной записи на неподдерживаемых серверах.

=head3 Почему переменные окружения в стиле CGI вместо хеша с HTTP заголовками?

Большинство существующих фреймворков для веб-приложений уже содержат код или
обработчик для запуска в CGI-окружении. Использование CGI-подобных ключей хеша
вместо HTTP заголовков делает тривиальной для разработчиков фреймворков задачу
реализации адаптера для поддержки PSGI. Например, L<Catalyst::Engine::PSGI>
всего лишь на несколько дюжин строк отличается от L<Catalyst::Engine::CGI> и
был написан менее чем за час.

=head3 Почему PATH_INFO URI декодирован?

Для совместимости со спецификацией CGI (RFC 3875) и большинством реализаций
веб-серверов (наподобие Apache и lighttpd).

Я понимаю, может быть неудобно, что вы не можете различить C<foo%2fbar> от
C<foo/bar> в окончании пути, но спецификация CGI ясно говорит, что
C<PATH_INFO> следует декодировать помощи сервера, и что веб-серверы могут
отклонять запросы, содержащие C<%2f> (так как такие запросы могут потерять
информацию в PATH_INFO). Оставив эти зарезервированные символы
недекодированными (частичное декодирование) можно ухудшить положение, так как
после этого вы не сможете сказать C<foo%2fbar> из C<foo%252fbar>, и возможна
дырка в безопасности при двойном кодировании или декодировании.

Для разработчиков веб-фреймворков, которым нужен больший контроль над
актуальным необработанным URI (таких как L<Catalyst>), мы сделали
ключ хеша окружения C<REQUEST_URI> ОБЯЗАТЕЛЬНЫМ. Серверам следует
устанавливать недекодированный (неразобранный) оригинальный URI (содержащий
строку запроса) в значение этого ключа. Обратите внимание, что C<REQUEST_URI>
совершенно необработан, даже если закодированные сущности URI-безопасны.

Для сравнения, WSGI (PEP-333) определяет C<SCRIPT_NAME> и C<PATH_INFO> как
декодированные, и Rack оставляет это зависеть от реализации, в то время как
I<исправление> большей части PATH_INFO осталось закодированным в реализациях
Ruby веб-серверов.

L<http://www.python.org/dev/peps/pep-0333/#url-reconstruction>
L<http://groups.google.com/group/rack-devel/browse_thread/thread/ddf4622e69bea53f>

=head1 СМ. ТАКЖЕ

WSGI FAQ ясно отвечает на множество вопросов о том, как были приняты некоторые
решения в устройстве API, некоторые из которых можно прямо применить к PSGI.

L<http://www.python.org/dev/peps/pep-0333/#questions-and-answers>

=head1 ЕЩЁ ВОПРОСЫ?

Если у вас есть вопрос, на который вы не нашли ответа здесь, или есть вещи, с
которыми вы полностью не согласны, присоединяйтесь к IRC каналу #plack на
irc.perl.org или к списку рассылки
L<http://groups.google.com/group/psgi-plack>. Будьте уверены, что прояснили,
чью шляпу вы носите: разработчика приложений, разработчика серверов или
разработчика middleware. И не критикуйте спецификацию только чтобы
покритиковать: покажите именно тот ваш код, который не работает или получился
слишком запутанным из-за ограничений спецификации и т.д. Мы проигнорируем все
придирки и неконструктивную болтовню.

=head1 AUTHOR

Tatsuhiko Miyagawa E<lt>miyagawa@bulknews.netE<gt>

=head1 COPYRIGHT AND LICENSE

Copyright Tatsuhiko Miyagawa, 2009-2010.

This document is licensed under the Creative Commons license by-sa.

=cut