-- | -- Module : Amazonka.Response -- Copyright : (c) 2013-2023 Brendan Hay -- License : Mozilla Public License, v. 2.0. -- Maintainer : Brendan Hay -- Stability : provisional -- Portability : non-portable (GHC extensions) -- -- Functions contained in this module fully consume the body and thus close -- the connection. This is needed to avoid hitting this issue: -- . -- -- The only exception is 'receiveBody', which passes a streaming response -- body to a callback and thus is not allowed to close the connection. Users -- of streaming functions are advised to be careful and consume the response -- body manually if they want the connection to be closed promptly. -- -- Note that using 'runResourceT' will always close the connection. module Amazonka.Response ( receiveNull, receiveEmpty, receiveXMLWrapper, receiveXML, receiveJSON, receiveBytes, receiveBody, ) where import Amazonka.Data import Amazonka.Prelude hiding (error) import Amazonka.Types import qualified Control.Monad.Trans.Except as Except import Control.Monad.Trans.Resource (liftResourceT) import qualified Data.Aeson as Aeson import qualified Data.ByteString.Lazy as LBS import Data.Conduit () import qualified Data.Conduit as Conduit import qualified Data.Conduit.Binary as Conduit.Binary import Data.Functor (($>)) import qualified Network.HTTP.Client as Client import Network.HTTP.Types (ResponseHeaders) import qualified Text.XML as XML receiveNull :: MonadResource m => AWSResponse a -> (ByteStringLazy -> IO ByteStringLazy) -> Service -> Proxy a -> ClientResponse ClientBody -> m (Either Error (ClientResponse (AWSResponse a))) receiveNull rs _ = stream $ \r _ _ _ -> liftIO (Client.responseClose r) $> Right rs receiveEmpty :: MonadResource m => (Int -> ResponseHeaders -> () -> Either String (AWSResponse a)) -> (ByteStringLazy -> IO ByteStringLazy) -> Service -> Proxy a -> ClientResponse ClientBody -> m (Either Error (ClientResponse (AWSResponse a))) receiveEmpty f _ = stream $ \r s h _ -> liftIO (Client.responseClose r) $> f s h () receiveXMLWrapper :: MonadResource m => Text -> (Int -> ResponseHeaders -> [XML.Node] -> Either String (AWSResponse a)) -> (ByteStringLazy -> IO ByteStringLazy) -> Service -> Proxy a -> ClientResponse ClientBody -> m (Either Error (ClientResponse (AWSResponse a))) receiveXMLWrapper n f = receiveXML (\s h x -> x .@ n >>= f s h) receiveXML :: MonadResource m => (Int -> ResponseHeaders -> [XML.Node] -> Either String (AWSResponse a)) -> (ByteStringLazy -> IO ByteStringLazy) -> Service -> Proxy a -> ClientResponse ClientBody -> m (Either Error (ClientResponse (AWSResponse a))) receiveXML = deserialise decodeXML receiveJSON :: MonadResource m => (Int -> ResponseHeaders -> Aeson.Object -> Either String (AWSResponse a)) -> (ByteStringLazy -> IO ByteStringLazy) -> Service -> Proxy a -> ClientResponse ClientBody -> m (Either Error (ClientResponse (AWSResponse a))) receiveJSON = deserialise Aeson.eitherDecode' receiveBytes :: MonadResource m => (Int -> ResponseHeaders -> ByteString -> Either String (AWSResponse a)) -> (ByteStringLazy -> IO ByteStringLazy) -> Service -> Proxy a -> ClientResponse ClientBody -> m (Either Error (ClientResponse (AWSResponse a))) receiveBytes = deserialise (Right . LBS.toStrict) receiveBody :: MonadResource m => (Int -> ResponseHeaders -> ResponseBody -> Either String (AWSResponse a)) -> (ByteStringLazy -> IO ByteStringLazy) -> Service -> Proxy a -> ClientResponse ClientBody -> m (Either Error (ClientResponse (AWSResponse a))) receiveBody f _ = stream $ \_ s h x -> pure (f s h (ResponseBody x)) -- | Deserialise an entire response body, such as an XML or JSON payload. deserialise :: MonadResource m => (ByteStringLazy -> Either String b) -> (Int -> ResponseHeaders -> b -> Either String (AWSResponse a)) -> (ByteStringLazy -> IO ByteStringLazy) -> Service -> Proxy a -> ClientResponse ClientBody -> m (Either Error (ClientResponse (AWSResponse a))) deserialise reader parser responseBodyHook Service {..} _ rs = Except.runExceptT $ do let status = Client.responseStatus rs headers = Client.responseHeaders rs body <- sinkLBS (Client.responseBody rs) >>= liftIO . responseBodyHook unless (check status) $ Except.throwE (error status headers body) case reader body >>= parser (fromEnum status) headers of Right ok -> pure (ok <$ rs) Left err -> Except.throwE $ SerializeError (SerializeError' abbrev status (Just body) err) -- | Stream a raw response body, such as an S3 object payload. stream :: MonadResource m => ( ClientResponse () -> Int -> ResponseHeaders -> ClientBody -> m (Either String (AWSResponse a)) ) -> Service -> Proxy a -> ClientResponse ClientBody -> m (Either Error (ClientResponse (AWSResponse a))) stream parser Service {..} _ rs = Except.runExceptT $ do let status = Client.responseStatus rs headers = Client.responseHeaders rs body = Client.responseBody rs unless (check status) $ do lazy <- sinkLBS body Except.throwE (error status headers lazy) lift (parser (void rs) (fromEnum status) headers body) >>= \case Right ok -> pure (ok <$ rs) Left err -> Except.throwE $ SerializeError (SerializeError' abbrev status Nothing err) sinkLBS :: MonadResource m => ClientBody -> m ByteStringLazy sinkLBS bdy = liftResourceT (bdy `Conduit.connect` Conduit.Binary.sinkLbs)