@@ -16,17 +16,19 @@ module Servant.Client.Internal.HttpClient where
16import Prelude ()
16import Prelude ()
17import Prelude.Compat
17import Prelude.Compat
19+import Control.Concurrent.STM.TVar
19import Control.Exception
20import Control.Exception
20import Control.Monad
21import Control.Monad
21import Control.Monad.Base (MonadBase (..))
22import Control.Monad.Base (MonadBase (..))
22import Control.Monad.Catch (MonadCatch, MonadThrow)
23import Control.Monad.Catch (MonadCatch, MonadThrow)
23import Control.Monad.Error.Class (MonadError (..))
24import Control.Monad.Error.Class (MonadError (..))
24import Control.Monad.Reader
25import Control.Monad.Reader
26+import Control.Monad.STM (atomically)
25import Control.Monad.Trans.Control (MonadBaseControl (..))
27import Control.Monad.Trans.Control (MonadBaseControl (..))
26import Control.Monad.Trans.Except
28import Control.Monad.Trans.Except
27import Data.ByteString.Builder (toLazyByteString)
29import Data.ByteString.Builder (toLazyByteString)
28import qualified Data.ByteString.Lazy as BSL
30import qualified Data.ByteString.Lazy as BSL
29−import Data.Foldable (toList)
31+import Data.Foldable (toList, for_)
30import Data.Functor.Alt (Alt (..))
32import Data.Functor.Alt (Alt (..))
31import Data.Maybe (maybeToList)
33import Data.Maybe (maybeToList)
32import Data.Monoid ((<>))
34import Data.Monoid ((<>))
@@ -34,6 +36,7 @@ import Data.Proxy (Proxy (..))
34import Data.Sequence (fromList)
36import Data.Sequence (fromList)
35import Data.String (fromString)
37import Data.String (fromString)
36import qualified Data.Text as T
38import qualified Data.Text as T
39+import Data.Time.Clock (getCurrentTime)
37import GHC.Generics
40import GHC.Generics
38import Network.HTTP.Media (renderHeader)
41import Network.HTTP.Media (renderHeader)
39import Network.HTTP.Types (hContentType, renderQuery,
42import Network.HTTP.Types (hContentType, renderQuery,
@@ -47,8 +50,13 @@ data ClientEnv
47 = ClientEnv
50 = ClientEnv
48 { manager :: Client.Manager
51 { manager :: Client.Manager
49 , baseUrl :: BaseUrl
52 , baseUrl :: BaseUrl
53+ , cookieJar :: Maybe (TVar Client.CookieJar)
56+-- | 'ClientEnv' smart constructor.
57+mkClientEnv :: Client.Manager -> BaseUrl -> ClientEnv
58+mkClientEnv mgr burl = ClientEnv mgr burl Nothing
52-- | Generates a set of client functions for an API.
60-- | Generates a set of client functions for an API.
54-- Example:
62-- Example:
@@ -68,7 +76,7 @@ client api = api `clientIn` (Proxy :: Proxy ClientM)
68-- | @ClientM@ is the monad in which client functions run. Contains the
76-- | @ClientM@ is the monad in which client functions run. Contains the
69-- 'Client.Manager' and 'BaseUrl' used for requests in the reader environment.
77-- 'Client.Manager' and 'BaseUrl' used for requests in the reader environment.
70newtype ClientM a = ClientM
78newtype ClientM a = ClientM
71− { runClientM' :: ReaderT ClientEnv (ExceptT ServantError IO) a }
79+ { unClientM :: ReaderT ClientEnv (ExceptT ServantError IO) a }
72 deriving ( Functor, Applicative, Monad, MonadIO, Generic
80 deriving ( Functor, Applicative, Monad, MonadIO, Generic
73 , MonadReader ClientEnv, MonadError ServantError, MonadThrow
81 , MonadReader ClientEnv, MonadError ServantError, MonadThrow
74 , MonadCatch)
82 , MonadCatch)
@@ -79,7 +87,7 @@ instance MonadBase IO ClientM where
79instance MonadBaseControl IO ClientM where
87instance MonadBaseControl IO ClientM where
80 type StM ClientM a = Either ServantError a
88 type StM ClientM a = Either ServantError a
82− liftBaseWith f = ClientM (liftBaseWith (\g -> f (g . runClientM')))
90+ liftBaseWith f = ClientM (liftBaseWith (\g -> f (g . unClientM)))
84 restoreM st = ClientM (restoreM st)
92 restoreM st = ClientM (restoreM st)
@@ -97,19 +105,33 @@ instance ClientLike (ClientM a) (ClientM a) where
97 mkClient = id
105 mkClient = id
99runClientM :: ClientM a -> ClientEnv -> IO (Either ServantError a)
107runClientM :: ClientM a -> ClientEnv -> IO (Either ServantError a)
100−runClientM cm env = runExceptT $ (flip runReaderT env) $ runClientM' cm
108+runClientM cm env = runExceptT $ flip runReaderT env $ unClientM cm
103performRequest :: Request -> ClientM Response
110performRequest :: Request -> ClientM Response
104performRequest req = do
111performRequest req = do
105− m <- asks manager
112+ ClientEnv m burl cookieJar' <- ask
106− burl <- asks baseUrl
113+ let clientRequest = requestToClientRequest burl req
114+ request <- case cookieJar' of
115+ Nothing -> pure clientRequest
116+ Just cj -> liftIO $ do
117+ now <- getCurrentTime
119+ oldCookieJar <- readTVar cj
120+ let (newRequest, newCookieJar) =
121+ Client.insertCookiesIntoRequest
107− let request = requestToClientRequest burl req
122+ (requestToClientRequest burl req)
125+ writeTVar cj newCookieJar
109 eResponse <- liftIO $ catchConnectionError $ Client.httpLbs request m
128 eResponse <- liftIO $ catchConnectionError $ Client.httpLbs request m
110 case eResponse of
129 case eResponse of
111− Left err -> throwError $ err
130+ Left err -> throwError err
112 Right response -> do
131 Right response -> do
132+ for_ cookieJar' $ \cj -> liftIO $ do
133+ now' <- getCurrentTime
134+ atomically $ modifyTVar' cj (fst . Client.updateCookieJar response request now')
113 let status = Client.responseStatus response
135 let status = Client.responseStatus response
114 status_code = statusCode status
136 status_code = statusCode status
115 ourResponse = clientResponseToResponse response
137 ourResponse = clientResponseToResponse response