@@ -1,14 +1,28 @@
1{-# LANGUAGE OverloadedStrings #-}
1{-# LANGUAGE OverloadedStrings #-}
2+{-# LANGUAGE ScopedTypeVariables #-}
2module Servant.Utils.Req where
3module Servant.Utils.Req where
4−import Control.Monad.Catch
5+import Control.Applicative
6+import Control.Concurrent
7+import Control.Exception
9+import Control.Monad.Catch (MonadThrow)
10+import Control.Monad.IO.Class
11+import Control.Monad.Trans.Either
13+import Data.Aeson.Parser
14+import Data.Aeson.Types
15+import Data.Attoparsec.ByteString
5import Data.ByteString.Lazy
16import Data.ByteString.Lazy
17+import Data.String.Conversions
6import Data.Text
18import Data.Text
7−import Network.HTTP.Client hiding (Proxy)
19+import Network.HTTP.Client
8import Network.HTTP.Types
20import Network.HTTP.Types
9import Network.URI
21import Network.URI
11import Servant.Utils.BaseUrl
22import Servant.Utils.BaseUrl
23+import System.IO.Unsafe
25+import qualified Network.HTTP.Client as Client
13data Req = Req
27data Req = Req
14 { reqPath :: String
28 { reqPath :: String
@@ -50,3 +64,58 @@ reqToRequest req (BaseUrl scheme host port) = fmap (setrqb . setQS ) $ parseUrl
51 setrqb r = r { requestBody = RequestBodyLBS (reqBody req) }
65 setrqb r = r { requestBody = RequestBodyLBS (reqBody req) }
52 setQS = setQueryString $ queryTextToQuery (qs req)
66 setQS = setQueryString $ queryTextToQuery (qs req)
69+-- * performing requests
71+{-# NOINLINE __manager #-}
72+__manager :: MVar Manager
73+__manager = unsafePerformIO (newManager defaultManagerSettings >>= newMVar)
75+__withGlobalManager :: (Manager -> IO a) -> IO a
76+__withGlobalManager action = modifyMVar __manager $ \ manager -> do
77+ result <- action manager
78+ return (manager, result)
80+performRequest :: FromJSON result =>
81+ Method -> Req -> Int -> BaseUrl -> EitherT String IO result
82+performRequest method req wantedStatus host = do
83+ partialRequest <- liftIO $ reqToRequest req host
85+ let request = partialRequest { Client.method = method
88+ eResponse <- liftIO $ __withGlobalManager $ \ manager ->
89+ catchStatusCodeException $
90+ Client.httpLbs request manager
93+ left (requestString ++ " failed with status: " ++ showStatus status)
96+ let status = Client.responseStatus response
97+ when (statusCode status /= wantedStatus) $
98+ left (requestString ++ " failed with status: " ++ showStatus status)
100+ (\ message -> left (requestString ++ " returned invalid json: " ++ message))
102+ (decodeLenient (Client.responseBody response))
105+ requestString = "HTTP " ++ cs method ++ " request"
106+ showStatus (Status code message) =
107+ show code ++ " - " ++ cs message
109+catchStatusCodeException :: IO a -> IO (Either Status a)
110+catchStatusCodeException action = catch (Right <$> action) $
112+ Client.StatusCodeException status _ _ ->
113+ return $ Left status
116+-- | Like 'Data.Aeson.decode' but allows all JSON values instead of just
117+-- objects and arrays.
118+decodeLenient :: FromJSON a => ByteString -> Either String a
119+decodeLenient input = do
120+ v :: Value <- parseOnly (Data.Aeson.Parser.value <* endOfInput) (cs input)
121+ parseEither parseJSON v