@@ -0,0 +1,392 @@
1+{-# LANGUAGE ForeignFunctionInterface #-}
2+{-# LANGUAGE EmptyDataDecls #-}
3+-----------------------------------------------------------------------------
5+-- Module : Network.Wai.Handler.FastCGI
6+-- Copyright : (c) Bjorn Bringert 2004-2005, (c) Lemmih 2006
7+-- License : BSD-style (see the file libraries/network/LICENSE)
9+-- Maintainer : michael@snoyman.com
10+-- Stability : experimental
11+-- Portability : non-portable (uses FFI)
13+-- Interface for FastCGI <http://fastcgi.com/>, using the fcgiapp API.
14+-- Totally ripped off by Michael Snoyman to work with Hack, then WAI.
16+-----------------------------------------------------------------------------
17+module Network.Wai.Handler.FastCGI
19+ -- * Single-threaded interface
24+ -- * Concurrent interface
25+ , runFastCGIConcurrent
26+ , runFastCGIConcurrent'
30+import Data.ByteString.Lazy.Internal (defaultChunkSize)
31+import Control.Concurrent ( forkOS )
32+import Control.Concurrent.MVar
33+import Control.Concurrent.QSem
34+import Control.Exception as Exception (catch, finally)
35+import Control.Monad ( liftM )
36+import Data.Word (Word8)
37+import Foreign ( Ptr, castPtr, nullPtr, peekArray0
38+ , alloca, mallocBytes, free, throwIfNeg_)
39+import Foreign.C ( CInt, CString, CStringLen
41+import Foreign.Storable ( Storable (..) )
42+import System.IO.Unsafe (unsafeInterleaveIO,unsafePerformIO)
44+import qualified Network.Wai as W
45+import qualified Network.Wai.Enumerator as WE
46+import qualified Network.Wai.Source as WS
47+import qualified Network.Wai.Handler.CGI as CGI
49+import qualified Data.ByteString as BS
50+import qualified Data.ByteString.Char8 as B8
51+import qualified Data.ByteString.Lazy.Char8 as Lazy
52+#if __GLASGOW_HASKELL__ >= 608
53+import qualified Data.ByteString.Internal as BSB
54+import qualified Data.ByteString.Unsafe as BSB
56+import qualified Data.ByteString.Base as BSB
60+import Control.Concurrent ( myThreadId )
61+import Prelude hiding ( log, catch )
62+import System.IO ( hPutStrLn, stderr )
63+import qualified System.IO
64+import Control.Arrow ((***))
65+import Data.Char (toLower)
69+------------------------------------------------------------------------
72+type StreamPtr = Ptr FCGX_Stream
73+type Environ = Ptr CString
75+------------------------------------------------------------------------
77+foreign import ccall unsafe "fcgiapp.h FCGX_IsCGI" fcgx_isCGI
80+foreign import ccall unsafe "fcgiapp.h FCGX_GetStr" fcgx_getStr
81+ :: CString -> CInt -> StreamPtr -> IO CInt
83+foreign import ccall unsafe "fcgiapp.h FCGX_PutStr" fcgx_putStr
84+ :: CString -> CInt -> StreamPtr -> IO CInt
86+foreign import ccall threadsafe "fcgiapp.h FCGX_Accept" fcgx_accept
87+ :: Ptr StreamPtr -> Ptr StreamPtr -> Ptr StreamPtr -> Ptr Environ -> IO CInt
88+foreign import ccall unsafe "fcgiapp.h FCGX_Finish" fcgx_finish
91+------------------------------------------------------------------------
93+-- | Handle a single CGI request, or FastCGI requests in an infinite loop.
94+-- This function only returns normally if it was a CGI request.
95+-- This lets you use the same program
96+-- as either a FastCGI or CGI program, depending on what the server
98+runFastCGIorCGI :: W.Application -> IO ()
99+runFastCGIorCGI f = do fcgi <- runOneFastCGIorCGI f
100+ if fcgi then runFastCGIorCGI f
103+-- | Handle a single FastCGI or CGI request. This lets you use the same program
104+-- as either a FastCGI or CGI program, depending on what the server
106+runOneFastCGIorCGI :: W.Application
107+ -> IO Bool -- ^ True if it was a FastCGI request,
109+runOneFastCGIorCGI f =
111+ if x /= 0 then CGI.run f >> return False
112+ else runOneFastCGI f >> return True
114+-- | Handle FastCGI requests in an infinite loop.
115+runFastCGI :: W.Application -> IO ()
116+runFastCGI f = runOneFastCGI f >> runFastCGI f
118+-- | Handle a single FastCGI request.
119+runOneFastCGI :: W.Application -> IO ()
125+ oneRequest f inp outp errp envp))))
127+oneRequest :: W.Application
133+oneRequest f inp outp errp envp =
135+ testReturn "FCGX_Accept" $ fcgx_accept inp outp errp envp
140+ handleRequest f ins outs errs env
143+handleRequest :: W.Application
149+handleRequest f ins outs _errs env =
151+ vars <- environToTable env
153+ let hPut = sPutStr' outs
154+ run' vars input hPut f
160+foreign import ccall unsafe "fcgiapp.h FCGX_Init" fcgx_init
163+foreign import ccall unsafe "fcgiapp.h FCGX_InitRequest" fcgx_initrequest
164+ :: Ptr FCGX_Request -> CInt -> CInt -> IO CInt
166+foreign import ccall threadsafe "fcgiapp.h FCGX_Accept_r" fcgx_accept_r
167+ :: Ptr FCGX_Request -> IO CInt
169+foreign import ccall unsafe "fcgiapp.h FCGX_Finish_r" fcgx_finish_r
170+ :: Ptr FCGX_Request -> IO ()
172+-- | Like 'Network.CGI.runCGI', but uses the FastCGI interface
173+-- and forks off a new thread (using 'forkOS') for every request.
174+runFastCGIConcurrent :: Int -- ^ Max number of concurrent threads.
175+ -> W.Application -> IO ()
176+runFastCGIConcurrent = runFastCGIConcurrent' forkOS
178+runFastCGIConcurrent' :: (IO () -> IO a) -- ^ How to fork a request.
179+ -> Int -- ^ Max number of concurrent threads.
180+ -> W.Application -> IO ()
182+runFastCGIConcurrent' fork m f
183+ = do qsem <- newQSem m
184+ testReturn "FCGX_Init" $ fcgx_init
185+ let loop = do waitQSem qsem
186+ reqp <- acceptRequest
187+ fork (oneRequestMT f reqp
189+ (finishRequest reqp >> signalQSem qsem))
191+ loop -- FIXME `catch` \e -> log (show e)
193+oneRequestMT :: W.Application -> Ptr FCGX_Request -> IO ()
194+oneRequestMT app r = do
196+ vars <- environToTable env
200+ let hPut = sPutStr' outs
201+ run' vars input hPut app
203+-- * FCGX_Reqest struct
206+acceptRequest :: IO (Ptr FCGX_Request)
208+ reqp <- mallocBytes (#size FCGX_Request)
211+ where initAndAccept reqp = do
212+ testReturn "FCGX_InitRequest" $ fcgx_initrequest reqp 0 0
213+ testReturn "FCGX_Accept_r" $ fcgx_accept_r reqp
215+finishRequest :: Ptr FCGX_Request -> IO ()
216+finishRequest reqp = do
220+peekIn, peekOut, _peekErr :: Ptr FCGX_Request -> IO (Ptr FCGX_Stream)
221+peekIn = (#peek FCGX_Request, in)
222+peekOut = (#peek FCGX_Request, out)
223+_peekErr = (#peek FCGX_Request, err)
225+peekEnvp :: Ptr FCGX_Request -> IO Environ
226+peekEnvp = (#peek FCGX_Request, envp)
233+sPutStr' :: StreamPtr -> BS.ByteString -> IO ()
235+ BSB.unsafeUseAsCStringLen str $ fcgxPutCStringLen h
237+sPutStr :: StreamPtr -> Lazy.ByteString -> IO ()
239+ mapM_ (flip BSB.unsafeUseAsCStringLen (fcgxPutCStringLen h)) (Lazy.toChunks str)
241+fcgxPutCStringLen :: StreamPtr -> CStringLen -> IO ()
242+fcgxPutCStringLen h (cs,len) =
243+ testReturn "FCGX_PutStr" $ fcgx_putStr cs (fromIntegral len) h
245+sRead :: StreamPtr -> IO Lazy.ByteString
246+sRead h = buildByteString (fcgxGetBuf h) 4096
248+fcgxGetBuf :: StreamPtr -> Ptr a -> Int -> IO Int
250+ liftM fromIntegral $ fcgx_getStr (castPtr p) (fromIntegral c) h
253+-- * ByteString utilities
256+-- | Data.ByteString.Lazy.hGetContentsN generalized to arbitrary
257+-- reading functions.
258+buildByteString :: (Ptr Word8 -> Int -> IO Int) -> Int -> IO Lazy.ByteString
259+buildByteString f k = lazyRead >>= return . Lazy.fromChunks
261+ lazyRead = unsafeInterleaveIO $ do
262+ ps <- BSB.createAndTrim k $ \p -> f p k
263+ case BS.length ps of
265+ n | n < k -> return [ps]
266+ _ -> do pss <- lazyRead
273+testReturn :: String -> IO CInt -> IO ()
274+testReturn e = throwIfNeg_ (\n -> e ++ " failed with error code: "++ show n)
276+environToTable :: Environ -> IO [(String,String)]
278+ do css <- peekArray0 nullPtr arr
279+ ss <- mapM peekCString css
280+ return $ map (splitBy '=') ss
282+-- | Split a list at the first occurence of a marker.
283+-- Do not include the marker in any of the resulting lists.
284+-- If the marker does not occur in the list, the entire
285+-- input with be in the first list.
286+splitBy :: Eq a => a -> [a] -> ([a],[a])
287+splitBy x xs = (y, drop 1 z)
288+ where (y,z) = break (==x) xs
294+{-# NOINLINE logMutex #-}
296+logMutex = unsafePerformIO (newMVar ())
298+log :: String -> IO ()
301+ withMVar logMutex (const $ hPutStrLn stderr (show t ++ ": " ++ msg))
303+run' :: [(String, String)] -- ^ all variables
304+ -> Lazy.ByteString -- ^ responseBody of input
305+ -> (BS.ByteString -> IO ()) -- ^ destination for output
308+run' vars inputH hPut app = do
309+ let rmethod = safeRead W.GET $ lookup' "REQUEST_METHOD" vars
310+ pinfo = lookup' "PATH_INFO" vars
311+ qstring = lookup' "QUERY_STRING" vars
312+ servername = lookup' "SERVER_NAME" vars
313+ serverport = safeRead 80 $ lookup' "SERVER_PORT" vars
314+ contentLength = safeRead 0 $ lookup' "CONTENT_LENGTH" vars
316+ case lookup "REMOTE_HOST" vars of
319+ case lookup "REMOTE_ADDR" vars of
323+ case map toLower $ lookup' "SERVER_PROTOCOL" vars of
327+ { W.requestMethod = rmethod
328+ , W.pathInfo = B8.pack pinfo
329+ , W.queryString = B8.pack qstring
330+ , W.serverName = B8.pack servername
331+ , W.serverPort = serverport
332+ , W.requestHeaders = map (cleanupVarName *** B8.pack) vars
333+ , W.urlScheme = urlScheme'
334+ , W.requestBody = requestBodyLBS inputH contentLength
335+ , W.errorHandler = System.IO.hPutStr System.IO.stderr
336+ , W.remoteHost = B8.pack remoteHost'
337+ , W.httpVersion = W.HttpVersion BS.empty
340+ let h = W.responseHeaders res
341+ let h' = case lookup W.ContentType h of
342+ Nothing -> (W.ContentType, B8.pack "text/html; charset=utf-8")
345+ hPut $ B8.pack $ "Status: " ++ (show $ W.statusCode $ W.status res) ++ " "
346+ hPut $ W.statusMessage $ W.status res
347+ hPut $ B8.singleton '\n'
348+ mapM_ (printHeader hPut) h'
349+ hPut $ B8.singleton '\n'
350+ _ <- W.runEnumerator (WE.fromEitherFile (W.responseBody res)) (myPut hPut) ()
353+myPut :: (BS.ByteString -> IO ()) -> () -> BS.ByteString -> IO (Either () ())
354+myPut output () bs = output bs >> return (Right ())
356+printHeader :: (BS.ByteString -> IO ())
357+ -> (W.ResponseHeader, BS.ByteString)
359+printHeader f (x, y) = do
360+ f $ W.responseHeaderToBS x
363+ f $ B8.singleton '\n'
365+cleanupVarName :: String -> W.RequestHeader
366+cleanupVarName ('H':'T':'T':'P':'_':a:as) =
367+ W.requestHeaderFromBS $ B8.pack $ a : helper' as where
368+ helper' ('_':x:rest) = '-' : x : helper' rest
369+ helper' (x:rest) = toLower x : helper' rest
371+cleanupVarName "CONTENT_TYPE" = W.ReqContentType
372+cleanupVarName "CONTENT_LENGTH" = W.ReqContentLength
373+cleanupVarName "SCRIPT_NAME" = W.requestHeaderFromBS $ B8.pack "CGI-Script-Name"
374+cleanupVarName x = W.requestHeaderFromBS $ B8.pack x -- FIXME remove?
376+requestBodyLBS :: Lazy.ByteString -> Int -> W.Source
377+requestBodyLBS l len = go (Lazy.toChunks l) len
379+ go _ 0 = W.Source $ return Nothing
381+ let len' = len - BS.length l
382+ len'' = if len' < 0 then 0 else len'
383+ in W.Source $ return $ Just (l, go ls len'')
385+lookup' :: String -> [(String, String)] -> String
386+lookup' key pairs = fromMaybe "" $ lookup key pairs
388+safeRead :: Read a => a -> String -> a