Support http-client’s CookieJar in servant-client

This commit is contained in:
Michal Rus 2017-12-31 02:48:44 +01:00
parent 0147f4b5c7
commit 5880bdfd73
No known key found for this signature in database
GPG key ID: 396762D2C75A20A7
12 changed files with 37 additions and 12 deletions

View file

@ -164,7 +164,7 @@ main :: IO ()
main = do main = do
mgr <- newManager defaultManagerSettings mgr <- newManager defaultManagerSettings
bracket (forkIO $ runApp userDB) killThread $ \_ -> bracket (forkIO $ runApp userDB) killThread $ \_ ->
runClientM (getSite u) (ClientEnv mgr (BaseUrl Http "localhost" 8080 "")) runClientM (getSite u) (ClientEnv mgr (BaseUrl Http "localhost" 8080 "") Nothing)
>>= print >>= print
where u = BasicAuthData "foo" "bar" where u = BasicAuthData "foo" "bar"

View file

@ -125,7 +125,7 @@ main = do
initDB connStr initDB connStr
mgr <- newManager defaultManagerSettings mgr <- newManager defaultManagerSettings
bracket (forkIO $ runApp pool) killThread $ \_ -> do bracket (forkIO $ runApp pool) killThread $ \_ -> do
ms <- flip runClientM (ClientEnv mgr (BaseUrl Http "localhost" 8080 "")) $ do ms <- flip runClientM (ClientEnv mgr (BaseUrl Http "localhost" 8080 "") Nothing) $ do
postMsg "hello" postMsg "hello"
postMsg "world" postMsg "world"
getMsgs getMsgs

View file

@ -86,7 +86,7 @@ main = do
initDB dbfile initDB dbfile
mgr <- newManager defaultManagerSettings mgr <- newManager defaultManagerSettings
bracket (forkIO $ runApp dbfile) killThread $ \_ -> do bracket (forkIO $ runApp dbfile) killThread $ \_ -> do
ms <- flip runClientM (ClientEnv mgr (BaseUrl Http "localhost" 8080 "")) $ do ms <- flip runClientM (ClientEnv mgr (BaseUrl Http "localhost" 8080 "") Nothing) $ do
postMsg "hello" postMsg "hello"
postMsg "world" postMsg "world"
getMsgs getMsgs

View file

@ -151,7 +151,7 @@ testClient = do
let (foo :<|> _) = client (Proxy :: Proxy TestAPIClient) let (foo :<|> _) = client (Proxy :: Proxy TestAPIClient)
(BasicAuthData "name" "pass") (BasicAuthData "name" "pass")
res <- runClientM (foo 42) res <- runClientM (foo 42)
(ClientEnv mgr (BaseUrl Http "localhost" port "")) (ClientEnv mgr (BaseUrl Http "localhost" port "") Nothing)
hPutStrLn stderr $ case res of hPutStrLn stderr $ case res of
Left err -> "Error: " ++ show err Left err -> "Error: " ++ show err
Right r -> "Success: " ++ show r Right r -> "Success: " ++ show r

View file

