{-# OPTIONS_HADDOCK prune #-} -- |Aborting the computation of 'Network.HTTP.Lucu.Resource.Resource' -- in any 'Prelude.IO' monads or arrows. module Network.HTTP.Lucu.Abortion ( Abortion(..) , abort , abortPurely , abortSTM , abortA , abortPage ) where import Control.Arrow import Control.Arrow.ArrowIO import Control.Concurrent.STM import Control.Exception import Control.Monad.Trans import qualified Data.ByteString.Char8 as C8 import Data.Dynamic import GHC.Conc (unsafeIOToSTM) import Network.HTTP.Lucu.Config import Network.HTTP.Lucu.DefaultPage import Network.HTTP.Lucu.Headers import Network.HTTP.Lucu.Request import Network.HTTP.Lucu.Response import System.IO.Unsafe import Text.XML.HXT.Arrow.WriteDocument import Text.XML.HXT.Arrow.XmlArrow import Text.XML.HXT.Arrow.XmlIOStateArrow import Text.XML.HXT.DOM.XmlKeywords data Abortion = Abortion { aboStatus :: !StatusCode , aboHeaders :: !Headers , aboMessage :: !(Maybe String) } deriving (Show, Typeable) instance Exception Abortion -- |Computation of @'abort' status headers msg@ aborts the -- 'Network.HTTP.Lucu.Resource.Resource' monad with given status, -- additional response headers, and optional message string. -- -- What this really does is to throw a special -- 'Control.Exception.Exception'. The exception will be caught by the -- Lucu system. -- -- 1. If the 'Network.HTTP.Lucu.Resource.Resource' is in the /Deciding -- Header/ or any precedent states, it is possible to use the -- @status@ and such like as a HTTP response to be sent to the -- client. -- -- 2. Otherwise the HTTP response can't be modified anymore so the -- only possible thing the system can do is to dump it to the -- stderr. See -- 'Network.HTTP.Lucu.Config.cnfDumpTooLateAbortionToStderr'. -- -- Note that the status code doesn't have to be an error code so you -- can use this action for redirection as well as error reporting e.g. -- -- > abort MovedPermanently -- > [("Location", "http://example.net/")] -- > (Just "It has been moved to example.net") abort :: MonadIO m => StatusCode -> [ (String, String) ] -> Maybe String -> m a abort status headers msg = status `seq` headers `seq` msg `seq` let abo = Abortion status (toHeaders $ map pack headers) msg in liftIO $ throwIO abo where pack (x, y) = (C8.pack x, C8.pack y) -- |This is similar to 'abort' but computes it with -- 'System.IO.Unsafe.unsafePerformIO'. abortPurely :: StatusCode -> [ (String, String) ] -> Maybe String -> a abortPurely = ((unsafePerformIO .) .) . abort -- |Computation of @'abortSTM' status headers msg@ just computes -- 'abort' in a 'Control.Monad.STM.STM' monad. abortSTM :: StatusCode -> [ (String, String) ] -> Maybe String -> STM a abortSTM status headers msg = status `seq` headers `seq` msg `seq` unsafeIOToSTM $! abort status headers msg -- | Computation of @'abortA' -< (status, (headers, msg))@ just -- computes 'abort' in an 'Control.Arrow.ArrowIO.ArrowIO'. abortA :: ArrowIO a => a (StatusCode, ([ (String, String) ], Maybe String)) c abortA = arrIO3 abort -- aboMessage が Just なら單に mkDefaultPage に渡すだけで良いので樂だが、 -- Nothing の場合は getDefaultPage を使ってデフォルトのメッセージを得な -- ければならない。 abortPage :: Config -> Maybe Request -> Response -> Abortion -> String abortPage conf reqM res abo = conf `seq` reqM `seq` res `seq` abo `seq` case aboMessage abo of Just msg -> let [html] = unsafePerformIO $ runX ( mkDefaultPage conf (aboStatus abo) (txt msg) >>> writeDocumentToString [(a_indent, v_1)] ) in html Nothing -> let res' = res { resStatus = aboStatus abo } res'' = foldl (.) id [setHeader name value | (name, value) <- fromHeaders $ aboHeaders abo] $ res' in getDefaultPage conf reqM res''