1
0
forked from GitHub/gf-core

src/server: refactoring to isolate dependencies on the cgi/fastcgi packages

* Introducing the module CGI, re-exporting a subset of the cgi package. It
  might complete replace the cgi package in the future.
* Introducing the module CGIUtils, containing functions from FastCGIUtils that
  have nothing to do with fastcgi.

Some low level hackery with unsafePerformIO and global variables was left
in FastCGIUtils, but it is actually not used, neither for gf -server nor
exec/pgf-fcgi.hs.
This commit is contained in:
hallgren
2014-09-02 12:27:47 +00:00
parent 442dadf100
commit 0e87a88f4b
11 changed files with 149 additions and 128 deletions

View File

@@ -28,7 +28,7 @@ import Network.URI(URI(..))
import Network.Shed.Httpd(initServer,Request(..),Response(..),noCache) import Network.Shed.Httpd(initServer,Request(..),Response(..),noCache)
--import qualified Network.FastCGI as FCGI -- from hackage direct-fastcgi --import qualified Network.FastCGI as FCGI -- from hackage direct-fastcgi
import Network.CGI(handleErrors,liftIO) import Network.CGI(handleErrors,liftIO)
import FastCGIUtils(handleCGIErrors)--,outputJSONP,stderrToFile import CGIUtils(handleCGIErrors)--,outputJSONP,stderrToFile
import Text.JSON(encode,showJSON,makeObj) import Text.JSON(encode,showJSON,makeObj)
--import System.IO.Silently(hCapture) --import System.IO.Silently(hCapture)
import System.Process(readProcessWithExitCode) import System.Process(readProcessWithExitCode)

View File

@@ -9,7 +9,7 @@ import PGF
import GF.Compile.ToAPI import GF.Compile.ToAPI
import Network.CGI import Network.CGI
import Text.JSON import Text.JSON
import FastCGIUtils import CGIUtils
import Cache import Cache
import qualified ExampleDemo as E import qualified ExampleDemo as E

11
src/server/CGI.hs Normal file
View File

@@ -0,0 +1,11 @@
-- | Isolate dependencies on the problematic cgi package to this module
module CGI(module C) where
import Network.CGI as C(
CGI,ContentType(..),Accept(..),Language(..),
getVarWithDefault,readInput,negotiate,requestAcceptLanguage,getInput,
setHeader,output,outputFPS,outputError,
handleErrors,catchCGI,throwCGI,
liftIO)
import Network.CGI.Protocol as C(CGIResult(..),CGIRequest(..),Input(..),
Headers,HeaderName(..))
import Network.CGI.Monad as C(runCGIT)

103
src/server/CGIUtils.hs Normal file
View File