@ -11,7 +11,7 @@ We start with a pretty standard set of imports and definition of the model:
{-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeOperators #-}
import Control.Concurrent (forkIO, killThread) import Control.Concurrent (forkIO, killThread)
import Control.Concurrent.STM.TVar (TVar, newTVar, readTVar, import Control.Concurrent.STM.TVar (TVar, newTVar, readTVar,
writeTVar) writeTVar)
import Control.Exception (bracket) import Control.Exception (bracket)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
@ -95,7 +95,7 @@ main = do
bracket (forkIO runApp) killThread $ \_ -> do bracket (forkIO runApp) killThread $ \_ -> do
let getBooksClient :<|> addBookClient = client api let getBooksClient :<|> addBookClient = client api
let printBooks = getBooksClient >>= liftIO . print let printBooks = getBooksClient >>= liftIO . print
_ <- flip runClientM (ClientEnv mgr (BaseUrl Http "localhost" port "")) $ do _ <- flip runClientM (ClientEnv mgr (BaseUrl Http "localhost" port "") Nothing) $ do
_ <- printBooks _ <- printBooks
_ <- addBookClient $ Book "Harry Potter and the Order of the Phoenix" _ <- addBookClient $ Book "Harry Potter and the Order of the Phoenix"
_ <- printBooks _ <- printBooks

View file

@ -136,7 +136,7 @@ queries = do
run :: IO () run :: IO ()
run = do run = do
manager' <- newManager defaultManagerSettings manager' <- newManager defaultManagerSettings
res <- runClientM queries (ClientEnv manager' (BaseUrl Http "localhost" 8081 "")) res <- runClientM queries (ClientEnv manager' (BaseUrl Http "localhost" 8081 "") Nothing)
case res of case res of
Left err -> putStrLn $ "Error: " ++ show err Left err -> putStrLn $ "Error: " ++ show err
Right (pos, message, em) -> do Right (pos, message, em) -> do

View file

@ -170,6 +170,6 @@ main :: IO ()
main = do main = do
mgr <- newManager defaultManagerSettings mgr <- newManager defaultManagerSettings
let clientBaseUrl = BaseUrl Http "www.example.com" 80 "" let clientBaseUrl = BaseUrl Http "www.example.com" 80 ""
ePos <- runClientM (position apiClient 10 20) $ ClientEnv mgr clientBaseUrl ePos <- runClientM (position apiClient 10 20) $ ClientEnv mgr clientBaseUrl Nothing
print ePos print ePos
``` ```

View file

@ -51,7 +51,9 @@ library
, mtl >= 2.1 && < 2.3 , mtl >= 2.1 && < 2.3
, semigroupoids >= 4.3 && < 5.3 , semigroupoids >= 4.3 && < 5.3
, servant-client-core == 0.12.* , servant-client-core == 0.12.*
, stm >= 2.1 && < 2.5
, text >= 1.2 && < 1.3 , text >= 1.2 && < 1.3
, time >= 1.7 && < 1.9
, transformers >= 0.3 && < 0.6 , transformers >= 0.3 && < 0.6
, transformers-base >= 0.4.4 && < 0.5 , transformers-base >= 0.4.4 && < 0.5
, transformers-compat >= 0.4 && < 0.6 , transformers-compat >= 0.4 && < 0.6

View file

@ -16,17 +16,19 @@ module Servant.Client.Internal.HttpClient where
import Prelude () import Prelude ()
import Prelude.Compat import Prelude.Compat
import Control.Concurrent.STM.TVar
import Control.Exception import Control.Exception
import Control.Monad import Control.Monad
import Control.Monad.Base (MonadBase (..)) import Control.Monad.Base (MonadBase (..))
import Control.Monad.Catch (MonadCatch, MonadThrow) import Control.Monad.Catch (MonadCatch, MonadThrow)
import Control.Monad.Error.Class (MonadError (..)) import Control.Monad.Error.Class (MonadError (..))
import Control.Monad.Reader import Control.Monad.Reader
import Control.Monad.STM (atomically)
import Control.Monad.Trans.Control (MonadBaseControl (..)) import Control.Monad.Trans.Control (MonadBaseControl (..))
import Control.Monad.Trans.Except import Control.Monad.Trans.Except
import Data.ByteString.Builder (toLazyByteString) import Data.ByteString.Builder (toLazyByteString)
import qualified Data.ByteString.Lazy as BSL import qualified Data.ByteString.Lazy as BSL
import Data.Foldable (toList) import Data.Foldable (toList, traverse_)
import Data.Functor.Alt (Alt (..)) import Data.Functor.Alt (Alt (..))
import Data.Maybe (maybeToList) import Data.Maybe (maybeToList)
import Data.Monoid ((<>)) import Data.Monoid ((<>))
@ -34,6 +36,7 @@ import Data.Proxy (Proxy (..))
import Data.Sequence (fromList) import Data.Sequence (fromList)
import Data.String (fromString) import Data.String (fromString)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Time.Clock (getCurrentTime)
import GHC.Generics import GHC.Generics
import Network.HTTP.Media (renderHeader) import Network.HTTP.Media (renderHeader)
import Network.HTTP.Types (hContentType, renderQuery, import Network.HTTP.Types (hContentType, renderQuery,
@ -47,6 +50,7 @@ data ClientEnv
= ClientEnv = ClientEnv
{ manager :: Client.Manager { manager :: Client.Manager
, baseUrl :: BaseUrl , baseUrl :: BaseUrl
, cookieJar :: Maybe (TVar Client.CookieJar)
} }
-- | Generates a set of client functions for an API. -- | Generates a set of client functions for an API.
@ -104,12 +108,30 @@ performRequest :: Request -> ClientM Response
performRequest req = do performRequest req = do
m <- asks manager m <- asks manager
burl <- asks baseUrl burl <- asks baseUrl
let request = requestToClientRequest burl req cookieJar' <- asks cookieJar
now <- liftIO getCurrentTime
request <- let clientRequest = requestToClientRequest burl req in
case cookieJar' of
Nothing -> pure clientRequest
Just cj -> liftIO . 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 eResponse <- liftIO $ catchConnectionError $ Client.httpLbs request m
case eResponse of case eResponse of
Left err -> throwError $ err Left err -> throwError $ err
Right response -> do Right response -> do
now' <- liftIO getCurrentTime
traverse_
(liftIO . atomically . flip modifyTVar'
(fst . Client.updateCookieJar response request now'))
cookieJar'
let status = Client.responseStatus response let status = Client.responseStatus response
status_code = statusCode status status_code = statusCode status
ourResponse = clientResponseToResponse response ourResponse = clientResponseToResponse response

View file

@ -302,7 +302,7 @@ manager' :: C.Manager
manager' = unsafePerformIO $ C.newManager C.defaultManagerSettings manager' = unsafePerformIO $ C.newManager C.defaultManagerSettings
runClient :: ClientM a -> BaseUrl -> IO (Either ServantError a) runClient :: ClientM a -> BaseUrl -> IO (Either ServantError a)
runClient x baseUrl' = runClientM x (ClientEnv manager' baseUrl') runClient x baseUrl' = runClientM x (ClientEnv manager' baseUrl' Nothing)
sucessSpec :: Spec sucessSpec :: Spec
sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do sucessSpec = beforeAll (startWaiApp server) $ afterAll endWaiApp $ do

View file

@ -92,7 +92,7 @@ manager' :: C.Manager
manager' = unsafePerformIO $ C.newManager C.defaultManagerSettings manager' = unsafePerformIO $ C.newManager C.defaultManagerSettings
runClient :: ClientM a -> BaseUrl -> IO (Either ServantError a) runClient :: ClientM a -> BaseUrl -> IO (Either ServantError a)
runClient x baseUrl' = runClientM x (ClientEnv manager' baseUrl') runClient x baseUrl' = runClientM x (ClientEnv manager' baseUrl' Nothing)
runResultStream :: ResultStream a -> IO (Maybe (Either String a), Maybe (Either String a), Maybe (Either String a), Maybe (Either String a)) runResultStream :: ResultStream a -> IO (Maybe (Either String a), Maybe (Either String a), Maybe (Either String a), Maybe (Either String a))
runResultStream (ResultStream k) = k $ \act -> (,,,) <$> act <*> act <*> act <*> act runResultStream (ResultStream k) = k $ \act -> (,,,) <$> act <*> act <*> act <*> act

View file

@ -1,5 +1,6 @@
servant servant
servant-server servant-server
servant-client servant-client
servant-client-core
servant-docs servant-docs
servant-foreign servant-foreign