sscgi 0.1.0 → 0.2.0
raw patch · 2 files changed
+33/−24 lines, 2 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
- Network.SCGI: data SCGI a
- Network.SCGI: instance Functor SCGI
- Network.SCGI: instance Monad SCGI
- Network.SCGI: instance MonadIO SCGI
- Network.SCGI: instance MonadReader Headers SCGI
- Network.SCGI: instance MonadState Headers SCGI
+ Network.SCGI: data SCGIT m a
+ Network.SCGI: instance Monad m => Monad (SCGIT m)
+ Network.SCGI: instance Monad m => MonadReader Headers (SCGIT m)
+ Network.SCGI: instance Monad m => MonadState Headers (SCGIT m)
+ Network.SCGI: instance MonadIO m => MonadIO (SCGIT m)
+ Network.SCGI: instance MonadTrans SCGIT
+ Network.SCGI: type SCGI = SCGIT IO
- Network.SCGI: allHeaders :: SCGI [(ByteString, ByteString)]
+ Network.SCGI: allHeaders :: Monad m => SCGIT m [(ByteString, ByteString)]
- Network.SCGI: header :: ByteString -> SCGI (Maybe ByteString)
+ Network.SCGI: header :: Monad m => ByteString -> SCGIT m (Maybe ByteString)
- Network.SCGI: method :: SCGI (Maybe ByteString)
+ Network.SCGI: method :: Monad m => SCGIT m (Maybe ByteString)
- Network.SCGI: path :: SCGI (Maybe ByteString)
+ Network.SCGI: path :: Monad m => SCGIT m (Maybe ByteString)
- Network.SCGI: runRequest :: Handle -> (Body -> SCGI Response) -> IO ()
+ Network.SCGI: runRequest :: MonadIO m => Handle -> (Body -> SCGIT m Response) -> m ()
- Network.SCGI: setHeader :: ByteString -> ByteString -> SCGI ()
+ Network.SCGI: setHeader :: Monad m => ByteString -> ByteString -> SCGIT m ()
Files
- Network/SCGI.hs +32/−23
- sscgi.cabal +1/−1
Network/SCGI.hs view
@@ -1,13 +1,14 @@ -- Copyright 2013 Chris Forno -module Network.SCGI (SCGI, runRequest, header, allHeaders, method, path, setHeader, Headers, Body, Status, Response(..)) where+module Network.SCGI (SCGIT, SCGI, runRequest, header, allHeaders, method, path, setHeader, Headers, Body, Status, Response(..)) where import Control.Applicative ((<$>), (<*>), (<*)) import Control.Arrow (first) import Control.Monad (liftM, liftM2)-import Control.Monad.IO.Class (MonadIO)+import Control.Monad.IO.Class (MonadIO, liftIO) import Control.Monad.Reader (ReaderT, runReaderT, MonadReader, asks) import Control.Monad.State (StateT, runStateT, MonadState, modify)+import Control.Monad.Trans.Class (MonadTrans, lift) import Data.Attoparsec.ByteString.Char8 (Parser, IResult(..), parseOnly, parseWith, char, decimal, take, takeTill) import Data.Attoparsec.Combinator (many1) import qualified Data.ByteString as B@@ -28,52 +29,60 @@ type Status = BL.ByteString data Response = Response Status Body -newtype SCGI a = SCGI (ReaderT Headers (StateT Headers IO) a)- deriving (Functor, Monad, MonadIO, MonadState Headers, MonadReader Headers)+newtype SCGIT m a = SCGIT (ReaderT Headers (StateT Headers m) a)+ deriving (Monad, MonadState Headers, MonadReader Headers, MonadIO) -runSCGI :: Headers -> SCGI Response -> IO (Response, Headers)-runSCGI headers (SCGI r) = runStateT (runReaderT r headers) M.empty+type SCGI = SCGIT IO +instance MonadTrans SCGIT where+ lift = SCGIT . lift . lift++runSCGIT :: Monad m => Headers -> SCGIT m Response -> m (Response, Headers)+runSCGIT headers (SCGIT r) = runStateT (runReaderT r headers) M.empty+ -- |Lookup a request header.-header :: B.ByteString -- ^ the header name (key)- -> SCGI (Maybe B.ByteString) -- ^ the header value if found+header :: Monad m+ => B.ByteString -- ^ the header name (key)+ -> SCGIT m (Maybe B.ByteString) -- ^ the header value if found header name = asks (M.lookup (CI.mk name)) -- |Return all request headers as a list in the format they were received from the web server.-allHeaders :: SCGI [(B.ByteString, B.ByteString)] -- ^ an association list of header: value pairs+allHeaders :: Monad m => SCGIT m [(B.ByteString, B.ByteString)] -- ^ an association list of header: value pairs allHeaders = asks (map (first CI.original) . M.toList) -- |Get the request method (GET, POST, etc.). You could look the header up -- yourself, but this normalizes the method name to uppercase.-method :: SCGI (Maybe B.ByteString) -- ^ the method if found+method :: Monad m => SCGIT m (Maybe B.ByteString) -- ^ the method if found method = liftM (B8.map toUpper) `liftM` header "REQUEST_METHOD" -- |Get the requested path. According to the spec, this can be complex, and -- actual CGI implementations diverge from the spec. I've found this to work, -- even though it doesn't seem correct or intuitive.-path :: SCGI (Maybe B.ByteString) -- ^ the path if found+path :: Monad m => SCGIT m (Maybe B.ByteString) -- ^ the path if found path = do path1 <- header "SCRIPT_NAME" path2 <- header "PATH_INFO" return $ liftM2 B.append path1 path2 -- |Set a response header.-setHeader :: B.ByteString -- ^ the header name (key)+setHeader :: Monad m+ => B.ByteString -- ^ the header name (key) -> B.ByteString -- ^ the header value- -> SCGI ()+ -> SCGIT m () setHeader name value = modify (M.insert (CI.mk name) value) -- |Run a request in the SCGI monad.-runRequest :: Handle -- ^ the handle connected to the web server (from 'accept')- -> (Body -> SCGI Response) -- ^ the action to run in the SCGI monad- -> IO () -- ^ nothing is returned, the result of the action is written back to the server+runRequest :: MonadIO m+ => Handle -- ^ the handle connected to the web server (from 'accept')+ -> (Body -> SCGIT m Response) -- ^ the action to run in the SCGI monad+ -> m () -- ^ nothing is returned, the result of the action is written back to the server runRequest h f = do -- Note: This could potentially read any amount of data into memory. -- For now, I'm leaving it up to the SCGI implementation in the server to block large header payloads. -- -- First, parse the netstring containing the headers. If we tried to avoid this step the syntax for -- the headers would be ambiguous.- result <- parseWith (B.hGetSome h 4096) netstringParser ""+ result <- liftIO $ parseWith (B.hGetSome h 4096) netstringParser "" case result of Done rest headerString -> case parseOnly (many1 headerParser) headerString of@@ -91,15 +100,15 @@ -- rest of the unparsed string and what remains to be read -- (determined from the CONTENT_LENGTH) and make that the body. let c = fromIntegral (len - B.length rest)- body <- (BL.fromChunks [rest] `BL.append`) `liftM` (if c > 0 then BL.hGet h c else return "")- (Response status body', headers') <- runSCGI headerMap (f body)+ body <- liftIO $ (BL.fromChunks [rest] `BL.append`) `liftM` (if c > 0 then BL.hGet h c else return "")+ (Response status body', headers') <- runSCGIT headerMap (f body) -- Every SCGI response must include a status line first.- BL.hPutStr h $ BL.concat ["Status: ", status, "\r\n"]+ liftIO $ BL.hPutStr h $ BL.concat ["Status: ", status, "\r\n"] -- Output the headers returned by the SCGI action.- mapM_ (\(k, v) -> B.hPutStr h $ B.concat [CI.original k, ": ", v, "\r\n"]) $ M.toList headers'- BL.hPutStr h "\r\n"+ liftIO $ mapM_ (\(k, v) -> B.hPutStr h $ B.concat [CI.original k, ": ", v, "\r\n"]) $ M.toList headers'+ liftIO $ BL.hPutStr h "\r\n" -- Finally, output the body.- BL.hPutStr h body'+ liftIO $ BL.hPutStr h body' _ -> error "Failed to parse CONTENT_LENGTH." _ -> error "Failed to parse SCGI request."
sscgi.cabal view
@@ -1,5 +1,5 @@ name: sscgi-version: 0.1.0+version: 0.2.0 synopsis: Simple SCGI Library description: This is a simple implementation of the SCGI protocol without support for the Network.CGI interface. homepage: https://github.com/jekor/haskell-sscgi