PSGI
НАИМЕНОВАНИЕ
PSGI - Спецификация Веб-серверного Шлюзового Интерфейса для Perl
ОБЗОР
Этот документ определяет стандартный интерфейс между веб-серверами и веб-приложениями или фреймворками на Perl. Этот интерфейс призван содействовать переносимости веб-приложений и сокращению дублирования усилий разработчиков веб-фреймворков.
Пожалуйста, имейте в виду, что PSGI - не просто "Ещё Один Веб-фреймворк". PSGI - это спецификация, предназначенная для отделения среды веб-сервера от кода веб-фреймворка. Также, PSGI не является программным интерфейсом (API) для веб-приложений. Разработчики веб-приложений (конечные пользователи) не будут запускать свои веб-приложения, используя PSGI напрямую - вместо этого предполагается использование фреймворков, поддерживающих PSGI.
ТЕРМИНОЛОГИЯ
- Веб-серверы
Веб-серверы принимают HTTP запросы от веб-клиентов, передают эти запросы веб-приложениям (если сконфигурированы делать это) и возвращают HTTP ответы инициировавшим запрос клиентам.
- PSGI сервер
PSGI сервер - это программа на Perl, предоставляющая среду для запуска в ней PSGI приложения.
PSGI определяет интерфейс для веб-приложений и основную задачу веб-приложений, которая заключается в том, чтобы обслуживать запросы через Интернет. PSGI сервер, скорее всего, будет либо: частью веб-сервера (как Apache mod_perl), соединён с веб-сервером (при помощи FastCGI, SCGI), вызываться веб-сервером (как в старом добром CGI), или же сам по себе будет автономным веб-сервером, написанным целиком или частично на Perl.
Тем не менее, PSGI сервер не обязан в действительности быть веб-сервером или его частью, так как PSGI только определяет интерфейс между сервером и приложением, а не между сервером и остальным миром.
PSGI Сервер также часто называется PSGI Application Container, так как он похож на Java Servlet container, который представляет собой Java процесс, предоставляющий среду для сервлетов Java.
- Приложения
Веб-приложения принимают HTTP запросы и возвращают HTTP ответы.
PSGI приложения - веб-приложения, соответствующие интерфейсу PSGI. Они должны принимать форму code reference с определёнными входными и выходными данными.
Для простоты PSGI приложения также будут называться Приложениями до конца этого документа.
- Middleware (связующее ПО)
Middleware (Связующее ПО) - приложение PSGI (code reference), а также Сервер. Middleware выглядит как приложение, когда вызывается со стороны сервера, и, в свою очередь, может вызывать другие приложения. Можно думать о нём как о плагине для расширения приложения PSGI.
- Разработчики фреймворков
Разработчики фреймворков - это авторы фреймворков для веб-приложений. Они пишут адаптеры (или движки), которые принимают входные данные PSGI, запускают веб-приложение и возвращают ответ PSGI серверу.
- Разработчики веб-приложений
Разработчики веб-приложений - это разработчики, которые пишут код на основе веб-фреймворков. Эти разработчики никогда не должны иметь дело с PSGI напрямую.
СПЕЦИФИКАЦИЯ
Приложение
Приложение PSGI - это Perl code reference. Оно принимает ровно один аргумент, окружение, и возвращает ссылку на массив, содержащий ровно три значения.
my $app = sub { my $env = shift; return [ '200', [ 'Content-Type' => 'text/plain' ], [ "Hello World" ], # or IO::Handle-like object ]; };
Окружение
Окружение ДОЛЖНО быть ссылкой на хеш, включающей в себя CGI-подобные заголовки, как описано ниже. Приложение может свободно изменять окружение. Окружение ДОЛЖНО содержать эти переменные (заимствовано из http://www.python.org/dev/peps/pep-0333/, http://rack.rubyforge.org/doc/files/SPEC.html и http://jackjs.org/jsgi-spec.html), за исключением тех случаев, когда они, как правило, пусты.
Если переменная окружения является логической переменной, её значение ОБЯЗАНО
соответствовать нотации Perl для булевых чисел. Это означает, что пустая
строка или явно указанный 0
являются правильными false значениями. Если
булевый ключ отсутствует, приложение МОЖЕТ интерпретировать его как false
значение.
Значения всех CGI переменных (в имени которых нет точки) ДОЛЖНЫ быть скалярными строками.
Подробнее см. ниже.
-
REQUEST_METHOD
: Метод HTTP запроса, такой как "GET" или "POST". Это НЕ ДОЛЖНА быть пустая строка, так что метод всегда должен присутствовать. -
SCRIPT_NAME
: Начальная часть пути URL запроса, соответствующего приложению. Говорит приложению о своем виртуальном "местоположении". Может быть пустой строкой, если приложение соответствует корневому URI сервера.Если этот ключ не пуст, он ДОЛЖЕН начинаться с прямого слэша (
/
). -
PATH_INFO
: Остаточная часть пути URL запроса, определяющая виртуальное "расположение" цели запроса внутри приложения. Может быть пустой строкой, если запрашиваемый URL указывает на корень приложения и не имеет завершающего слэша. Это значение должно быть URI-декодировано серверами для того, чтобы быть совместимым с http://www.ietf.org/rfc/rfc3875.Если эта переменная не пуста, она ДОЛЖНА начинаться с прямого слэша (
/
). -
REQUEST_URI
: Не декодированная, необработанная строка URL. Это необработанный путь URI и часть запроса, которые фигурируют в HTTPGET /... HTTP/1.x
строке и не содержат схемы URI и имён хоста.В отличие от
PATH_INFO
, это значение НЕ ДОЛЖНО декодироваться сервером. Это ответственность приложения - правильно декодировать пути для того, чтобы отображать URL-ы на обработчики приложения, если оно предпочитает использовать эту переменную окружения вместоPATH_INFO
. -
QUERY_STRING
: Порция запрашиваемого URL, которая следует за?
, если таковая имеется. Эта переменная МОЖЕТ быть пустой, но ДОЛЖНА всегда присутствовать, даже если она пуста. -
SERVER_NAME
,SERVER_PORT
: При комбинации соSCRIPT_NAME
иPATH_INFO
, эти переменные окружения могут быть использованы для завершения URL. Обратите внимание, что, тем не менее, для реконструкции запрашиваемого URL предпочтительнее использоватьHTTP_HOST
(если он присутствует), чемSERVER_NAME
.SERVER_NAME
иSERVER_PORT
НЕ ДОЛЖНЫ быть пустыми строками, и всегда должны присутствовать. -
SERVER_PROTOCOL
: Версия протокола, используемого клиентом для отправки запроса. Как правило, имеет вид "HTTP/1.0" или "HTTP/1.1" и может использоваться приложением для определения того, как трактовать заголовки запроса HTTP. -
CONTENT_LENGTH
: Длина содержимого в байтах, в виде целого числа. Присутствие или отсутствие этой переменной окружения должно соответствовать присутствию или отсутствию HTTP заголовка Content-Length в запросе. -
CONTENT_TYPE
: MIME-тип запроса, в том виде, в каком он указан клиентом. Присутствие или отсутствие этой переменной окружения должно соответствовать присутствию или отсутствию HTTP заголовка Content-Type в запросе. -
Переменные
HTTP_*
: Эти переменные окружения соответствуют предоставленным клиентом заголовкам HTTP запроса. Присутствие или отсутствие этих ключей должно соответствовать присутствию или отсутствию соответствующих заголовков HTTP в запросе.Переменная окружения получается путём конвертации имени поля заголовка HTTP в верхний регистр, замещения всех дефисов
-
на подчёркивания_
и подстановки приставкиHTTP
, как в http://www.ietf.org/rfc/rfc3875.Если есть несколько строк заголовка, отправленных с одним ключом (именем переменной), серверу следует рассматривать их так, как будто они были посланы в одной строке и объединять их при помощи
,
, как описано в http://www.ietf.org/rfc/rfc2616.
В добавление к ключам, описанным выше, окружение PSGI ДОЛЖНО также включать следующие специфичные для PSGI ключи:
-
psgi.version
: Ссылка на массив [1,1], представляющий используемую версию PSGI. Первое число - старший номер версии, второе - младший номер версии. -
psgi.url_scheme
: Строкаhttp
илиhttps
, зависящая от URL запроса. -
psgi.input
: входной поток. Подробности см. ниже. -
psgi.errors
: поток ошибок. Подробности см. ниже. -
psgi.multithread
: Это булево значение, которое ДОЛЖНО быть true, если приложение может быть одновременно вызвано другой нитью того же процесса, иначе false. -
psgi.multiprocess
: Это булево значение, которое ДОЛЖНО быть true, если эквивалентный объект приложения может быть одновременно вызван другим процессом, иначе false. -
psgi.run_once
: Булево значение, содержащее true, если сервер ожидает (но не гарантирует!), что приложение будет вызвано только в этот единственный раз за время жизни содержащего его процесса. Обычно это бывает верно только для сервера, основанного на CGI (или на чём-то похожем). -
psgi.nonblocking
: Булево значение, содержащее true, если сервер вызывает приложение в неблокирующем событийном цикле. -
psgi.streaming
: Булево значение, равное true, если сервер ожидает отложенного ответа в стиле callback и потокового пишущего объекта.
Сервер или приложение может также сохранять свои данные в переменных окружения. Эти переменные ДОЛЖНЫ содержать по меньшей мере одну точку, и их СЛЕДУЕТ называть, начиная с уникальной приставки.
Приставка psgi.
зарезервирована для использования с основной спецификацией
PSGI, а приставка psgix.
зарезервирована для официально одобренных
расширений. Эти приставки НЕ ДОЛЖНЫ использоваться другими серверами или
приложением. См. список официально одобренных расширений в
psgi-extensions|PSGI::Extentions.
Окружение НЕ ДОЛЖНО содержать ключи с именем HTTP_CONTENT_TYPE
или
HTTP_CONTENT_LENGTH
.
Одна из переменных SCRIPT_NAME
или PATH_INFO
ДОЛЖНА быть установлена.
Когда REQUEST_URI
- это /
, переменной PATH_INFO
следует быть равной
/
, а переменной SCRIPT_NAME
следует быть пустой. Переменная
SCRIPT_NAME
НЕ ДОЛЖНА быть равной /
, но МОЖЕТ быть пустой.
Входной Поток
Входной поток в psgi.input
- это IO::Handle-подобный объект, который
отдаёт поток необработанных данных HTTP POST или PUT запросов. Если это
дескриптор файла, он ДОЛЖЕН быть открыт в бинарном режиме. Входной поток
ДОЛЖЕН отвечать на запрос read
и МОЖЕТ реализовывать seek
.
Встроенные в Perl файловые дескрипторы или основанные на IO::Handle объекты
должны работать "как есть" внутри PSGI сервера. Разработчикам приложений НЕ
СЛЕДУЕТ контролировать тип или класс потока. Вместо этого им СЛЕДУЕТ
просто вызывать метод read
объекта.
Разработчикам приложений НЕ СЛЕДУЕТ использовать встроенную функцию Perl
read
или итератор (<$fh>
) для чтения из входного потока. Вместо
этого разработчикам приложений следует вызывать read
как метод
($fh->read
) для обеспечения утиной типизации.
Разработчикам фреймворков, если они знают, что входной поток будет использован со встроенной функцией read() в любом апстрим-коде, который они не смогут трогать, СЛЕДУЕТ использовать PerlIO или tied handle для обработки этой проблемы.
Ожидается, что объект входного потока обеспечивает метод read
:
- read
$input->read($buf, $len [, $offset ]);
Возвращает число действительно прочитанных символов, 0 в конце файла, или undef при ошибке.
Он может также реализовывать опциональный метод seek
. Если
переменная окружения psgix.input.buffered
равна true, он ДОЛЖЕН
реализовывать метод seek
.
См. документацию IO::Handle для получения дальнейших подробностей о том, как эти методы в точности должны работать.
Поток Ошибок
Поток ошибок в psgi.errors
- это IO::Handle-подобный объект для печати
ошибок. Поток ошибок должен реализовывать метод print
.
Как и с входным потоком, встроенные файловые дескрипторы Perl или
основанные на IO::Handle объекты должны работать "как есть" в PSGI сервере.
Разработчикам приложений НЕ СЛЕДУЕТ проверять тип или класс потока. Вместо
этого, им СЛЕДУЕТ просто вызывать метод объекта print
.
Ответ
Приложения ДОЛЖНЫ возвращать ответ или как ссылку на трёхэлементный массив, или code reference на отложенный/потоковый ответ.
Массив, на который возвращается ссылка, состоит из следующих элементов:
Статус
Код статуса HTTP. Это ДОЛЖНО быть целое число, большее или равное 100, и ему СЛЕДУЕТ быть кодом статуса HTTP, как описано в http://www.w3.org/Protocols/rfc2616.
Заголовки
Заголовки ДОЛЖНЫ быть ссылкой на массив (не ссылкой на хеш) или парами ключ/значение. Это значит, они ДОЛЖНЫ содержать чётное число элементов.
Заголовок НЕ ДОЛЖЕН содержать ключ с названием Status
, а также
какие-либо ключи с :
или переводом строки в названии. Он НЕ ДОЛЖЕН
содержать какие-либо ключи, оканчивающиеся на -
или _
.
Все ключи ДОЛЖНЫ состоять только из букв, цифр, _
или -
. Все ключи
ДОЛЖНЫ начинаться с буквы. Значение заголовка ДОЛЖНО быть скалярной
строкой, и должно быть определено. Значение строки НЕ ДОЛЖНО содержать
символы ниже восьмеричного 037, т.е. chr(31).
Если одни и те же имена ключей появляются несколько раз в ссылке на массив,
эти строки заголовков ДОЛЖНЫ отправляться к клиенту по отдельности
(например, несколько строк Set-Cookie
).
Content-Type
ДОЛЖЕН присутствовать Content-Type
за исключением случая, когда
Status
равен 1xx, 204 или 304 - в этом случае НЕ ДОЛЖНО быть указано
типа содержимого.
Content-Length
Заголовок Content-Length
НЕ ДОЛЖЕН быть указан, когда Status
равен
1xx, 204 или 304.
Если Status не 1xx, 204 или 304 и заголовок Content-Length
отсутствует,
PSGI сервер МОЖЕТ вычислить длину содержимого, глядя на Тело ответа. Это
значение может затем быть добавлено к списку заголовков, возвращенному
приложением.
Тело ответа
Тело ответа ДОЛЖНО быть возвращено из приложения или как ссылка на массив, или как дескриптор, содержащий тело ответа в виде байтовой строки. Тело ДОЛЖНО быть закодировано в соответствующей кодировке и НЕ ДОЛЖНО содержать "широких" символов (> 255).
-
Если тело ответа - ссылка на массив, ожидается, что оно содержит массив строк, из которых состоит само тело.
my $body = [ "Hello\n", "World\n" ];
Обратите внимание, что элементы в массиве НЕОБЯЗАТЕЛЬНО должны оканчиваться переводом строки. Серверу СЛЕДУЕТ отправлять каждый элемент "как есть" клиенту, и НЕ СЛЕДУЕТ заботиться о том, оканчиваются ли строки переводом строки, или нет.
Ссылка на массив с единственным значением валидна. Так,
[ $html ]
- валидное тело ответа. -
Тело ответа может вместо этого быть дескриптором - или встроенным файловым дескриптором Perl, или IO::Handle-подобным объектом.
open my $body, "</path/to/file"; open my $body, "<:via(SomePerlIO)", ...; my $body = IO::File->new("/path/to/file"); # mock class that implements getline() and close() my $body = SomeClass->new();
Серверам НЕ СЛЕДУЕТ проверять тип или класс тела ответа. Вместо этого им следует просто вызывать
getline
для перебора строк тела, и вызватьclose
по завершению.Серверы МОГУТ проверять, является ли тело реальным файловым дескриптором, используя
fileno
иScalar::Util::reftype
. Если тело - реальный дескриптор файла, сервер МОЖЕТ оптимизировать выполнение, используя методы вроде sendfile(2).Объект тела также МОЖЕТ иметь метод
path
. Ожидается, что этот метод возвратит путь к файлу, доступному серверу. Это позволяет серверу использовать данную информацию вместо номера файлового дескриптора для отдачи файла.Серверам СЛЕДУЕТ устанавливать специальную переменную
$/
для получения размера буфера при чтении содержимого из$body
при помощи методаgetline
. Это делается путём присваивания$/
ссылки на целое число ($/= \8192
).Если файловый дескриптор тела ответа - встроенный файловый дескриптор Perl или объект IO::Handle, они обратят внимание на значение. Похожим образом объект, предоставляющий аналогичный API, МОЖЕТ также обратить внимание на эту специальную переменную, но от него не требуется делать это.
Отложенный Ответ и Потоковое Тело ответа
The PSGI interface allows applications and servers to provide a callback-style response instead of the three-element array reference. This allows for a delayed response and a streaming body (server push).
Интерфейс PSGI позволяет приложениям и серверам предоставлять ответ в виде вызываемой функции вместо ссылки на трёхэлементный массив. Это позволяет возвращать отложенные ответы и потоковое тело ответа (server push).
PSGI серверам СЛЕДУЕТ реализовывать этот интерфейс, и переменная
psgi.streaming
должна быть равна true в таких серверах.
Чтобы разрешить отложенный ответ, приложению СЛЕДУЕТ возвращать вызываемую
функцию в качестве ответа. Приложение МОЖЕТ проверять, что переменная
psgi.streaming
равна true, и возвращаться к непосредственному ответу, если
это не так.
This callback will be called with another subroutine reference (referred to as the responder from now on) as its only argument. The responder should in turn be called with the standard three element array reference response. This is best illustrated with an example:
Эта вызываемая функция будет вызвана со ссылкой надругую функцию (далее называемую ответчик) в качестве единственного аргумента. Ответчик должен в свою очередь быть вызван со стандартным ответом в виде ссылки на трёхэлементный массив. Наилучшим образом это иллюстрируется следующим примером:
my $app = sub { my $env = shift; # Откладывает ответ до тех пор, пока он не получит ответ от сети return sub { my $responder = shift; fetch_content_from_server(sub { my $content = shift; $responder->([ 200, $headers, [ $content ] ]); }); }; };
Приложение МОЖЕТ опустить третий элемент (тело) при вызове ответчика.
Если тело пропущено, ответчик ДОЛЖЕН возвратить ещё один объект,
в котором реализованы методы write
и close
. Снова проиллюстрируем это
примером.
my $app = sub { my $env = shift; # immediately starts the response and stream the content return sub { my $responder = shift; my $writer = $responder->( [ 200, [ 'Content-Type', 'application/json' ]]); wait_for_events(sub { my $new_event = shift; if ($new_event) { $writer->write($new_event->as_json . "\n"); } else { $writer->close; } }); }; };
Этот отложенный ответ и потоковый API полезны, если вы хотите реализовать основанную на неблокирующем вводе/выводе отдачу потока сервером или технологию long-poll Comet push, но могут также быть использованы для реализации небуферизованной записи в блокирующем сервере.
Middleware
Компонент middleware берёт другое PSGI приложение и запускает его. С точки зрения сервера компонент middleware - PSGI приложение. С точки зрения приложения, запускаемого компонентом middleware, middleware - это сервер. Как правило это делается для реализации некоего способа предварительной обработки хеша PSGI окружения или пост-обработки запроса.
Вот простой пример, который добавляет специальный HTTP заголовок X-PSGI-Used к любому приложению PSGI.
# $app - простое приложение PSGI my $app = sub { my $env = shift; return [ '200', [ 'Content-Type' => 'text/plain' ], [ "Hello World" ] ]; }; # $xheader - часть middleware, которая обёрнута вокруг $app my $xheader = sub { my $env = shift; my $res = $app->($env); push @{$res->[1]}, 'X-PSGI-Used' => 1; return $res; };
Middleware ДОЛЖНО вести себя в точности так же, как приложение PSGI с точки зрения сервера. Middleware МОЖЕТ решить не поддерживать потоковый интерфейс, обсуждавшийся ранее, но ему СЛЕДУЕТ пропускать беспрепятственно те типы ответов, которые оно не понимает.
CHANGELOGS
1.1: 2010.02.xx
-
Added optional PSGI keys as extensions:
psgix.logger
andpsgix.session
. -
psgi.streaming
SHOULD be implemented by PSGI servers, rather than MAY. -
PSGI keys
psgi.run_once
,psgi.nonblocking
andpsgi.streaming
MUST be set by PSGI servers. -
Removed
poll_cb
from writer methods.
ACKNOWLEDGEMENTS
Some parts of this specification are adopted from the following specifications.
-
PEP333 Python Web Server Gateway Interface http://www.python.org/dev/peps/pep-0333
-
JSGI Specification http://jackjs.org/jsgi-spec.html
I'd like to thank authors of these great documents.
AUTHOR
Tatsuhiko Miyagawa <miyagawa@bulknews.net>
CONTRIBUTORS
The following people have contributed to the PSGI specification and Plack implementation by commiting their code, sending patches, reporting bugs, asking questions, suggesting useful advices, nitpicking, chatting on IRC or commenting on my blog (in no particular order):
Tokuhiro Matsuno Kazuhiro Osawa Yuval Kogman Kazuho Oku Alexis Sukrieh Takatoshi Kitano Stevan Little Daisuke Murase mala Pedro Melo Jesse Luehrs John Beppu Shawn M Moore Mark Stosberg Matt S Trout Jesse Vincent Chia-liang Kao Dave Rolsky Hans Dieter Pearcey Randy J Ray Benjamin Trott Max Maischein Slaven Rezić Marcel Grünauer Masayoshi Sekimura Brock Wilcox Piers Cawley Daisuke Maki Kang-min Liu Yasuhiro Matsumoto Ash Berlin Artur Bergman Simon Cozens Scott McWhirter Jiro Nishiguchi Masahiro Chiba Patrick Donelan Paul Driver Florian Ragwitz
COPYRIGHT AND LICENSE
Copyright Tatsuhiko Miyagawa, 2009-2011.
This document is licensed under the Creative Commons license by-sa.
Английский оригинал находится здесь: https://github.com/miyagawa/psgi-specs
Переводчики (сортировка по длине имени :):