{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE TupleSections #-}
{-# LANGUAGE OverloadedStrings #-}
module Network.HTTP.Simple
(
httpBS
, httpLBS
, httpNoBody
, httpJSON
, httpJSONEither
, httpSink
, httpSource
, withResponse
, H.Header
, H.Query
, H.QueryItem
, H.Request
, H.RequestHeaders
, H.Response
, H.ResponseHeaders
, JSONException (..)
, H.HttpException (..)
, H.Proxy (..)
, H.defaultRequest
, H.parseRequest
, H.parseRequest_
, parseRequestThrow
, parseRequestThrow_
, setRequestMethod
, setRequestSecure
, setRequestHost
, setRequestPort
, setRequestPath
, addRequestHeader
, getRequestHeader
, setRequestHeader
, setRequestHeaders
, setRequestQueryString
, getRequestQueryString
, addToRequestQueryString
, setRequestBody
, setRequestBodyJSON
, setRequestBodyLBS
, setRequestBodySource
, setRequestBodyFile
, setRequestBodyURLEncoded
, H.setRequestIgnoreStatus
, H.setRequestCheckStatus
, setRequestBasicAuth
, setRequestManager
, setRequestProxy
, getResponseStatus
, getResponseStatusCode
, getResponseHeader
, getResponseHeaders
, getResponseBody
, httpLbs
) where
import qualified Data.ByteString as S
import qualified Data.ByteString.Lazy as L
import qualified Network.HTTP.Client as H
import qualified Network.HTTP.Client.Internal as HI
import qualified Network.HTTP.Client.TLS as H
import Network.HTTP.Client.Conduit (bodyReaderSource)
import qualified Network.HTTP.Client.Conduit as HC
import Control.Monad.IO.Unlift (MonadIO, liftIO, MonadUnliftIO, withRunInIO)
import Data.Aeson (FromJSON (..), Value)
import Data.Aeson.Parser (json')
import qualified Data.Aeson.Types as A
import qualified Data.Aeson as A
import qualified Data.Traversable as T
import Control.Exception (throw, throwIO, Exception)
import Data.Monoid
import Data.Typeable (Typeable)
import qualified Data.Conduit as C
import Data.Conduit (runConduit, (.|), ConduitM)
import qualified Data.Conduit.Attoparsec as C
import qualified Network.HTTP.Types as H
import Data.Int (Int64)
import Control.Monad.Trans.Resource (MonadResource, MonadThrow)
import qualified Control.Exception as E (bracket)
import Data.Void (Void)
import qualified Data.Attoparsec.ByteString as Atto
import qualified Data.Attoparsec.ByteString.Char8 as Atto8
httpBS :: MonadIO m => H.Request -> m (H.Response S.ByteString)
httpBS req = liftIO $ do
man <- H.getGlobalManager
fmap L.toStrict `fmap` H.httpLbs req man
httpLBS :: MonadIO m => H.Request -> m (H.Response L.ByteString)
httpLBS req = liftIO $ do
man <- H.getGlobalManager
H.httpLbs req man
httpNoBody :: MonadIO m => H.Request -> m (H.Response ())
httpNoBody req = liftIO $ do
man <- H.getGlobalManager
H.httpNoBody req man
httpJSON :: (MonadIO m, FromJSON a) => H.Request -> m (H.Response a)
httpJSON req = liftIO $ httpJSONEither req >>= T.mapM (either throwIO return)
httpJSONEither :: (MonadIO m, FromJSON a)
=> H.Request
-> m (H.Response (Either JSONException a))
httpJSONEither req = liftIO $ httpSink req' sink
where
req' = addRequestHeader H.hAccept "application/json" req
sink orig = fmap (\x -> fmap (const x) orig) $ do
eres1 <- C.sinkParserEither (json' <* (Atto8.skipSpace *> Atto.endOfInput))
case eres1 of
Left e -> return $ Left $ JSONParseException req' orig e
Right value ->
case A.fromJSON value of
A.Error e -> return $ Left $ JSONConversionException
req' (fmap (const value) orig) e
A.Success x -> return $ Right x
data JSONException
= JSONParseException H.Request (H.Response ()) C.ParseError
| JSONConversionException H.Request (H.Response Value) String
deriving (Show, Typeable)
instance Exception JSONException
httpSink :: MonadUnliftIO m
=> H.Request
-> (H.Response () -> ConduitM S.ByteString Void m a)
-> m a
httpSink req sink = withRunInIO $ \run -> do
man <- H.getGlobalManager
E.bracket
(H.responseOpen req man)
H.responseClose
$ \res -> run
$ runConduit
$ bodyReaderSource (getResponseBody res)
.| sink (fmap (const ()) res)
httpSource :: (MonadResource m, MonadIO n)
=> H.Request
-> (H.Response (C.ConduitM i S.ByteString n ())
-> C.ConduitM i o m r)
-> C.ConduitM i o m r
httpSource req withRes = do
man <- liftIO H.getGlobalManager
C.bracketP (H.responseOpen req man) H.responseClose
(withRes . fmap bodyReaderSource)
withResponse :: (MonadUnliftIO m, MonadIO n)
=> H.Request
-> (H.Response (C.ConduitM i S.ByteString n ()) -> m a)
-> m a
withResponse req withRes = withRunInIO $ \run -> do
man <- H.getGlobalManager
E.bracket
(H.responseOpen req man)
H.responseClose
(run . withRes . fmap bodyReaderSource)
parseRequestThrow :: MonadThrow m => String -> m HC.Request
parseRequestThrow = HC.parseUrlThrow
parseRequestThrow_ :: String -> HC.Request
parseRequestThrow_ = either throw id . HC.parseUrlThrow
httpLbs :: MonadIO m => H.Request -> m (H.Response L.ByteString)
httpLbs = httpLBS
setRequestMethod :: S.ByteString -> H.Request -> H.Request
setRequestMethod x req = req { H.method = x }
setRequestSecure :: Bool -> H.Request -> H.Request
setRequestSecure x req = req { H.secure = x }
setRequestHost :: S.ByteString -> H.Request -> H.Request
setRequestHost x r = r { H.host = x }
setRequestPort :: Int -> H.Request -> H.Request
setRequestPort x r = r { H.port = x }
setRequestPath :: S.ByteString -> H.Request -> H.Request
setRequestPath x r = r { H.path = x }
addRequestHeader :: H.HeaderName -> S.ByteString -> H.Request -> H.Request
addRequestHeader name val req =
req { H.requestHeaders = (name, val) : H.requestHeaders req }
getRequestHeader :: H.HeaderName -> H.Request -> [S.ByteString]
getRequestHeader name =
map snd . filter (\(x, _) -> x == name) . H.requestHeaders
setRequestHeader :: H.HeaderName -> [S.ByteString] -> H.Request -> H.Request
setRequestHeader name vals req =
req { H.requestHeaders =
filter (\(x, _) -> x /= name) (H.requestHeaders req)
++ (map (name, ) vals)
}
setRequestHeaders :: H.RequestHeaders -> H.Request -> H.Request
setRequestHeaders x req = req { H.requestHeaders = x }
getRequestQueryString :: H.Request -> H.Query
getRequestQueryString = H.parseQuery . H.queryString
setRequestQueryString :: H.Query -> H.Request -> H.Request
setRequestQueryString = H.setQueryString
addToRequestQueryString :: H.Query -> H.Request -> H.Request
addToRequestQueryString additions req = setRequestQueryString q req
where q = additions <> getRequestQueryString req
setRequestBody :: H.RequestBody -> H.Request -> H.Request
setRequestBody x req = req { H.requestBody = x }
setRequestBodyJSON :: A.ToJSON a => a -> H.Request -> H.Request
setRequestBodyJSON x req =
req { H.requestHeaders
= (H.hContentType, "application/json; charset=utf-8")
: filter (\(y, _) -> y /= H.hContentType) (H.requestHeaders req)
, H.requestBody = H.RequestBodyLBS $ A.encode x
}
setRequestBodyLBS :: L.ByteString -> H.Request -> H.Request
setRequestBodyLBS = setRequestBody . H.RequestBodyLBS
setRequestBodySource :: Int64
-> ConduitM () S.ByteString IO ()
-> H.Request
-> H.Request
setRequestBodySource len src req = req { H.requestBody = HC.requestBodySource len src }
setRequestBodyFile :: FilePath -> H.Request -> H.Request
setRequestBodyFile = setRequestBody . HI.RequestBodyIO . H.streamFile
setRequestBodyURLEncoded :: [(S.ByteString, S.ByteString)] -> H.Request -> H.Request
setRequestBodyURLEncoded = H.urlEncodedBody
setRequestBasicAuth :: S.ByteString
-> S.ByteString
-> H.Request
-> H.Request
setRequestBasicAuth = H.applyBasicAuth
setRequestManager :: H.Manager -> H.Request -> H.Request
setRequestManager x req = req { HI.requestManagerOverride = Just x }
setRequestProxy :: Maybe H.Proxy -> H.Request -> H.Request
setRequestProxy x req = req { H.proxy = x }
getResponseStatus :: H.Response a -> H.Status
getResponseStatus = H.responseStatus
getResponseStatusCode :: H.Response a -> Int
getResponseStatusCode = H.statusCode . getResponseStatus
getResponseHeader :: H.HeaderName -> H.Response a -> [S.ByteString]
getResponseHeader name = map snd . filter (\(x, _) -> x == name) . H.responseHeaders
getResponseHeaders :: H.Response a -> [(H.HeaderName, S.ByteString)]
getResponseHeaders = H.responseHeaders
getResponseBody :: H.Response a -> a
getResponseBody = H.responseBody