summaryrefslogtreecommitdiff
path: root/src/morpho-server/FastCGIUtils.hs
diff options
context:
space:
mode:
authorbjorn <bjorn@bringert.net>2008-08-24 19:31:12 +0000
committerbjorn <bjorn@bringert.net>2008-08-24 19:31:12 +0000
commitb3ab690dddc76f3a33cc9c48d300518e73af041d (patch)
treed93d3b1ef63dcd3ec1a65f9e8bfdd476fc9a41ef /src/morpho-server/FastCGIUtils.hs
parenta8f054657448348ef8564d06958269fd4cf1adb9 (diff)
First (hacky) working version of FastCGI JSON morphology server.
Diffstat (limited to 'src/morpho-server/FastCGIUtils.hs')
-rw-r--r--src/morpho-server/FastCGIUtils.hs141
1 files changed, 141 insertions, 0 deletions
diff --git a/src/morpho-server/FastCGIUtils.hs b/src/morpho-server/FastCGIUtils.hs
new file mode 100644
index 000000000..e95cad3f5
--- /dev/null
+++ b/src/morpho-server/FastCGIUtils.hs
@@ -0,0 +1,141 @@
+{-# LANGUAGE DeriveDataTypeable #-}
+module FastCGIUtils (initFastCGI, loopFastCGI,
+ DataRef, newDataRef, getData,
+ throwCGIError, handleCGIErrors) where
+
+import Control.Concurrent
+import Control.Exception
+import Control.Monad
+import Data.Dynamic
+import Data.IORef
+import Prelude hiding (catch)
+import System.Directory
+import System.Environment
+import System.Exit
+import System.IO
+import System.IO.Unsafe
+import System.Posix
+import System.Time
+
+
+import Network.FastCGI
+
+initFastCGI :: IO ()
+initFastCGI = installSignalHandlers
+
+loopFastCGI :: CGI CGIResult -> IO ()
+loopFastCGI f =
+ do (do runOneFastCGI f
+ exitIfToldTo
+ restartIfModified)
+ `catchAborted` logError "Request aborted"
+ loopFastCGI f
+
+
+-- Signal handling for FastCGI programs.
+
+
+installSignalHandlers :: IO ()
+installSignalHandlers =
+ do t <- myThreadId
+ installHandler sigUSR1 (Catch gracefulExit) Nothing
+ installHandler sigTERM (Catch gracelessExit) Nothing
+ installHandler sigPIPE (Catch (requestAborted t)) Nothing
+ return ()
+
+{-# NOINLINE shouldExit #-}
+shouldExit :: IORef Bool
+shouldExit = unsafePerformIO $ newIORef False
+
+catchAborted :: IO a -> IO a -> IO a
+catchAborted x y = x `catch` \e -> case e of
+ ErrorCall "**aborted**" -> y
+ _ -> throw e
+
+requestAborted :: ThreadId -> IO ()
+requestAborted t = throwTo t (ErrorCall "**aborted**")
+
+gracelessExit :: IO ()
+gracelessExit = do logError "Graceless exit"
+ exitWith ExitSuccess
+
+gracefulExit :: IO ()
+gracefulExit =
+ do logError "Graceful exit"
+ writeIORef shouldExit True
+
+exitIfToldTo :: IO ()
+exitIfToldTo =
+ do b <- readIORef shouldExit
+ when b $ do logError "Exiting..."
+ exitWith ExitSuccess
+
+
+-- Restart handling for FastCGI programs.
+
+{-# NOINLINE myModTimeRef #-}
+myModTimeRef :: IORef EpochTime
+myModTimeRef = unsafePerformIO (getProgModTime >>= newIORef)
+
+-- FIXME: doesn't get directory
+myProgPath :: IO FilePath
+myProgPath = getProgName
+
+getProgModTime :: IO EpochTime
+getProgModTime = liftM modificationTime (myProgPath >>= getFileStatus)
+
+needsRestart :: IO Bool
+needsRestart = liftM2 (/=) (readIORef myModTimeRef) getProgModTime
+
+exitIfModified :: IO ()
+exitIfModified =
+ do restart <- needsRestart
+ when restart $ exitWith ExitSuccess
+
+restartIfModified :: IO ()
+restartIfModified =
+ do restart <- needsRestart
+ when restart $ do prog <- myProgPath
+ args <- getArgs
+ hPutStrLn stderr $ prog ++ " has been modified, restarting ..."
+ -- FIXME: setCurrentDirectory?
+ executeFile prog False args Nothing
+
+-- Utilities for getting and caching read-only data from disk.
+-- The data is reloaded when the file on disk has been modified.
+
+type DataRef a = IORef (Maybe (ClockTime, a))
+
+newDataRef :: MonadIO m => m (DataRef a)
+newDataRef = liftIO $ newIORef Nothing
+
+getData :: MonadIO m => (FilePath -> m a) -> DataRef a -> FilePath -> m a
+getData loadData ref file =
+ do t' <- liftIO $ getModificationTime file
+ m <- liftIO $ readIORef ref
+ case m of
+ Just (t,x) | t' == t -> return x
+ _ -> do logCGI $ "Loading " ++ show file ++ "..."
+ x <- loadData file
+ liftIO $ writeIORef ref (Just (t',x))
+ return x
+
+-- Logging
+
+logError :: String -> IO ()
+logError s = hPutStrLn stderr s
+
+-- * General CGI Error exception mechanism
+
+data CGIError = CGIError { cgiErrorCode :: Int, cgiErrorMessage :: String, cgiErrorText :: [String] }
+ deriving Typeable
+
+throwCGIError :: Int -> String -> [String] -> CGI a
+throwCGIError c m t = throwCGI $ DynException $ toDyn $ CGIError c m t
+
+handleCGIErrors :: CGI CGIResult -> CGI CGIResult
+handleCGIErrors x = x `catchCGI` \e -> case e of
+ DynException d -> case fromDynamic d of
+ Nothing -> throw e
+ Just (CGIError c m t) -> outputError c m t
+ _ -> throw e