packages feed

wai-middleware-delegate-0.1.0.0: test/Test/Fetch.hs

{-# LANGUAGE BangPatterns      #-}
{-# LANGUAGE NamedFieldPuns    #-}
{-# LANGUAGE OverloadedStrings #-}

module Test.Fetch
  ( fetch
  )
where

import qualified Data.ByteString              as BS
import qualified Data.ByteString.Char8        as C8
import qualified Data.ByteString.Lazy         as LBS

import           Control.Monad.Trans.Resource (runResourceT)
import           Data.Conduit                 (ConduitT, SealedConduitT, Void,
                                               await, sealConduitT, ($$+-))
import qualified Data.Conduit.Binary          as CB
import           Data.Int                     (Int64)
import           Data.Maybe                   (fromMaybe)
import           Network.Connection           (TLSSettings (..))
import           Network.HTTP.Client          (Request, newManager,
                                               responseBody, responseHeaders,
                                               responseStatus, secure, managerSetProxy, proxyFromRequest)
import           Network.HTTP.Client.TLS      (mkManagerSettings)
import           Network.HTTP.Simple          (setRequestManager, withResponse)
import           Network.HTTP.Types           (hContentLength, statusCode)

import           Test.HttpReply


fetch :: Request -> IO HttpReply
fetch req = do
    let mSettings = mkManagerSettings (TLSSettingsSimple True False False) Nothing
        mSettings' = managerSetProxy proxyFromRequest mSettings
    m <- newManager mSettings'
    runResourceT $ withResponse (setRequestManager m req) getSrc
  where
    getSrc resp = do
      let mbBodyLength = readInt64 <$> lookup hContentLength (responseHeaders resp)
      bodyText <- checkBodySize (sealConduitT $ responseBody resp) mbBodyLength
      return $ HttpReply (secure req) (statusCode $ responseStatus resp) (responseHeaders resp) bodyText

bodyCheckBlock :: Int64
bodyCheckBlock = 1000

checkBodySize :: (Monad m) => SealedConduitT () BS.ByteString m () -> Maybe Int64 -> m BS.ByteString
checkBodySize bodySrc Nothing = fmap (BS.concat . LBS.toChunks) $ bodySrc $$+- CB.take $ fromIntegral bodyCheckBlock
checkBodySize bodySrc (Just len)
  | len <= bodyCheckBlock = checkBodySize bodySrc Nothing
  | otherwise = fromMaybe "Success" <$> (bodySrc $$+- sizeCheckSink len)

-- A pipe that counts the size of each incoming C8.Bytestring, when the last is
-- received, the result is Nothing if the size matches the expected value or an
-- error message if it does not.
sizeCheckSink :: Monad m => Int64 -> ConduitT C8.ByteString Void m (Maybe C8.ByteString)
sizeCheckSink expectedSize = sink 0
  where
    sink !count = await >>= maybe (closeSink count) (sinkBlock count)
    sinkBlock !count bs = sink (count + fromIntegral (BS.length bs))

    -- | no more bytes: return Nothing if the count so far matches the expected value
    closeSink !count
      | count == expectedSize = return Nothing
      | otherwise = return $ Just . C8.pack $ "Error : Body length " ++ show count
                     ++ " should have been " ++ show expectedSize ++ "."

readInt64 :: C8.ByteString -> Int64
readInt64 = read . C8.unpack