Потоковая байтовая строка как тело ответа WAI HTTP-сервера - PullRequest
0 голосов
/ 03 мая 2018

У меня есть значение body :: BS.ByteString (ResourceT IO) (), от функции, основанной на BS.readFile. Я хочу передать это значение как тело ответа от Wai Application. Есть помощник, streamingResponse, который принимает значение типа Stream (Of ByteString) IO r. Я могу конвертировать BS.ByteString (ResourceT IO) () в Stream (Of ByteString) (ResourceT IO) () с помощью BS.toChunks, но он содержит дополнительный монадный слой ResourceT. Передача body в streamingResponse дает мне:

Couldn't match type ‘ResourceT IO’ with ‘IO’
  Expected type: Stream (Of ByteString) IO ()
    Actual type: Stream (Of ByteString) (ResourceT IO) ()

Я пробовал разные вещи, такие как упаковка в runResourceT, привязка и перенос значений и т. Д., Но на самом деле не знаю, как поступить. Здесь - строка в полном проекте, если требуется дополнительный контекст.

Update0

hoist runResourceT body вроде бы проверка типа. Кто-то также направил меня к Haskell Pipes thread , что может быть очень связанной проблемой и возможным указанием на решение.

Ответы [ 2 ]

0 голосов
/ 04 мая 2018

Если мы хотим разрешить Stream s, которые живут в ResourceT, мы можем обойтись без функций из streaming-wai (которые работают только для Stream s на основе IO) и вместо этого построить поверх функций, таких как responseStream из network-wai :

import           Control.Monad.Trans.Resource
import           Network.Wai                     
import           Streaming                   
import qualified Streaming.Prelude               as S
import           Data.ByteString.Builder (byteString, Builder)

streamingResponseR :: Stream (Of ByteString) (ResourceT IO) r
                   -> Status
                   -> ResponseHeaders
                   -> Response
streamingResponseR stream status headers =
    responseStream status headers streamingBody
    where
    streamingBody writeBuilder flush =
        let writer a =
                do liftIO (writeBuilder (byteString a))
                    -- flushes for every produced bytestring, perhaps not optimal
                   liftIO flush
         in runResourceT $ void $ S.effects $ S.for stream writer

streamingBody имеет тип StreamingBody, который фактически является синонимом типа для функции (Builder -> IO ()) -> IO () -> IO (), которая принимает обратный вызов записи и обратный вызов сброса в качестве параметров и использует их для записи ответа, используя некоторый источник данных, который находится в области. (Обратите внимание, что эти обратные вызовы предоставляются WAI , а не пользователем.)

В нашем случае источником данных является Stream, который живет в ResourceT. Нам нужно поднять обратные вызовы write и flush (которые живут в IO), используя liftIO, также не забывайте вызывать runResourceT, чтобы возвратить простое действие IO в конце.


Что, если мы захотим сбросить ответ только после того, как накопленная длина испущенных тестовых строк достигнет некоторого предела?

Нам потребуется функция (не реализованная здесь) для создания деления при каждом достижении лимита:

breaks' :: Monad m 
        => Int 
        -> Stream (Of ByteString) m r 
        -> Stream (Stream (Of ByteString) m) m r
breaks' breakSize = undefined

И затем мы могли бы вставить действие очистки между каждой группой, используя intercalates, перед записью потока:

streamingBodyFrom :: Stream (Of ByteString) (ResourceT IO) () 
                  -> Int 
                  -> StreamingBody
streamingBodyFrom stream breakSize writeBuilder flush =
    let writer a = liftIO (writeBuilder (byteString a))
        flusher = liftIO flush
        broken = breaks' breakSize stream
     in runResourceT . S.mapM_ writer . S.intercalates flusher $ broken
0 голосов
/ 04 мая 2018

Вместо readFile, будет ли достаточно withFile + hSetBinaryMode + Data.ByteString.Streaming.fromHandle?

fromHandle производит ByteString IO (), который может быть преобразован в Stream (Of ByteString) IO (), который streamingResponse или streamingBody может принять.

Существует вопрос о том, где поставить операцию брекетинга withFile. В соответствии с документацией WAI вы можете обернуть ею результат вашей функции Application -building:

Обратите внимание, что, начиная с WAI 3.0, этот тип структурирован в продолжение стиль передачи, позволяющий правильно обрабатывать ресурсы. Это было обрабатывался в прошлом другими способами (например, ResourceT).

[...]

Чтобы выделить ресурсы безопасным для исключений образом, вы можете используйте шаблон скобок вне вызова responseStream.


Примечание: документация streaming-bytestring гласит, что fromHandle автоматически закрывает дескриптор при достижении EOF. Глядя на реализацию, это не похоже на . Вам нужно withFile, чтобы правильно закрыть ручку.

...