@@ -0,0 +1,103 @@
{-# LANGUAGE DeriveDataTypeable, CPP #-}
-- | CGI utility functions for output, error handling and logging
module CGIUtils (throwCGIError, handleCGIErrors,
stderrToFile,logError,
outputJSONP,outputEncodedJSONP,
outputPNG,outputBinary,outputBinary',
outputHTML,outputPlain) where
import Control.Exception(Exception(..),SomeException(..),throw)
import Data.Dynamic(Typeable,cast)
import Prelude hiding (catch)
import System.IO(hPutStrLn,stderr)
#ifndef mingw32_HOST_OS
import System.Posix
#endif
import CGI(CGI,CGIResult,setHeader,output,outputFPS,outputError,
getInput,catchCGI,throwCGI)
import Text.JSON
import qualified Codec.Binary.UTF8.String as UTF8 (encodeString)
import qualified Data.ByteString.Lazy as BS
-- * Logging
#ifndef mingw32_HOST_OS
logError :: String -> IO ()
logError s = hPutStrLn stderr s
stderrToFile :: FilePath -> IO ()
stderrToFile file =
do let mode = ownerReadMode<>ownerWriteMode<>groupReadMode<>otherReadMode
(<>) = unionFileModes
flags = defaultFileFlags { append = True }
fileFd <- openFd file WriteOnly (Just mode) flags
dupTo fileFd stdError
return ()
#else
logError :: String -> IO ()
logError s = return ()
stderrToFile :: FilePath -> IO ()
stderrToFile s = return ()
#endif
-- * General CGI Error exception mechanism
data CGIError = CGIError { cgiErrorCode :: Int, cgiErrorMessage :: String, cgiErrorText :: [String] }
deriving (Show,Typeable)
instance Exception CGIError where
toException e = SomeException e
fromException (SomeException e) = cast e
throwCGIError :: Int -> String -> [String] -> CGI a
throwCGIError c m t = throwCGI $ toException $ CGIError c m t
handleCGIErrors :: CGI CGIResult -> CGI CGIResult
handleCGIErrors x =
x `catchCGI` \e -> case fromException e of
Nothing -> throw e
Just (CGIError c m t) -> do setXO; outputError c m t
-- * General CGI and JSON stuff
outputJSONP :: JSON a => a -> CGI CGIResult
outputJSONP = outputEncodedJSONP . encode
outputEncodedJSONP :: String -> CGI CGIResult
outputEncodedJSONP json =
do mc <- getInput "jsonp"
let (ty,str) = case mc of
Nothing -> ("json",json)
Just c -> ("javascript",c ++ "(" ++ json ++ ")")
ct = "application/"++ty++"; charset=utf-8"
outputStrict ct $ UTF8.encodeString str
outputPNG :: BS.ByteString -> CGI CGIResult
outputPNG = outputBinary' "image/png"
outputBinary :: BS.ByteString -> CGI CGIResult
outputBinary = outputBinary' "application/binary"
outputBinary' :: String -> BS.ByteString -> CGI CGIResult
outputBinary' ct x = do
setHeader "Content-Type" ct
setXO
outputFPS x
outputHTML :: String -> CGI CGIResult
outputHTML = outputStrict "text/html; charset=utf-8" . UTF8.encodeString
outputPlain :: String -> CGI CGIResult
outputPlain = outputStrict "text/plain; charset=utf-8" . UTF8.encodeString
outputStrict :: String -> String -> CGI CGIResult
outputStrict ct x | x == x = do setHeader "Content-Type" ct
setXO
output x
| otherwise = fail "I am the pope."
setXO = setHeader "Access-Control-Allow-Origin" "*"
-- https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS

View File

@@ -1,35 +1,24 @@
{-# LANGUAGE DeriveDataTypeable, CPP #-} {-# LANGUAGE CPP #-}
module FastCGIUtils (--initFastCGI, loopFastCGI, module FastCGIUtils(initFastCGI,loopFastCGI) where
throwCGIError, handleCGIErrors,
stderrToFile,logError,
outputJSONP,outputEncodedJSONP,
outputPNG,outputBinary,
outputHTML,outputPlain,
splitBy) where
import Control.Concurrent import Control.Concurrent(ThreadId,myThreadId)
import Control.Exception import Control.Exception(ErrorCall(..),throw,throwTo,catch)
import Control.Monad import Control.Monad(when,liftM,liftM2)
import Data.Dynamic import Data.IORef(IORef,newIORef,readIORef,writeIORef)
import Data.IORef
import Prelude hiding (catch) import Prelude hiding (catch)
import System.Environment import System.Environment(getArgs,getProgName)
import System.Exit import System.Exit(ExitCode(..),exitWith)
import System.IO import System.IO(hPutStrLn,stderr)
import System.IO.Unsafe import System.IO.Unsafe(unsafePerformIO)
#ifndef mingw32_HOST_OS #ifndef mingw32_HOST_OS
import System.Posix import System.Posix
#endif #endif
--import Network.FastCGI import Network.FastCGI
import Network.CGI(CGI,CGIResult,setHeader,output,outputFPS,outputError,
getInput,catchCGI,throwCGI)
import Text.JSON import CGIUtils(logError)
import qualified Codec.Binary.UTF8.String as UTF8 (encodeString)
import qualified Data.ByteString.Lazy as BS
{- -- There are used in MorphoService.hs, but not in PGFService.hs -- There are used in MorphoService.hs, but not in PGFService.hs
initFastCGI :: IO () initFastCGI :: IO ()
initFastCGI = installSignalHandlers initFastCGI = installSignalHandlers
@@ -40,11 +29,9 @@ loopFastCGI f =
restartIfModified) restartIfModified)
`catchAborted` logError "Request aborted" `catchAborted` logError "Request aborted"
loopFastCGI f loopFastCGI f
-}
-- Signal handling for FastCGI programs. -- Signal handling for FastCGI programs.
#ifndef mingw32_HOST_OS #ifndef mingw32_HOST_OS
installSignalHandlers :: IO () installSignalHandlers :: IO ()
installSignalHandlers = installSignalHandlers =
@@ -121,89 +108,3 @@ restartIfModified :: IO ()
restartIfModified = return () restartIfModified = return ()
#endif #endif
-- Logging
#ifndef mingw32_HOST_OS
logError :: String -> IO ()
logError s = hPutStrLn stderr s
stderrToFile :: FilePath -> IO ()
stderrToFile file =
do let mode = ownerReadMode `unionFileModes` ownerWriteMode `unionFileModes` groupReadMode `unionFileModes` otherReadMode
fileFd <- openFd file WriteOnly (Just mode) (defaultFileFlags { append = True })
dupTo fileFd stdError
return ()
#else
logError :: String -> IO ()
logError s = return ()
stderrToFile :: FilePath -> IO ()
stderrToFile s = return ()
#endif
-- * General CGI Error exception mechanism
data CGIError = CGIError { cgiErrorCode :: Int, cgiErrorMessage :: String, cgiErrorText :: [String] }
deriving (Show,Typeable)
instance Exception CGIError where
toException e = SomeException e
fromException (SomeException e) = cast e
throwCGIError :: Int -> String -> [String] -> CGI a
throwCGIError c m t = throwCGI $ toException $ CGIError c m t
handleCGIErrors :: CGI CGIResult -> CGI CGIResult
handleCGIErrors x =
x `catchCGI` \e -> case fromException e of
Nothing -> throw e
Just (CGIError c m t) -> do setXO; outputError c m t
-- * General CGI and JSON stuff
outputJSONP :: JSON a => a -> CGI CGIResult
outputJSONP = outputEncodedJSONP . encode
outputEncodedJSONP :: String -> CGI CGIResult
outputEncodedJSONP json =
do mc <- getInput "jsonp"
let (ty,str) = case mc of
Nothing -> ("json",json)
Just c -> ("javascript",c ++ "(" ++ json ++ ")")
ct = "application/"++ty++"; charset=utf-8"
outputStrict ct $ UTF8.encodeString str
outputPNG :: BS.ByteString -> CGI CGIResult
outputPNG x = do
setHeader "Content-Type" "image/png"
setXO
outputFPS x
outputBinary :: BS.ByteString -> CGI CGIResult
outputBinary x = do
setHeader "Content-Type" "application/binary"
setXO
outputFPS x
outputHTML :: String -> CGI CGIResult
outputHTML = outputStrict "text/html; charset=utf-8" . UTF8.encodeString
outputPlain :: String -> CGI CGIResult
outputPlain = outputStrict "text/plain; charset=utf-8" . UTF8.encodeString
outputStrict :: String -> String -> CGI CGIResult
outputStrict ct x | x == x = do setHeader "Content-Type" ct
setXO
output x
| otherwise = fail "I am the pope."
setXO = setHeader "Access-Control-Allow-Origin" "*"
-- https://developer.mozilla.org/en-US/docs/HTTP/Access_control_CORS
-- * General utilities
splitBy :: (a -> Bool) -> [a] -> [[a]]
splitBy _ [] = [[]]
splitBy f list = case break f list of
(first,[]) -> [first]
(first,_:rest) -> first : splitBy f rest

