{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
module Servant.Client.Internal.HttpClient.Streaming (
module Servant.Client.Internal.HttpClient.Streaming,
ClientEnv (..),
mkClientEnv,
clientResponseToResponse,
requestToClientRequest,
catchConnectionError,
) where
import Prelude ()
import Prelude.Compat
import Control.Concurrent.STM.TVar
import Control.DeepSeq
(NFData, force)
import Control.Exception
(evaluate, throwIO)
import Control.Monad ()
import Control.Monad.Base
(MonadBase (..))
import Control.Monad.Codensity
(Codensity (..))
import Control.Monad.Error.Class
(MonadError (..))
import Control.Monad.Reader
import Control.Monad.STM
(atomically)
import Control.Monad.Trans.Except
import qualified Data.ByteString.Lazy as BSL
import Data.Foldable
(for_)
import Data.Functor.Alt
(Alt (..))
import Data.Proxy
(Proxy (..))
import Data.Time.Clock
(getCurrentTime)
import GHC.Generics
import Network.HTTP.Types
(statusCode)
import qualified Network.HTTP.Client as Client
import Servant.Client.Core
import Servant.Client.Internal.HttpClient
(ClientEnv (..), catchConnectionError,
clientResponseToResponse, mkClientEnv, requestToClientRequest)
client :: HasClient ClientM api => Proxy api -> Client ClientM api
client api = api `clientIn` (Proxy :: Proxy ClientM)
hoistClient
:: HasClient ClientM api
=> Proxy api
-> (forall a. m a -> n a)
-> Client m api
-> Client n api
hoistClient = hoistClientMonad (Proxy :: Proxy ClientM)
newtype ClientM a = ClientM
{ unClientM :: ReaderT ClientEnv (ExceptT ServantError (Codensity IO)) a }
deriving ( Functor, Applicative, Monad, MonadIO, Generic
, MonadReader ClientEnv, MonadError ServantError)
instance MonadBase IO ClientM where
liftBase = ClientM . liftIO
instance Alt ClientM where
a <!> b = a `catchError` \_ -> b
instance RunClient ClientM where
runRequest = performRequest
throwServantError = throwError
instance RunStreamingClient ClientM where
withStreamingRequest = performWithStreamingRequest
instance ClientLike (ClientM a) (ClientM a) where
mkClient = id
withClientM :: ClientM a -> ClientEnv -> (Either ServantError a -> IO b) -> IO b
withClientM cm env k =
let Codensity f = runExceptT $ flip runReaderT env $ unClientM cm
in f k
runClientM :: NFData a => ClientM a -> ClientEnv -> IO (Either ServantError a)
runClientM cm env = withClientM cm env (evaluate . force)
performRequest :: Request -> ClientM Response
performRequest req = do
ClientEnv m burl cookieJar' <- ask
let clientRequest = requestToClientRequest burl req
request <- case cookieJar' of
Nothing -> pure clientRequest
Just cj -> liftIO $ do
now <- getCurrentTime
atomically $ do
oldCookieJar <- readTVar cj
let (newRequest, newCookieJar) =
Client.insertCookiesIntoRequest
(requestToClientRequest burl req)
oldCookieJar
now
writeTVar cj newCookieJar
pure newRequest
eResponse <- liftIO $ catchConnectionError $ Client.httpLbs request m
case eResponse of
Left err -> throwError err
Right response -> do
for_ cookieJar' $ \cj -> liftIO $ do
now' <- getCurrentTime
atomically $ modifyTVar' cj (fst . Client.updateCookieJar response request now')
let status = Client.responseStatus response
status_code = statusCode status
ourResponse = clientResponseToResponse response
unless (status_code >= 200 && status_code < 300) $
throwError $ FailureResponse ourResponse
return ourResponse
performWithStreamingRequest :: Request -> (StreamingResponse -> IO a) -> ClientM a
performWithStreamingRequest req k = do
m <- asks manager
burl <- asks baseUrl
let request = requestToClientRequest burl req
ClientM $ lift $ lift $ Codensity $ \k1 ->
Client.withResponse request m $ \res -> do
let status = Client.responseStatus res
status_code = statusCode status
unless (status_code >= 200 && status_code < 300) $ do
b <- BSL.fromChunks <$> Client.brConsume (Client.responseBody res)
throwIO $ FailureResponse $ clientResponseToResponse res { Client.responseBody = b }
x <- k (clientResponseToResponse res)
k1 x