packages feed

second-transfer-0.10.0.2: hs-src/SecondTransfer/Exception.hs

{-# LANGUAGE DeriveDataTypeable, ExistentialQuantification, ScopedTypeVariables #-}
{-|
Module      : SecondTransfer.Exception
-}
module SecondTransfer.Exception (
    -- * Exceptions thrown by the HTTP/2 sessions
      HTTP2SessionException                       (..)
    , FramerException                             (..)
    , BadPrefaceException                         (..)
    , HTTP11Exception                             (..)
    , HTTP11SyntaxException                       (..)
    , ClientSessionAbortedException               (..)
    , HTTP500PrecursorException                   (..)
    , GatewayAbortedException                     (..)
    , ConnectionCloseReason                       (..)
    , convertHTTP500PrecursorExceptionToException
    , getHTTP500PrecursorExceptionFromException
    , ContentLengthMissingException               (..)

      -- * Exceptions related to the IO layer
    , IOProblem                                   (..)
    , GenericIOProblem                            (..)
    , StreamCancelledException                    (..)
    , NoMoreDataException                         (..)

      -- * Exceptions related to SOCKS5
    , SOCKS5ProtocolException                     (..)

      -- * Internal exceptions
    , HTTP2ProtocolException                      (..)

      -- * Utility functions
    , ignoreException
    , reportExceptions
    , keyedReportExceptions
    , forkIOExc

      -- * Proxies
    , blockedIndefinitelyOnMVar
    , blockedIndefinitelyOnSTM
    , noMoreDataException
    , ioProblem
    , gatewayAbortedException
    , ioException
    ) where

import           Control.Exception
import           Data.Typeable
import           Control.Concurrent               (forkIO, ThreadId)


-- | Abstract exception. All HTTP/2 exceptions derive from here
data HTTP2SessionException = forall e . Exception e => HTTP2SessionException e
    deriving Typeable

instance Show HTTP2SessionException where
    show (HTTP2SessionException e) = show e

instance Exception HTTP2SessionException


convertHTTP2SessionExceptionToException :: Exception e => e -> SomeException
convertHTTP2SessionExceptionToException = toException . HTTP2SessionException


getHTTP2SessionExceptionFromException :: Exception e => SomeException -> Maybe e
getHTTP2SessionExceptionFromException x = do
    HTTP2SessionException a <- fromException x
    cast a


-- | Concrete exception. Used internally to signal that the client violated
--   the protocol. Clients of the library shall never see this exception.
data HTTP2ProtocolException = HTTP2ProtocolException
    deriving (Typeable, Show)

instance Exception HTTP2ProtocolException where
    toException   = convertHTTP2SessionExceptionToException
    fromException = getHTTP2SessionExceptionFromException

-- TODO below: add the other protocol reasons

-- | Reasons for a remote server interrupting a connectionn of this client
data ConnectionCloseReason =
    NormalTermination_CCR     -- ^ Corresponds to NO_ERROR
    |SessionAlreadyClosed_CCR -- ^ A request was done after the session was previously closed.
    |IOChannelClosed_CCR      -- ^ This one happens when one of the IO channels is closed and a BlockedIndefinitelyOnMVar bubbles up. It should only happen in the test suite, as the OpenSSL_TLS channel uses a specialized exception type. If you see it in the wild, it is a bug.
    |ProtocolError_CCR        -- ^ Any other reason
    deriving Show

-- | Concrete Exception. Used internally to signal that the server broke
--   the connection. This is a public exception that clients of the library
--   will see when acting as an HTTP client.
data ClientSessionAbortedException = ClientSessionAbortedException ConnectionCloseReason
    deriving (Typeable, Show)

instance Exception ClientSessionAbortedException where
    toException = convertHTTP2SessionExceptionToException
    fromException = getHTTP2SessionExceptionFromException

-- | Abstract exception. Thrown when encoding/decoding of a frame fails
data FramerException = forall e . Exception e => FramerException e
    deriving Typeable

instance Show FramerException where
    show (FramerException e) = show e

instance Exception FramerException where
    toException = convertHTTP2SessionExceptionToException
    fromException = getHTTP2SessionExceptionFromException

convertFramerExceptionToException :: Exception e => e -> SomeException
convertFramerExceptionToException = toException . FramerException

getFramerExceptionFromException :: Exception e => SomeException -> Maybe e
getFramerExceptionFromException x = do
    FramerException a <- fromException x
    cast a


-- | Thrown when the HTTP/2 connection prefix doesn't
--   match the expected prefix.
data BadPrefaceException = BadPrefaceException
    deriving (Typeable, Show)

instance Exception BadPrefaceException where
    toException   = convertFramerExceptionToException
    fromException = getFramerExceptionFromException


-- | Abstract exception. All HTTP/1.1 related exceptions derive from here.
--   Notice that this includes a lot of logical errors and they can be
--   raised when handling HTTP/2 sessions as well
data HTTP11Exception = forall e . Exception e => HTTP11Exception e
    deriving Typeable

instance Show HTTP11Exception where
    show (HTTP11Exception e) = show e

instance Exception HTTP11Exception

convertHTTP11ExceptionToException :: Exception e => e -> SomeException
convertHTTP11ExceptionToException = toException . HTTP11Exception

getHTTP11ExceptionFromException :: Exception e => SomeException -> Maybe e
getHTTP11ExceptionFromException x = do
    HTTP11Exception a <- fromException x
    cast a

-- | Abstract exception. It is an error if an exception of this type bubbles
--   to this library, but we will do our best to handle it gracefully in the
--   Session engines.
--   All internal error precursors at the workers can thus inherit from here
--   to have a fallback option in case they forget to handle the error. It should
--   also be used for the case of streaming requests that are interrupted by the
--   upstream server.
--   This exception inherits from HTTP11Exception
data HTTP500PrecursorException = forall e . Exception e => HTTP500PrecursorException e
    deriving Typeable

instance Show HTTP500PrecursorException where
    show (HTTP500PrecursorException e) = show e

-- | Use the traditional idiom if you need to derive from 'HTTP500PrecursorException',
--   this is one of the helpers
convertHTTP500PrecursorExceptionToException :: Exception e => e -> SomeException
convertHTTP500PrecursorExceptionToException = toException . HTTP500PrecursorException

-- | Use the traditional idiom if you need to derive from 'HTTP500PrecursorException',
--   this is one of the helpers
getHTTP500PrecursorExceptionFromException :: Exception e => SomeException -> Maybe e
getHTTP500PrecursorExceptionFromException x = do
    HTTP500PrecursorException a <- fromException x
    cast a

-- Here we say how we go with these exceptions....
instance Exception HTTP500PrecursorException where
    toException = convertHTTP11ExceptionToException
    fromException = getHTTP11ExceptionFromException

-- | Used by the ReverseProxy to signal an error from the upstream/Gateway
data GatewayAbortedException = GatewayAbortedException
    deriving (Typeable, Show)

instance Exception GatewayAbortedException where
    toException = convertHTTP500PrecursorExceptionToException
    fromException = getHTTP11ExceptionFromException

gatewayAbortedException :: Proxy GatewayAbortedException
gatewayAbortedException = Proxy

-- | Thrown with HTTP/1.1 over HTTP/1.1 sessions when the response body
--   or the request body doesn't include a Content-Length header field,
--   given that should have included it
data ContentLengthMissingException = ContentLengthMissingException
    deriving (Typeable, Show)

instance Exception ContentLengthMissingException where
    toException = convertHTTP11ExceptionToException
    fromException = getHTTP11ExceptionFromException


-- Concrete exception
data HTTP11SyntaxException = HTTP11SyntaxException String
    deriving (Typeable, Show)

instance Exception HTTP11SyntaxException where
    toException = convertHTTP11ExceptionToException
    fromException = getHTTP11ExceptionFromException

-- | Throw exceptions derived from this (e.g, `GenericIOProblem` below)
--   to have the HTTP/2 session to terminate gracefully.
data IOProblem = forall e . Exception e => IOProblem e
    deriving Typeable

instance  Show IOProblem where
    show (IOProblem e) = show e

instance Exception IOProblem

ioProblem :: Proxy IOProblem
ioProblem = Proxy

-- | A concrete case of the above exception. Throw one of this
--   if you don't want to implement your own type. Use
--   `IOProblem` in catch signatures.
data GenericIOProblem = GenericIOProblem
    deriving (Show, Typeable)

instance Exception GenericIOProblem where
    toException = toException . IOProblem
    fromException x = do
        IOProblem a <- fromException x
        cast a

-- | This is raised by the IOCallbacks when the endpoint
--   is not willing to return or to accept more data
data NoMoreDataException = NoMoreDataException
    deriving (Show, Typeable)

instance Exception NoMoreDataException where
    toException = toException . IOProblem
    fromException x = do
        IOProblem  a <- fromException x
        cast a

noMoreDataException :: Proxy NoMoreDataException
noMoreDataException = Proxy

-- | This exception will be raised inside a `CoherentWorker` when the underlying
-- stream is cancelled (STREAM_RESET in HTTP\/2). Do any necessary cleanup
-- in a handler, or simply use the fact that the exception is asynchronously
-- delivered
-- to your CoherentWorker Haskell thread, giving you an opportunity to
-- interrupt any blocked operations.
data StreamCancelledException = StreamCancelledException
    deriving (Show, Typeable)

instance Exception StreamCancelledException


-- | Exception to denote that something failed with the SOCKS5 protocol
data SOCKS5ProtocolException = SOCKS5ProtocolException
    deriving (Show, Typeable)


instance Exception SOCKS5ProtocolException


-- | Simple utility function that ignores an exception. Good to work
--   on threads when we know stuff. It takes as a first parameter a
--   proxy.
ignoreException :: Exception e => Proxy e -> a -> IO a -> IO a
ignoreException prx default_value comp =
  let
    predicate :: Proxy e ->  e -> Maybe ()
    predicate _prx _ = Just ()
    in catchJust (predicate prx ) comp (const $ return default_value)


-- | Simple utility function that reports exceptions
reportExceptions :: forall a . IO a -> IO a
reportExceptions comp =
  do
    ei <- try comp
    case (ei :: Either SomeException a) of
        Left e@(SomeException ee) -> do
            putStrLn $ "Bubbling exc " ++ displayException e ++ " (with type " ++ (show $ typeOf ee) ++ ")"
            throwIO e

        Right a -> do
            return a


keyedReportExceptions :: forall a . String -> IO a -> IO a
keyedReportExceptions key comp =
  do
    ei <- try comp
    case (ei :: Either SomeException a) of
        Left e@(SomeException ee) -> do
            putStrLn $ "Bubbling exc " ++ displayException e ++ " (with type " ++ (show $ typeOf ee) ++ ") at " ++ key
            throwIO e

        Right a -> do
            return a

-- | Just report all unhandled and un-ignored exceptions
---  in forked threads
forkIOExc :: String -> IO () -> IO ThreadId
forkIOExc msg comp = forkIO $ keyedReportExceptions msg  comp


blockedIndefinitelyOnMVar :: Proxy BlockedIndefinitelyOnMVar
blockedIndefinitelyOnMVar = Proxy

blockedIndefinitelyOnSTM :: Proxy BlockedIndefinitelyOnSTM
blockedIndefinitelyOnSTM = Proxy

ioException :: Proxy IOException
ioException = Proxy