Какие силы заставили WAI-приложение быть переработанным пять раз?

Я с любопытством посмотрел на WAI интерфейс, и хотя он выглядит простым, я был удивлен, увидев, сколько итераций потребовалось для стабилизации в текущей форме!

Я предполагал, что стиль CPS для безопасности ресурсов будет самой интересной вещью, но, похоже, есть чему поучиться!

$ git log -p --reverse -- wai/Network/Wai.hs | grep '\+type Application'
+type Application = Request -> Iteratee B.ByteString IO Response
+type Application = Request -> ResourceT IO Response
+type Application = Request -> C.ResourceT IO Response
+type Application = Request -> IO Response
+type Application = Request -> (forall b. (Response -> IO b) -> IO b)
+type Application = Request -> (Response -> IO ResponseReceived)
                            -> IO ResponseReceived

Некоторая археология дает несколько неудовлетворительные результаты:

$ git log --reverse -G 'type Application' --pretty=oneline -- wai/Network/Wai.hs | cat
879d4a23047c3585e1cba4cdd7c3e8fc13e17592 Moved everything to wai subfolder
360442ac74f7e79bb0e320110056b3f44e15107c Began moving wai/warp to conduit
af7d1a79cbcada0b18883bcc5e5e19a1cd06ae7b conduit 0.3
fe2032ad4c7435709ed79683acac3b91110bba04 Pass around an InternalState instead of living in ResourceT
63ad533299a0a5bad01a36171d98511fdf8d5821 Application uses bracket pattern
1e1b8c222cce96c3d58cd27318922c318642050d ResponseReceived, to avoid existential issues

1 ответ

Кажется, что все проекты обусловлены тремя основными проблемами:

  • У запросов могут быть потоковые тела (поэтому нам не нужно загружать их все в память, прежде чем начинать их обрабатывать). Как лучше всего это представить?
  • Ответы также могут быть переданы в потоковом режиме. Как лучше всего это представить?
  • Как обеспечить правильное освобождение ресурсов, выделенных на производство ответа? (Например, как обеспечить освобождение дескрипторов файлов после передачи файла?)

type Application = Request -> Iteratee B.ByteString IO Response

В этой версии используются итераторы, которые были ранним решением для потоковой передачи данных в Haskell. Итеративные потребители должны были быть написаны "на основе push", что, возможно, менее естественно, чем "на основе pull", используемые в современных потоковых библиотеках.

Потоковое тело запроса передается итерируемому, и мы получаем Response значение в конце. Response содержит перечислитель (функция, которая передает потоковые байты ответа итератору ответа, предоставленному сервером). Предположительно, перечислитель будет управлять распределением ресурсов, используя такие функции, как bracket,


type Application = Request -> ResourceT IO Response

Эта версия использует монадный преобразователь resourcet для управления ресурсами вместо того, чтобы делать это в перечислителе. Есть специальный Source введите внутри обоих Request а также Response который обрабатывает потоковые данные (и который немного трудно понять, ИМХО).


type Application = Request -> IO Response

Эта версия использует потоковые абстракции из канала, но избегает resourcet и вместо этого предоставляет в виде скобок responseSourceBracket функция для обработки ресурсов в потоковых ответах.


type Application = Request -> (forall b. (Response -> IO b) -> IO b)
type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived

Эта версия переходит к подходу на основе продолжения, который позволяет функции обработчика использовать регулярные bracket -подобные функции для управления распределением ресурсов. Вернемся к исходной точке в этом отношении!

Каналы больше не используются для потоковой передачи. Теперь есть равнина Request -> IO ByteString функция для чтения фрагментов тела запроса и (Builder -> IO ()) -> IO () -> IO () функция в Response для генерации потока ответов. (The Builder -> IO () Функция записи вместе с действием сброса предоставляются сервером.)

Как и в версиях на основе ресурсов, и в отличие от версий на основе итераций, эта реализация позволяет перекрывать чтение тела запроса с потоковой передачей ответа.

Полиморфный обработчик - это хитрый трюк, обеспечивающий ответный обратный вызов Response -> IO b всегда вызывается: обработчик должен вернуть b и единственный способ получить его - это вызвать обратный вызов!

Это полиморфное решение, кажется, вызвало некоторые проблемы (возможно, с хранением обработчиков в контейнерах?) Вместо использования полиморфизма, мы можем использовать ResponseReceived токен без публичного конструктора. Эффект тот же: единственный способ для кода обработчика получить токен, который он должен вернуть, - вызвать обратный вызов.

Другие вопросы по тегам