Документация Perl 5

PSGI::FAQ


NAME

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

ВОПРОСЫ

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Plack

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

  • CGI::PSGI

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

  • CGI::Emulate::PSGI и CGI::Compile

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

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

  • Plack::Request и Plack::Response

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

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

HTTP::Engine

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

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

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

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

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

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

В качестве альтернативы можно использовать Plack::Request и Plack::Response, что предоставляет совместимое c HTTP::Engine::Request и 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;
  }

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

API Дизайн

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Самый простой способ для реализации технологии push со стороны сервера это вернуть из приложения IO::Handle-подобный объект как body, который реализует метод 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]);
          });
      };
  };

wait_for_new_message может быть блокирующим и неблокирующим: выбор за разработчиком. В большинстве случаев требуется неблокирующий режим, тогда следует использовать цикл обработки событий (event loop), как AnyEvent. Также можно проверить значение 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;
              }
          });
      };
  };

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

СМ. ТАКЖЕ

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

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

ЕЩЁ ВОПРОСЫ?

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

AUTHOR

Tatsuhiko Miyagawa <miyagawa@bulknews.net>

COPYRIGHT AND LICENSE

Copyright Tatsuhiko Miyagawa, 2009-2010.

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

Английский оригинал находится здесь: https://github.com/miyagawa/psgi-specs

Переводчики (сортировка по длине имени :):