----------------------------------------------------------------------------- -- | -- Module : Network.FastCGI -- Copyright : (c) Bjorn Bringert 2004-2005 -- License : BSD-style (see the file libraries/network/LICENSE) -- -- Maintainer : bjorn@bringert.net -- Stability : experimental -- Portability : non-portable (uses FFI) -- -- Interface for FastCGI , using the fcgiapp API. -- ----------------------------------------------------------------------------- module Network.FastCGI ({-runFastCGIConcurrent,-} runFastCGIorCGI, runOneFastCGIorCGI, runFastCGI, runOneFastCGI, module Network.CGI) where import Control.Concurrent (forkOS) import Control.Exception as Exception (catch, finally, throwIO) import Control.Monad (when, liftM) import Data.Word (Word8) import Foreign ( Ptr, castPtr, nullPtr, plusPtr, peekArray0 , alloca , mallocBytes, free, throwIfNeg_, allocaBytes , mallocArray, reallocArray, withForeignPtr) import Foreign.C ( CInt, CString, CStringLen, CChar, withCStringLen , peekCString, castCCharToChar ) import Foreign.Storable ( Storable (..) ) import System.IO.Unsafe (unsafeInterleaveIO) import Network.CGI import Network.CGI.Monad (runCGIT) import Network.CGI.Protocol (runCGIEnvFPS) import qualified Data.ByteString as BS import qualified Data.ByteString.Base as BSB import qualified Data.ByteString.Base as Lazy (LazyByteString(LPS)) import qualified Data.ByteString.Lazy.Char8 as Lazy -- For debugging import Control.Concurrent (myThreadId) import Prelude hiding (log) import System.Mem (performGC) import System.IO (hPutStrLn, stderr) #include data FCGX_Stream = FCGX_Stream type StreamPtr = Ptr FCGX_Stream type Environ = Ptr CString foreign import ccall threadsafe "fcgiapp.h FCGX_IsCGI" fcgx_isCGI :: IO CInt foreign import ccall threadsafe "fcgiapp.h FCGX_GetStr" fcgx_getStr :: CString -> CInt -> StreamPtr -> IO CInt foreign import ccall threadsafe "fcgiapp.h FCGX_PutStr" fcgx_putStr :: CString -> CInt -> StreamPtr -> IO CInt -- -- This code uses the not MT safe functions of the fcgiapp API, -- to avoid getting intermittent SIGPIPEs. Dunno why that happens. -- foreign import ccall "fcgiapp.h FCGX_Accept" fcgx_accept :: Ptr StreamPtr -> Ptr StreamPtr -> Ptr StreamPtr -> Ptr Environ -> IO CInt foreign import ccall "fcgiapp.h FCGX_Finish" fcgx_finish :: IO () -- | Handle a single CGI request, or FastCGI requests in an infinite loop. -- This function only returns normally if it was a CGI request. -- This lets you use the same program -- as either a FastCGI or CGI program, depending on what the server -- treats it as. runFastCGIorCGI :: CGI CGIResult -> IO () runFastCGIorCGI f = do fcgi <- runOneFastCGIorCGI f if fcgi then performGC >> runFastCGIorCGI f else return () -- | Handle a single FastCGI or CGI request. This lets you use the same program -- as either a FastCGI or CGI program, depending on what the server -- treats it as. runOneFastCGIorCGI :: CGI CGIResult -> IO Bool -- ^ True if it was a FastCGI request, -- False if CGI. runOneFastCGIorCGI f = do x <- fcgx_isCGI if x /= 0 then runCGI f >> return False else runOneFastCGI f >> return True -- | Handle FastCGI requests in an infinite loop. runFastCGI :: CGI CGIResult -> IO () runFastCGI f = runOneFastCGI f >> performGC >> runFastCGI f -- | Handle a single FastCGI request. runOneFastCGI :: CGI CGIResult -> IO () runOneFastCGI f = do alloca (\inp -> alloca (\outp -> alloca (\errp -> alloca (\envp -> oneRequest f inp outp errp envp)))) oneRequest :: CGI CGIResult -> Ptr StreamPtr -> Ptr StreamPtr -> Ptr StreamPtr -> Ptr Environ -> IO () oneRequest f inp outp errp envp = do testReturn "FCGX_Accept" $ fcgx_accept inp outp errp envp ins <- peek inp outs <- peek outp errs <- peek errp env <- peek envp handleRequest f ins outs errs env fcgx_finish handleRequest :: CGI CGIResult -> StreamPtr -> StreamPtr -> StreamPtr -> Environ -> IO () handleRequest f ins outs errs env = do vars <- environToTable env input <- sRead ins output <- runCGIEnvFPS vars input (runCGIT f) sPutStr outs output -- -- The following code uses the MT safe fcgiapp API, -- but causes mod_fastcgi to get SIGPIPE once in a while. -- {- data FCGX_Request = FCGX_Request foreign import ccall threadsafe "fcgiapp.h FCGX_Init" fcgx_init :: IO CInt foreign import ccall threadsafe "fcgiapp.h FCGX_InitRequest" fcgx_initrequest :: Ptr FCGX_Request -> CInt -> CInt -> IO CInt foreign import ccall threadsafe "fcgiapp.h FCGX_Accept_r" fcgx_accept_r :: Ptr FCGX_Request -> IO CInt foreign import ccall threadsafe "fcgiapp.h FCGX_Finish_r" fcgx_finish_r :: Ptr FCGX_Request -> IO () -- | Like 'Network.NewCGI.runCGI', but uses the FastCGI interface. runFastCGI :: CGI CGIResult -> IO () runFastCGI = runFastCGI' id -- | Like 'Network.NewCGI.runCGI', but uses the FastCGI interface -- and forks off a new thread (using 'forkOS') for every request. runFastCGIConcurrent :: CGI CGIResult -> IO () runFastCGIConcurrent = runFastCGI' fork where fork m = do t <- forkOS m log $ "Created child: " ++ show t runFastCGI' :: (IO () -> IO a) -> CGI CGIResult -> IO () runFastCGI' fork f = do log "Calling FCGX_Init" testReturn "FCGX_Init" $ fcgx_init loop where loop = do reqp <- acceptRequest log "Forking" fork (oneRequest f reqp `finally` finishRequest reqp) loop oneRequest :: CGI CGIResult -> Ptr FCGX_Request -> IO () oneRequest f r = do env <- peekEnvp r vars <- environToTable env ins <- peekIn r input <- sStrictRead ins log "Running CGI action" output <- runCGIEnv vars input f outs <- peekOut r log "Returning output" sPutStr outs output -- -- * FCGX_Reqest struct -- acceptRequest :: IO (Ptr FCGX_Request) acceptRequest = do log "Allocating FCGX_Request" reqp <- mallocBytes (#size FCGX_Request) initAndAccept reqp `onError` free reqp return reqp where initAndAccept reqp = do log "Calling FCGX_InitRequest" testReturn "FCGX_InitRequest" $ fcgx_initrequest reqp 0 0 log "Calling FCGX_Accept_r" testReturn "FCGX_Accept_r" $ fcgx_accept_r reqp finishRequest :: Ptr FCGX_Request -> IO () finishRequest reqp = do log "Calling FCGX_Finish_r" fcgx_finish_r reqp log "Freeing FCGX_Request" free reqp peekIn, peekOut, peekErr :: Ptr FCGX_Request -> IO (Ptr FCGX_Stream) peekIn = (#peek FCGX_Request, in) peekOut = (#peek FCGX_Request, out) peekErr = (#peek FCGX_Request, err) peekEnvp :: Ptr FCGX_Request -> IO Environ peekEnvp = (#peek FCGX_Request, envp) -} -- -- * Stream IO -- sPutStr :: StreamPtr -> Lazy.ByteString -> IO () sPutStr h (Lazy.LPS strs) = mapM_ (flip BSB.unsafeUseAsCStringLen (fcgxPutCStringLen h)) strs fcgxPutCStringLen :: StreamPtr -> CStringLen -> IO () fcgxPutCStringLen h (cs,len) = testReturn "FCGX_PutStr" $ fcgx_putStr cs (fromIntegral len) h {- -- Based on Data.FastPackedString.hGetContents sRead :: StreamPtr -> IO BS.ByteString sRead h = do let start_size = 1024 p <- mallocArray start_size i <- fcgx_getStr p start_size h if i < start_size then liftM BS.packMallocCString $ reallocArray p i else f p start_size where f p s = do let s' = 2 * s p' <- reallocArray p s' i <- fcgx_getStr (p' `plusPtr` s) s h if i < s then liftM BS.packMallocCString $ reallocArray p' (s + i) else f p' s' -} sRead :: StreamPtr -> IO Lazy.ByteString sRead h = buildByteString (fcgxGetBuf h) 4096 fcgxGetBuf :: StreamPtr -> Ptr a -> Int -> IO Int fcgxGetBuf h p c = liftM fromIntegral $ fcgx_getStr (castPtr p) (fromIntegral c) h -- -- * ByteString utilities -- -- | Data.ByteString.Lazy.hGetContentsN generalized to arbitrary -- reading functions. buildByteString :: (Ptr Word8 -> Int -> IO Int) -> Int -> IO Lazy.ByteString buildByteString f k = lazyRead >>= return . Lazy.LPS where lazyRead = unsafeInterleaveIO $ do ps <- BSB.createAndTrim k $ \p -> f p k case BS.length ps of 0 -> return [] n | n < k -> return [ps] _ -> do pss <- lazyRead return (ps : pss) -- -- * Utilities -- -- | Run some action if the first action throws an exception. -- The exception is re-thrown. onError :: IO a -> IO () -> IO a onError f h = f `Exception.catch` (\e -> h >> throwIO e) testReturn :: String -> IO CInt -> IO () testReturn e = throwIfNeg_ (const $ e ++ " failed") environToTable :: Environ -> IO [(String,String)] environToTable arr = do css <- peekArray0 nullPtr arr ss <- mapM peekCString css return $ map (splitBy '=') ss -- | Split a list at the first occurence of a marker. -- Do not include the marker in any of the resulting lists. -- If the marker does not occur in the list, the entire -- input with be in the first list. splitBy :: Eq a => a -> [a] -> ([a],[a]) splitBy x xs = (y, drop 1 z) where (y,z) = break (==x) xs -- -- * Debugging -- log :: String -> IO () log msg = do t <- myThreadId hPutStrLn stderr (show t ++ ": " ++ msg)