{-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
module Dhall.Import.HTTP where
import Control.Monad (join)
import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.Trans.State.Strict (StateT)
import Data.ByteString (ByteString)
import Data.CaseInsensitive (CI)
import Data.Dynamic (fromDynamic, toDyn)
import Data.Semigroup ((<>))
import Lens.Family.State.Strict (zoom)
import qualified Control.Monad.Trans.State.Strict as State
import qualified Data.Text as Text
import Dhall.Import.Types
#ifdef __GHCJS__
import qualified JavaScript.XHR
#else
import qualified Control.Exception
import qualified Data.Text.Lazy
import qualified Data.Text.Lazy.Encoding
#endif
#if MIN_VERSION_http_client(0,5,0)
import Network.HTTP.Client
(HttpException(..), HttpExceptionContent(..), Manager)
#else
import Network.HTTP.Client (HttpException(..), Manager)
#endif
import qualified Network.HTTP.Client as HTTP
import qualified Network.HTTP.Client.TLS as HTTP
import qualified Network.HTTP.Types.Status
mkPrettyHttpException :: HttpException -> PrettyHttpException
mkPrettyHttpException ex =
PrettyHttpException (renderPrettyHttpException ex) (toDyn ex)
renderPrettyHttpException :: HttpException -> String
#if MIN_VERSION_http_client(0,5,0)
renderPrettyHttpException (InvalidUrlException _ r) =
"\n"
<> "\ESC[1;31mError\ESC[0m: Invalid URL\n"
<> "\n"
<> "↳ " <> show r
renderPrettyHttpException (HttpExceptionRequest _ e) =
case e of
ConnectionFailure _ ->
"\n"
<> "\ESC[1;31mError\ESC[0m: Remote host not found\n"
InvalidDestinationHost host ->
"\n"
<> "\ESC[1;31mError\ESC[0m: Invalid remote host name\n"
<> "\n"
<> "↳ " <> show host <> "\n"
ResponseTimeout ->
"\n"
<> "\ESC[1;31mError\ESC[0m: The remote host took too long to respond\n"
StatusCodeException response _
| statusCode == 404 ->
"\n"
<> "\ESC[1;31mError\ESC[0m: Remote file not found\n"
| otherwise ->
"\n"
<> "\ESC[1;31mError\ESC[0m: Unexpected HTTP status code:\n"
<> "\n"
<> "↳ " <> show statusCode <> "\n"
where
statusCode =
Network.HTTP.Types.Status.statusCode
(HTTP.responseStatus response)
e' -> "\n" <> show e'
#else
renderPrettyHttpException e = case e of
FailedConnectionException2 _ _ _ e' ->
"\n"
<> "\ESC[1;31mError\ESC[0m: Wrong host\n"
<> "\n"
<> "↳ " <> show e'
InvalidDestinationHost host ->
"\n"
<> "\ESC[1;31mError\ESC[0m: Invalid host name\n"
<> "\n"
<> "↳ " <> show host
ResponseTimeout ->
"\ESC[1;31mError\ESC[0m: The host took too long to respond\n"
e' -> "\n"
<> show e'
#endif
needManager :: StateT (Status m) IO Manager
needManager = do
x <- zoom manager State.get
case join (fmap fromDynamic x) of
Just m -> return m
Nothing -> do
let settings = HTTP.tlsManagerSettings
#ifdef MIN_VERSION_http_client
#if MIN_VERSION_http_client(0,5,0)
{ HTTP.managerResponseTimeout = HTTP.responseTimeoutMicro (30 * 1000 * 1000) }
#else
{ HTTP.managerResponseTimeout = Just (30 * 1000 * 1000) }
#endif
#endif
m <- liftIO (HTTP.newManager settings)
zoom manager (State.put (Just (toDyn m)))
return m
fetchFromHttpUrl
:: String
-> Maybe [(CI ByteString, ByteString)]
-> StateT (Status m) IO (String, Text.Text)
#ifdef __GHCJS__
fetchFromHttpUrl url Nothing = do
(statusCode, body) <- liftIO (JavaScript.XHR.get (Text.pack url))
case statusCode of
200 -> return ()
_ -> fail (url <> " returned a non-200 status code: " <> show statusCode)
return (url, body)
fetchFromHttpUrl _ _ = do
fail "Dhall does not yet support custom headers when built using GHCJS"
#else
fetchFromHttpUrl url mheaders = do
m <- needManager
request <- liftIO (HTTP.parseUrlThrow url)
let requestWithHeaders =
case mheaders of
Nothing -> request
Just headers -> request { HTTP.requestHeaders = headers }
let io = HTTP.httpLbs requestWithHeaders m
let handler e = do
let _ = e :: HttpException
Control.Exception.throwIO (mkPrettyHttpException e)
response <- liftIO (Control.Exception.handle handler io)
let bytes = HTTP.responseBody response
case Data.Text.Lazy.Encoding.decodeUtf8' bytes of
Left err -> liftIO (Control.Exception.throwIO err)
Right text -> return (url, Data.Text.Lazy.toStrict text)
#endif