@@ -20,41 +20,37 @@ import Control.Monad
20import Control.Monad.Base (MonadBase (..))
20import Control.Monad.Base (MonadBase (..))
21import Control.Monad.Catch (MonadCatch, MonadThrow)
21import Control.Monad.Catch (MonadCatch, MonadThrow)
22import Control.Monad.Error.Class (MonadError (..))
22import Control.Monad.Error.Class (MonadError (..))
23+import Data.ByteString.Builder (toLazyByteString)
24+import qualified Data.ByteString.Lazy as BSL
25+import Data.Monoid ((<>))
26+import Data.String (fromString)
27+import qualified Data.Text as T
28+import GHC.Exts (fromList)
23−import Control.Monad.IO.Class ()
29+{-import Control.Monad.IO.Class ()-}
24import Control.Monad.Reader
30import Control.Monad.Reader
25import Control.Monad.Trans.Control (MonadBaseControl (..))
31import Control.Monad.Trans.Control (MonadBaseControl (..))
26import Control.Monad.Trans.Except
32import Control.Monad.Trans.Except
27−import Data.ByteString.Lazy hiding (any, elem, filter, map,
33+{-import Data.ByteString.Lazy hiding (any, elem, filter, map,-}
28− null, pack)
34+ {-null, pack)-}
29import Data.Foldable (toList)
35import Data.Foldable (toList)
30import Data.Functor.Alt (Alt (..))
36import Data.Functor.Alt (Alt (..))
31import Data.Proxy
37import Data.Proxy
32−import Data.String.Conversions (cs)
38+{-import Data.String.Conversions (cs)-}
33import GHC.Generics
39import GHC.Generics
34−import Network.HTTP.Media
40+import Network.HTTP.Media (parseAccept, renderHeader, (//))
35−import Network.HTTP.Types
41+import Network.HTTP.Types (hContentType, renderQuery,
36−import Servant.API.ContentTypes
43+{-import Servant.API.ContentTypes-}
37−import Servant.Client.Class
44+import Servant.Client.Core
38−import Servant.Common.BaseUrl
45+{-import Servant.Common.BaseUrl-}
39−import Servant.Common.Req
46+{-import Servant.Common.Req-}
41import qualified Network.HTTP.Client as Client
48import qualified Network.HTTP.Client as Client
42−import qualified Network.HTTP.Types.Header as HTTP
49+{-import qualified Network.HTTP.Types.Header as HTTP-}
44−instance RunClient ClientM NoContent ( Int, ByteString, MediaType
45− , [HTTP.Header], Response ByteString) where
46− runRequest _ meth req = performRequest meth req
48−instance (MimeUnrender ct a) =>
49− RunClient ClientM ct ([HTTP.Header], a) where
50− runRequest p meth req = performRequestCT p meth req
52−instance RunClient ClientM NoContent [HTTP.Header] where
53− runRequest _ meth req = performRequestNoBody meth req
55data ClientEnv
51data ClientEnv
56 = ClientEnv
52 = ClientEnv
57− { manager :: Manager
53+ { manager :: Client.Manager
58 , baseUrl :: BaseUrl
54 , baseUrl :: BaseUrl
@@ -82,55 +78,83 @@ instance MonadBaseControl IO ClientM where
82instance Alt ClientM where
78instance Alt ClientM where
83 a <!> b = a `catchError` \_ -> b
79 a <!> b = a `catchError` \_ -> b
81+instance RunClient ClientM where
82+ runRequest = performRequest
85runClientM :: ClientM a -> ClientEnv -> IO (Either ServantError a)
84runClientM :: ClientM a -> ClientEnv -> IO (Either ServantError a)
86runClientM cm env = runExceptT $ (flip runReaderT env) $ runClientM' cm
85runClientM cm env = runExceptT $ (flip runReaderT env) $ runClientM' cm
89−performRequest :: Method -> Req
91−performRequest reqMethod req = do
93− reqHost <- asks baseUrl
94− partialRequest <- liftIO $ reqToRequest req reqHost
96− let request = partialRequest { Client.method = reqMethod }
89+performRequest :: Request -> ClientM Response
90+performRequest req = do
93+ let request = requestToClientRequest burl req
98 eResponse <- liftIO $ catchConnectionError $ Client.httpLbs request m
95 eResponse <- liftIO $ catchConnectionError $ Client.httpLbs request m
99 case eResponse of
96 case eResponse of
100− Left err ->
97+ Left err -> throwError $ err
101− throwError . ConnectionError $ SomeException err
103 Right response -> do
98 Right response -> do
104 let status = Client.responseStatus response
99 let status = Client.responseStatus response
105 body = Client.responseBody response
100 body = Client.responseBody response
106 hdrs = Client.responseHeaders response
101 hdrs = Client.responseHeaders response
107 status_code = statusCode status
102 status_code = statusCode status
103+ ourResponse = clientResponseToReponse response
108 ct <- case lookup "Content-Type" $ Client.responseHeaders response of
104 ct <- case lookup "Content-Type" $ Client.responseHeaders response of
109 Nothing -> pure $ "application"//"octet-stream"
105 Nothing -> pure $ "application"//"octet-stream"
110 Just t -> case parseAccept t of
106 Just t -> case parseAccept t of
111− Nothing -> throwError $ InvalidContentTypeHeader (cs t) body
107+ Nothing -> throwError $ InvalidContentTypeHeader ourResponse
112 Just t' -> pure t'
108 Just t' -> pure t'
113 unless (status_code >= 200 && status_code < 300) $
109 unless (status_code >= 200 && status_code < 300) $
114− throwError $ FailureResponse (UrlReq reqHost req) status ct body
110+ throwError $ FailureResponse ourResponse
115− return (status_code, body, ct, hdrs, response)
111+ return ourResponse
113+clientResponseToReponse :: Client.Response BSL.ByteString -> Response
114+clientResponseToReponse r = Response
115+ { responseStatusCode = Client.responseStatus r
116+ , responseBody = Client.responseBody r
117+ , responseHeaders = fromList $ Client.responseHeaders r
118+ , responseHttpVersion = Client.responseVersion r
121+requestToClientRequest :: BaseUrl -> Request -> Client.Request
122+requestToClientRequest burl r = Client.defaultRequest
123+ { Client.method = requestMethod r
124+ , Client.host = fromString $ baseUrlHost burl
125+ , Client.port = baseUrlPort burl
126+ , Client.path = BSL.toStrict
127+ $ fromString (baseUrlPath burl)
128+ <> toLazyByteString (requestPath r)
129+ , Client.queryString = renderQuery True . toList $ requestQueryString r
130+ , Client.requestHeaders =
131+ let orig = toList $ requestHeaders r
132+ in maybe orig (: orig) contentTypeHdr
133+ , Client.requestBody = body
136+ (body, contentTypeHdr) = case requestBody r of
137+ Nothing -> (Client.RequestBodyLBS "", Nothing)
138+ Just (RequestBodyLBS body, typ)
139+ -> (Client.RequestBodyLBS body, Just (hContentType, renderHeader typ))
117−performRequestCT :: MimeUnrender ct result => Proxy ct -> Method -> Req
141+{-performRequestCT :: MimeUnrender ct result => Proxy ct -> Method -> Req-}
118− -> ClientM ([HTTP.Header], result)
142+ {--> ClientM ([HTTP.Header], result)-}
119−performRequestCT ct reqMethod req = do
143+{-performRequestCT ct reqMethod req = do-}
120− let acceptCTS = contentTypes ct
144+ {-let acceptCTS = contentTypes ct-}
121− (_status, respBody, respCT, hdrs, _response) <-
145+ {-(_status, respBody, respCT, hdrs, _response) <--}
122− performRequest reqMethod (req { reqAccept = toList acceptCTS })
146+ {-performRequest reqMethod (req { reqAccept = toList acceptCTS })-}
123− unless (any (matches respCT) acceptCTS) $ throwError $ UnsupportedContentType respCT respBody
147+ {-unless (any (matches respCT) acceptCTS) $ throwError $ UnsupportedContentType respCT respBody-}
124− case mimeUnrender ct respBody of
148+ {-case mimeUnrender ct respBody of-}
125− Left err -> throwError $ DecodeFailure err respCT respBody
149+ {-Left err -> throwError $ DecodeFailure err respCT respBody-}
126− Right val -> return (hdrs, val)
150+ {-Right val -> return (hdrs, val)-}
128−performRequestNoBody :: Method -> Req -> ClientM [HTTP.Header]
152+{-performRequestNoBody :: Method -> Req -> ClientM [HTTP.Header]-}
129−performRequestNoBody reqMethod req = do
153+{-performRequestNoBody reqMethod req = do-}
130− (_status, _body, _ct, hdrs, _response) <- performRequest reqMethod req
154+ {-(_status, _body, _ct, hdrs, _response) <- performRequest reqMethod req-}
131− return hdrs
155+ {-return hdrs-}
133catchConnectionError :: IO a -> IO (Either ServantError a)
157catchConnectionError :: IO a -> IO (Either ServantError a)
134catchConnectionError action =
158catchConnectionError action =
135 catch (Right <$> action) $ \e ->
159 catch (Right <$> action) $ \e ->
136− pure . Left . ConnectionError $ SomeException (e :: HttpException)
160+ pure . Left . ConnectionError . T.pack $ show (e :: Client.HttpException)