View File

@@ -7,7 +7,11 @@ import PGF (PGF)
import qualified PGF import qualified PGF
import PGF.Lexing import PGF.Lexing
import Cache import Cache
import FastCGIUtils import CGIUtils(outputJSONP,outputPlain,outputHTML,logError,outputBinary,
outputBinary',handleCGIErrors,throwCGIError,stderrToFile)
import CGI(CGI,readInput,getInput,getVarWithDefault,
CGIResult,requestAcceptLanguage,handleErrors,setHeader,
Accept(..),Language(..),negotiate,liftIO)
import URLEncoding import URLEncoding
#if C_RUNTIME #if C_RUNTIME
@@ -18,7 +22,6 @@ import qualified PGF2 as C
import Data.Time.Clock(UTCTime) import Data.Time.Clock(UTCTime)
import Data.Time.Format(formatTime) import Data.Time.Format(formatTime)
import System.Locale(defaultTimeLocale,rfc822DateFormat) import System.Locale(defaultTimeLocale,rfc822DateFormat)
import Network.CGI
import Text.JSON import Text.JSON
import Text.PrettyPrint as PP(render, text, (<+>)) import Text.PrettyPrint as PP(render, text, (<+>))
import qualified Codec.Binary.UTF8.String as UTF8 (decodeString) import qualified Codec.Binary.UTF8.String as UTF8 (decodeString)
@@ -687,9 +690,7 @@ outputGraphviz code =
"gv" -> outputPlain code "gv" -> outputPlain code
_ -> outputFPS' fmt =<< liftIO (pipeIt2graphviz fmt code) _ -> outputFPS' fmt =<< liftIO (pipeIt2graphviz fmt code)
where where
outputFPS' fmt bs = outputFPS' = outputBinary' . mimeType
do setHeader "Content-Type" (mimeType fmt)
outputFPS bs
mimeType fmt = mimeType fmt =
case fmt of case fmt of

View File

@@ -1,9 +1,9 @@
module RunHTTP(runHTTP,Options(..),cgiHandler) where module RunHTTP(runHTTP,Options(..),cgiHandler) where
import Network.URI(uriPath,uriQuery) import Network.URI(uriPath,uriQuery)
import Network.CGI(ContentType(..)) import CGI(ContentType(..))
import Network.CGI.Protocol(CGIResult(..),CGIRequest(..),Input(..), import CGI(CGIResult(..),CGIRequest(..),Input(..),
Headers,HeaderName(..)) Headers,HeaderName(..))
import Network.CGI.Monad(runCGIT) import CGI(runCGIT)
import Network.Shed.Httpd(initServer,Request(..),Response(..)) import Network.Shed.Httpd(initServer,Request(..),Response(..))
import qualified Data.ByteString.Lazy.Char8 as BS(pack,unpack) import qualified Data.ByteString.Lazy.Char8 as BS(pack,unpack)
import qualified Data.Map as M(fromList) import qualified Data.Map as M(fromList)

View File

@@ -1,7 +1,7 @@
module ServeStaticFile where module ServeStaticFile where
import System.FilePath import System.FilePath
import System.Directory(doesDirectoryExist) import System.Directory(doesDirectoryExist)
import Network.CGI(setHeader,outputFPS,liftIO) import CGI(setHeader,outputFPS,liftIO)
import qualified Data.ByteString.Lazy.Char8 as BS import qualified Data.ByteString.Lazy.Char8 as BS
serveStaticFile path = serveStaticFile path =

View File

@@ -4,7 +4,7 @@ import Control.Monad(when)
import System.Directory(createDirectoryIfMissing,doesFileExist, import System.Directory(createDirectoryIfMissing,doesFileExist,
getDirectoryContents,copyFile,removeFile) getDirectoryContents,copyFile,removeFile)
import System.FilePath((</>)) import System.FilePath((</>))
import System.Cmd(system) import System.Process(system)
import System.Exit(ExitCode(..)) import System.Exit(ExitCode(..))
import Distribution.Simple import Distribution.Simple

View File

@@ -3,7 +3,7 @@ import Control.Concurrent(forkIO)
import Network.FastCGI(runFastCGI,runFastCGIConcurrent') import Network.FastCGI(runFastCGI,runFastCGIConcurrent')
import PGFService(cgiMain,newPGFCache,stderrToFile,logFile) import PGFService(cgiMain,newPGFCache,stderrToFile,logFile)
import System.IO
main = do stderrToFile logFile main = do stderrToFile logFile
fcgiMain =<< newPGFCache fcgiMain =<< newPGFCache

View File

@@ -21,10 +21,13 @@ flag c-runtime
Default: False Default: False
Library Library
exposed-modules: PGFService FastCGIUtils ServeStaticFile RunHTTP Cache exposed-modules: PGFService FastCGIUtils CGIUtils ServeStaticFile RunHTTP Cache
other-modules: URLEncoding Fold other-modules: URLEncoding CGI Fold
hs-source-dirs: . transfer hs-source-dirs: . transfer
build-depends: fastcgi >= 3001.0.2.2
-- Install it in Ubuntu with: apt-get install libghc-fastcgi-dev
build-depends: base >=4.2 && <5, build-depends: base >=4.2 && <5,
time, time-compat, old-locale, time, time-compat, old-locale,
directory, directory,
@@ -41,6 +44,8 @@ Library
bytestring, bytestring,
pretty, pretty,
random random
ghc-options: -fwarn-unused-imports
if os(windows) if os(windows)
ghc-options: -optl-mwindows ghc-options: -optl-mwindows
else else
@@ -65,7 +70,7 @@ executable pgf-http
executable pgf-service executable pgf-service
main-is: pgf-fcgi.hs main-is: pgf-fcgi.hs
Hs-source-dirs: exec Hs-source-dirs: exec
ghc-options: -threaded ghc-options: -threaded -fwarn-unused-imports
if impl(ghc>=7.0) if impl(ghc>=7.0)
ghc-options: -rtsopts ghc-options: -rtsopts