snap-server 0.2.11 → 0.2.12
raw patch · 23 files changed
+981/−473 lines, 23 filesdep ~snap-core
Dependency ranges changed: snap-core
Files
- snap-server.cabal +8/−6
- src/Snap/Internal/Http/Parser.hs +50/−44
- src/Snap/Internal/Http/Server.hs +39/−31
- src/Snap/Internal/Http/Server/LibevBackend.hs +78/−73
- src/Snap/Internal/Http/Server/SimpleBackend.hs +101/−39
- test/benchmark/Benchmark.hs +2/−10
- test/benchmark/Snap/Internal/Http/Parser/Benchmark.hs +26/−22
- test/benchmark/Snap/Internal/Http/Parser/Data.hs +24/−0
- test/common/Paths_snap_server.hs +9/−0
- test/common/Snap/Test/Common.hs +88/−0
- test/common/Test/Common/Rot13.hs +19/−0
- test/common/Test/Common/TestHandler.hs +73/−0
- test/pongserver/Paths_snap_server.hs +0/−9
- test/runTestsAndCoverage.sh +10/−3
- test/snap-server-testsuite.cabal +24/−37
- test/suite/Paths_snap_server.hs +0/−9
- test/suite/Snap/Internal/Http/Parser/Tests.hs +49/−63
- test/suite/Snap/Internal/Http/Server/Tests.hs +169/−59
- test/suite/Snap/Test/Common.hs +0/−20
- test/suite/Test/Blackbox.hs +185/−0
- test/suite/TestSuite.hs +20/−9
- test/testserver/Main.hs +7/−30
- test/testserver/Paths_snap_server.hs +0/−9
snap-server.cabal view
@@ -1,5 +1,5 @@ name: snap-server-version: 0.2.11+version: 0.2.12 synopsis: A fast, iteratee-based, epoll-enabled web server for the Snap Framework description: This is the first developer prerelease of the Snap framework. Snap is a@@ -57,22 +57,24 @@ README.SNAP.md, test/benchmark/Benchmark.hs, test/benchmark/Snap/Internal/Http/Parser/Benchmark.hs,+ test/benchmark/Snap/Internal/Http/Parser/Data.hs,+ test/common/Paths_snap_server.hs,+ test/common/Snap/Test/Common.hs,+ test/common/Test/Common/TestHandler.hs,+ test/common/Test/Common/Rot13.hs, test/data/fileServe/foo.bin, test/data/fileServe/foo.bin.bin.bin, test/data/fileServe/foo.html, test/data/fileServe/foo.txt, test/pongserver/Main.hs,- test/pongserver/Paths_snap_server.hs, test/runTestsAndCoverage.sh, test/snap-server-testsuite.cabal,- test/suite/Paths_snap_server.hs, test/suite/Data/Concurrent/HashMap/Tests.hs, test/suite/Snap/Internal/Http/Parser/Tests.hs, test/suite/Snap/Internal/Http/Server/Tests.hs,- test/suite/Snap/Test/Common.hs,+ test/suite/Test/Blackbox.hs, test/suite/TestSuite.hs, test/testserver/Main.hs,- test/testserver/Paths_snap_server.hs, test/testserver/static/hello.txt @@ -120,7 +122,7 @@ murmur-hash >= 0.1 && < 0.2, network == 2.2.1.*, old-locale,- snap-core >= 0.2.11 && <0.3,+ snap-core >= 0.2.12 && <0.3, template-haskell, time, transformers,
src/Snap/Internal/Http/Parser.hs view
@@ -30,9 +30,10 @@ import qualified Data.ByteString.Lazy as L import qualified Data.ByteString.Nums.Careless.Hex as Cvt import Data.Char+import Data.DList (DList)+import qualified Data.DList as D import Data.List (foldl') import Data.Int-import Data.IORef import Data.Iteratee.WrappedByteString import Data.Map (Map) import qualified Data.Map as Map@@ -40,15 +41,12 @@ import qualified Data.Vector.Unboxed as Vec import Data.Vector.Unboxed (Vector) import Data.Word (Word8, Word64)-import Foreign.C.Types-import Foreign.ForeignPtr import Prelude hiding (take, takeWhile) ------------------------------------------------------------------------------ import Snap.Internal.Http.Types hiding (Enumerator) import Snap.Iteratee hiding (take, foldl', filter) - ------------------------------------------------------------------------------ -- | an internal version of the headers part of an HTTP request data IRequest = IRequest@@ -98,9 +96,9 @@ | n .&. 0xf000000000000000 == 0 = trim (i-1) (n `shiftL` 4) | otherwise = fst (S.unfoldrN i f n) - f n = Just (char (n `shiftR` 60), n `shiftL` 4)+ f n = Just (ch (n `shiftR` 60), n `shiftL` 4) - char (fromIntegral -> i)+ ch (fromIntegral -> i) | i < 10 = (c2w '0' - 0) + i | otherwise = (c2w 'a' - 10) + i @@ -118,50 +116,58 @@ -- > -- > Chunk "a\r\nfoobarquux\r\n0\r\n\r\n" Empty ---writeChunkedTransferEncoding :: ForeignPtr CChar- -> Enumerator IO a- -> Enumerator IO a-writeChunkedTransferEncoding buf enum it = do- killwrap <- newIORef False- (out,_) <- unsafeBufferIterateeWithBuffer buf- (ignoreEOF $ wrap killwrap it)- i <- enum out- v <- runIter i (EOF Nothing)- j <- checkIfDone return v- writeIORef killwrap True- w <- runIter j (Chunk (WrapBS "0\r\n\r\n"))- checkIfDone return w+writeChunkedTransferEncoding :: Enumerator IO a+writeChunkedTransferEncoding it = do+ let out = wrap it+ return out where- ignoreEOF iter = IterateeG $ \s ->+ wrap iter = bufIt (0,D.empty) iter++ bufSiz = 16284++ sendOut :: DList ByteString+ -> Iteratee IO a+ -> IO (Iteratee IO a)+ sendOut dl iter = do+ let chunks = D.toList dl+ let bs = L.fromChunks chunks+ let n = L.length bs++ if n == 0+ then return iter+ else do+ let o = L.concat [ L.fromChunks [ toHex (toEnum . fromEnum $ n)+ , "\r\n" ]+ , bs+ , "\r\n" ]++ enumLBS o iter+++ bufIt (n,dl) iter = IterateeG $ \s -> do case s of- (EOF Nothing) -> return $ Cont iter Nothing- _ -> do- i <- runIter iter s >>= checkIfDone return- return $ Cont (ignoreEOF i) Nothing+ (EOF Nothing) -> do+ i' <- sendOut dl iter+ j <- liftM liftI $ runIter i' (Chunk (WrapBS "0\r\n\r\n"))+ runIter j (EOF Nothing) - wrap killwrap iter = IterateeG $ \s -> do- quit <- readIORef killwrap+ (EOF e) -> return $ Cont undefined e - if quit- then runIter iter s- else case s of- (EOF Nothing) -> do- return $ Cont iter Nothing+ (Chunk (WrapBS x)) -> do+ let m = S.length x - (EOF e) -> return $ Cont undefined e- (Chunk (WrapBS x)) -> do- let n = S.length x- if n == 0- then do- return $ Cont iter Nothing- else do- let o = S.concat [ toHex (toEnum n)- , "\r\n"- , x- , "\r\n" ]- i <- liftM liftI $ runIter iter (Chunk $ WrapBS o)- return $ Cont (wrap killwrap i) Nothing+ if m == 0+ then return $ Cont (bufIt (n,dl) iter) Nothing+ else do+ let n' = m + n+ let dl' = D.snoc dl x++ if n' > bufSiz+ then do+ i' <- sendOut dl' iter+ return $ Cont (bufIt (0,D.empty) i') Nothing+ else return $ Cont (bufIt (n',dl') iter) Nothing ------------------------------------------------------------------------------
src/Snap/Internal/Http/Server.hs view
@@ -1,6 +1,7 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} module Snap.Internal.Http.Server where@@ -156,7 +157,6 @@ elog <- maybeSpawnLogger efp return (alog, elog)) (\(alog, elog) -> do- threadDelay 1000000 maybe (return ()) stopLogger alog maybe (return ()) stopLogger elog) @@ -206,7 +206,7 @@ , Handler $ \(e :: AsyncException) -> do logE elog $ S.concat [ "Server.httpServe.go: got async exception, "- , "terminating:\n", bshow e ]+ , "terminating: ", bshow e ] throwIO e , Handler $ \(e :: Backend.BackendTerminatedException) -> do@@ -299,9 +299,10 @@ go = do buf <- mkIterateeBuffer- let iter = runServerMonad lh lip lp rip rp (logA alog) (logE elog) $- httpSession writeEnd buf onSendFile tickle- handler+ let iter1 = runServerMonad lh lip lp rip rp (logA alog) (logE elog) $+ httpSession writeEnd buf onSendFile tickle+ handler+ let iter = iterateeDebugWrapper "httpSession iteratee" iter1 readEnd iter >>= run @@ -335,16 +336,15 @@ -> ServerMonad () httpSession writeEnd' ibuf onSendFile tickle handler = do - (writeEnd, cancelBuffering) <-- liftIO $ I.unsafeBufferIterateeWithBuffer ibuf writeEnd'-- -- (writeEnd, cancelBuffering) <- liftIO $ I.bufferIteratee writeEnd'- let killBuffer = writeIORef cancelBuffering True+ writeEnd1 <- liftIO $ I.unsafeBufferIterateeWithBuffer ibuf writeEnd' + let writeEnd = iterateeDebugWrapper "writeEnd" writeEnd1 liftIO $ debug "Server.httpSession: entered" mreq <- receiveRequest + + -- successfully got a request, so restart timer liftIO tickle @@ -359,6 +359,7 @@ checkExpect100Continue req writeEnd logerr <- gets _logError+ (req',rspOrig) <- lift $ handler logerr req liftIO $ debug $ "Server.httpSession: finished running user handler"@@ -372,16 +373,18 @@ else rspTmp liftIO $ debug "Server.httpSession: handled, skipping request body"+ srqEnum <- liftIO $ readIORef $ rqBody req' let (SomeEnumerator rqEnum) = srqEnum- lift $ joinIM $ rqEnum skipToEof+ lift $ joinIM+ $ rqEnum (iterateeDebugWrapper "httpSession/skipToEof" skipToEof) liftIO $ debug $ "Server.httpSession: request body skipped, " ++ "sending response" date <- liftIO getDateString let ins = Map.insert "Date" [date] . Map.insert "Server" sERVER_HEADER let rsp' = updateHeaders ins rsp- (bytesSent,_) <- sendResponse rsp' writeEnd ibuf killBuffer onSendFile+ (bytesSent,_) <- sendResponse rsp' writeEnd onSendFile liftIO . debug $ "Server.httpSession: sent " ++ (Prelude.show bytesSent) ++ " bytes"@@ -469,13 +472,13 @@ hasContentLength :: Int -> ServerMonad () hasContentLength l = do liftIO $ debug $ "receiveRequest/setEnumerator: " ++- "request had content-length"+ "request had content-length " ++ Prelude.show l liftIO $ writeIORef (rqBody req) (SomeEnumerator e) liftIO $ debug "receiveRequest/setEnumerator: body enumerator set" where e :: Enumerator IO a- e = return . joinI . I.take l .- iterateeDebugWrapper "rqBody iterator"+ e it = return $ joinI $ I.take l $+ iterateeDebugWrapper "rqBody iterator" it noContentLength :: ServerMonad () noContentLength = do@@ -601,29 +604,33 @@ ------------------------------------------------------------------------------ -- Response must be well-formed here-sendResponse :: Response+sendResponse :: forall a . Response -> Iteratee IO a- -> ForeignPtr CChar- -> IO () -> (FilePath -> Int64 -> IO a) -> ServerMonad (Int64, a)-sendResponse rsp' writeEnd ibuf killBuffering onSendFile = do+sendResponse rsp' writeEnd onSendFile = do rsp <- fixupResponse rsp' let !headerString = mkHeaderString rsp (!x,!bs) <- case (rspBody rsp) of- (Enum e) -> liftIO $ whenEnum headerString e- (SendFile f) -> liftIO $ whenSendFile headerString rsp f+ (Enum e) -> lift $ whenEnum headerString rsp e+ (SendFile f) -> lift $ whenSendFile headerString rsp f return $! (bs,x) where --------------------------------------------------------------------------- whenEnum hs e = do- let enum = enumBS hs >. e- let hl = fromIntegral $ S.length hs+ whenEnum :: ByteString+ -> Response+ -> (forall x . Enumerator IO x)+ -> Iteratee IO (a,Int64)+ whenEnum hs rsp e = do+ let enum = if rspTransformingRqBody rsp+ then enumBS hs >. e+ else enumBS hs >. e >. enumEof - (x,bs) <- liftIO $ enum (countBytes writeEnd) >>= run+ let hl = fromIntegral $ S.length hs+ (x,bs) <- joinIM $ enum (countBytes writeEnd) return (x, bs-hl) @@ -631,10 +638,10 @@ -------------------------------------------------------------------------- whenSendFile hs r f = do -- guaranteed to have a content length here.- enumBS hs writeEnd >>= run+ joinIM $ (enumBS hs >. enumEof) writeEnd let !cl = fromJust $ rspContentLength r- x <- onSendFile f cl+ x <- liftIO $ onSendFile f cl return (x, cl) @@ -663,10 +670,11 @@ let sendChunked = (rspHttpVersion r) == (1,1) if sendChunked then do- liftIO $ killBuffering let r' = setHeader "Transfer-Encoding" "chunked" r- let e = writeChunkedTransferEncoding ibuf $- rspBodyToEnum $ rspBody r+ let origE = rspBodyToEnum $ rspBody r++ let e i = writeChunkedTransferEncoding i >>= origE+ return $ r' { rspBody = Enum e } else do@@ -690,7 +698,7 @@ return $ r' { rspBody = b } where- i :: Enumerator IO a -> Enumerator IO a+ i :: forall z . Enumerator IO z -> Enumerator IO z i enum iter = enum (joinI $ takeExactly cl iter)
src/Snap/Internal/Http/Server/LibevBackend.hs view
@@ -9,9 +9,9 @@ module Snap.Internal.Http.Server.LibevBackend ( Backend- , BackendTerminatedException+ , BackendTerminatedException(..) , Connection- , TimeoutException+ , TimeoutException(..) , name , debug , bindIt@@ -47,11 +47,10 @@ import Foreign hiding (new) import Foreign.C.Error import Foreign.C.Types-import GHC.Conc (forkOnIO)+import GHC.Conc (forkOnIO, numCapabilities) import Network.Libev import Network.Socket import Prelude hiding (catch)-import System.Timeout ------------------------------------------------------------------------------ -- FIXME: should be HashSet, make that later.@@ -80,8 +79,9 @@ , _asyncObj :: !EvAsyncPtr , _killCb :: !(FunPtr AsyncCallback) , _killObj :: !EvAsyncPtr- , _connectionThreads :: !(HashMap ThreadId ())+ , _connectionThreads :: !(HashMap ThreadId Connection) , _backendCPU :: !Int+ , _backendFreed :: !(MVar ()) } @@ -216,6 +216,9 @@ -- thread set stuff connSet <- H.new (H.hashString . show) + -- freed gets stuffed with () when all resources are released.+ freed <- newEmptyMVar+ let b = Backend sock sockFd connq@@ -230,6 +233,7 @@ killObj connSet cpu+ freed forkOnIO cpu $ loopThread b @@ -244,9 +248,11 @@ (ignoreException go) `finally` cleanup debug $ "loop finished" where- cleanup = do+ cleanup = block $ do debug $ "loopThread: cleaning up" ignoreException $ freeBackend backend+ putMVar (_backendFreed backend) ()+ lock = _loopLock backend loop = _evLoop backend go = takeMVar lock >> block (evLoop loop 0)@@ -288,43 +294,28 @@ writeIORef active False -seconds :: Int -> Int-seconds n = n * ((10::Int)^(6::Int))-- stop :: Backend -> IO () stop b = ignoreException $ do debug $ "Backend.stop" - -- FIXME: what are we gonna do here?- -- -- 1. take the loop lock -- 2. shut down the accept() callback -- 3. stuff a poison pill (a bunch of -1 values should do) down the -- connection queue so that withConnection knows to throw an exception -- back up to its caller- -- 4. release the loop lock- -- 5. wait until all of the threads have finished, or until 10 seconds have- -- elapsed, whichever comes first- -- 6. take the loop lock- -- 7. call evUnloop and wake up the loop using evAsyncSend- -- 8. release the loop lock, the main loop thread should then free/clean+ -- 4. call evUnloop and wake up the loop using evAsyncSend+ -- 5. release the loop lock, the main loop thread should then free/clean -- everything up (threads, connections, io objects, callbacks, etc)+ -- 6. wait for the loop thread to signal it has cleaned up and exited withMVar lock $ \_ -> do evIoStop loop acceptObj- replicateM_ 10 $ writeChan connQ (-1)-- debug $ "Backend.stop: waiting at most 10 seconds for connection threads to die"- waitForThreads b $ seconds 10- debug $ "Backend.stop: all threads presumed dead, unlooping"-- withMVar lock $ \_ -> do+ replicateM_ (numCapabilities*2) $ writeChan connQ (-1) evUnloop loop evunloop_all evAsyncSend loop killObj - debug $ "unloop sent"-+ debug $ "accepting threads killed, unloop sent, waiting for completion"+ takeMVar $ _backendFreed b where loop = _evLoop b@@ -335,18 +326,6 @@ -waitForThreads :: Backend -> Int -> IO ()-waitForThreads backend t = timeout t wait >> return ()- where- threadSet = _connectionThreads backend- wait = do- b <- H.null threadSet- if b- then return ()- else threadDelay (seconds 1) >> wait--- getAddr :: SockAddr -> IO (ByteString, Int) getAddr addr = case addr of@@ -370,41 +349,60 @@ now <- getCurrentDateTime whenToDie <- readIORef ioref - if whenToDie < now+ if whenToDie <= now then do debug "Backend.timerCallback: killing thread" tid <- readMVar tmv throwTo tid TimeoutException else do- evTimerSetRepeat tmr $ fromRational . toRational $ (whenToDie - now)- evTimerAgain loop tmr+ debug $ "Backend.timerCallback: now=" ++ show now+ ++ ", whenToDie=" ++ show whenToDie+ evTimerSetRepeat tmr $ fromRational . toRational $ (whenToDie - now)+ evTimerAgain loop tmr -freeConnection :: Connection -> IO ()-freeConnection conn = ignoreException $ do- withMVar loopLock $ \_ -> block $ do- debug $ "freeConnection (" ++ show fd ++ ")"+-- if you already hold the loop lock, you are entitled to destroy a connection+destroyConnection :: Connection -> IO ()+destroyConnection conn = do+ debug "Backend.destroyConnection: closing socket and killing connection"+ c_close fd - c_close fd+ -- stop and free timer object+ evTimerStop loop timerObj+ freeEvTimer timerObj+ freeTimerCallback timerCb - -- stop and free timer object- evTimerStop loop timerObj- freeEvTimer timerObj- freeTimerCallback timerCb+ -- stop and free i/o objects+ evIoStop loop ioWrObj+ freeEvIo ioWrObj+ freeIoCallback ioWrCb - -- stop and free i/o objects- evIoStop loop ioWrObj- freeEvIo ioWrObj- freeIoCallback ioWrCb+ evIoStop loop ioRdObj+ freeEvIo ioRdObj+ freeIoCallback ioRdCb - evIoStop loop ioRdObj- freeEvIo ioRdObj- freeIoCallback ioRdCb+ where+ backend = _backend conn+ loop = _evLoop backend + fd = _socketFd conn+ ioWrObj = _connWriteIOObj conn+ ioWrCb = _connWriteIOCallback conn+ ioRdObj = _connReadIOObj conn+ ioRdCb = _connReadIOCallback conn+ timerObj = _timerObj conn+ timerCb = _timerCallback conn+++freeConnection :: Connection -> IO ()+freeConnection conn = ignoreException $ do+ withMVar loopLock $ \_ -> block $ do+ debug $ "freeConnection (" ++ show fd ++ ")"+ destroyConnection conn tid <- readMVar $ _connThread conn - -- removal the thread id from the backend set+ -- remove the thread id from the backend set H.delete tid $ _connectionThreads backend -- wake up the event loop so it can be apprised of the changes@@ -415,14 +413,7 @@ loop = _evLoop backend loopLock = _loopLock backend asyncObj = _asyncObj backend- fd = _socketFd conn- ioWrObj = _connWriteIOObj conn- ioWrCb = _connWriteIOCallback conn- ioRdObj = _connReadIOObj conn- ioRdCb = _connReadIOCallback conn- timerObj = _timerObj conn- timerCb = _timerCallback conn ignoreException :: IO () -> IO ()@@ -431,15 +422,26 @@ freeBackend :: Backend -> IO () freeBackend backend = ignoreException $ block $ do- -- note: we only get here after an unloop-+ -- note: we only get here after an unloop, so we have the loop lock+ -- here. (?) -- kill everything in thread table tset <- H.toList $ _connectionThreads backend++ let nthreads = Prelude.length tset++ debug $ "Backend.freeBackend: killing active connection threads"++ Prelude.mapM_ (destroyConnection . snd) tset++ -- kill the threads twice, they're probably getting stuck in the+ -- freeConnection 'finally' handler Prelude.mapM_ (killThread . fst) tset+ Prelude.mapM_ (killThread . fst) tset - debug $ "Backend.freeBackend: all threads killed"- debug $ "Backend.freeBackend: destroying resources"+ debug $ "Backend.freeBackend: " ++ show nthreads ++ " thread(s) killed"+ debug $ "Backend.freeBackend: destroying libev resources"+ freeEvIo acceptObj freeIoCallback acceptCb c_close fd@@ -474,7 +476,10 @@ withConnection :: Backend -> Int -> (Connection -> IO ()) -> IO () withConnection backend cpu proc = go where- threadProc conn = ignoreException (proc conn) `finally` freeConnection conn+ threadProc conn = (do+ x <- blocked+ debug $ "withConnection/threadProc: we are blocked? " ++ show x+ proc conn) `finally` freeConnection conn go = do debug $ "withConnection: reading from chan"@@ -563,7 +568,7 @@ tid <- forkOnIO cpu $ threadProc conn - H.update tid () (_connectionThreads backend)+ H.update tid conn (_connectionThreads backend) putMVar thrmv tid @@ -636,7 +641,7 @@ tickleTimeout conn = do debug "Backend.tickleTimeout" now <- getCurrentDateTime- writeIORef (_timerTimeoutTime conn) (now + 20)+ writeIORef (_timerTimeoutTime conn) (now + 30) recvData :: Connection -> Int -> IO ByteString
src/Snap/Internal/Http/Server/SimpleBackend.hs view
@@ -9,9 +9,9 @@ module Snap.Internal.Http.Server.SimpleBackend ( Backend- , BackendTerminatedException+ , BackendTerminatedException(..) , Connection- , TimeoutException+ , TimeoutException(..) , name , debug , bindIt@@ -33,6 +33,7 @@ import Control.Concurrent import Control.Exception+import Control.Monad import Data.ByteString (ByteString) import Data.ByteString.Internal (c2w, w2c) import qualified Data.ByteString as B@@ -72,10 +73,15 @@ type TimeoutTable = PSQ ThreadId CTime +type QueueElem = Maybe (Socket,SockAddr)+ data Backend = Backend- { _acceptSocket :: !Socket- , _timeoutEdits :: !(IORef (DList (TimeoutTable -> TimeoutTable)))- , _timeoutThread :: !(MVar ThreadId) }+ { _acceptSocket :: !Socket+ , _acceptThread :: !ThreadId+ , _timeoutEdits :: !(IORef (DList (TimeoutTable -> TimeoutTable)))+ , _timeoutThread :: !(MVar ThreadId)+ , _connectionQueue :: !(Chan QueueElem)+ } data Connection = Connection { _backend :: Backend@@ -127,49 +133,92 @@ return sock +acceptThread :: Socket -> Chan QueueElem -> IO ()+acceptThread sock connq = loop `finally` cleanup+ where+ loop = do+ debug $ "acceptThread: calling accept()"+ s@(_,addr) <- accept sock+ debug $ "acceptThread: accepted connection from remote: " ++ show addr+ debug $ "acceptThread: queueing"+ writeChan connq $ Just s+ loop++ cleanup = block $ do+ debug $ "acceptThread: cleanup, closing socket and notifying "+ ++ "chan listeners"+ sClose sock+ replicateM 10 $ writeChan connq Nothing++ new :: Socket -- ^ value you got from bindIt -> Int -> IO Backend-new sock _ = do+new sock cpu = do debug $ "Backend.new: listening" - ed <- newIORef D.empty- t <- newEmptyMVar+ ed <- newIORef D.empty+ t <- newEmptyMVar - let b = Backend sock ed t+ connq <- newChan+ accThread <- forkOnIO cpu $ acceptThread sock connq - tid <- forkIO $ timeoutThread b PSQ.empty+ let b = Backend sock accThread ed t connq++ tid <- forkIO $ timeoutThread b putMVar t tid return b -timeoutThread :: Backend -> TimeoutTable -> IO ()-timeoutThread backend = loop- where- loop tt = do- tt' <- killTooOld tt+timeoutThread :: Backend -> IO ()+timeoutThread backend = do+ tref <- newIORef $ PSQ.empty+ let loop = do+ killTooOld tref threadDelay (5000000)- loop tt'+ loop + loop `catch` (\(_::SomeException) -> killAll tref) - killTooOld table = do- -- atomic swap edit list- now <- getCurrentDateTime+ where+ applyEdits table = do edits <- atomicModifyIORef tedits $ \t -> (D.empty, D.toList t)+ return $ foldl' (flip ($)) table edits - let table' = foldl' (flip ($)) table edits- !t' <- killOlderThan now table'- return t'+ killTooOld tref = do+ !table <- readIORef tref+ -- atomic swap edit list+ now <- getCurrentDateTime+ table' <- applyEdits table+ !t' <- killOlderThan now table'+ writeIORef tref t' - -- timeout = 60 seconds- tIMEOUT = 60+ -- timeout = 30 seconds+ tIMEOUT = 30 + killAll !tref = do+ debug "Backend.timeoutThread: shutdown, killing all connections"+ !table <- readIORef tref+ !table' <- applyEdits table+ go table'+ where+ go !t = maybe (return ())+ (\m -> (killThread $ PSQ.key m) >>+ (go $ PSQ.deleteMin t))+ (PSQ.findMin t)+ killOlderThan now !table = do+ debug "Backend.timeoutThread: killing old connections" let mmin = PSQ.findMin table maybe (return table)- (\m -> if now - PSQ.prio m > tIMEOUT+ (\m -> do+ debug $ "Backend.timeoutThread: minimum value "+ ++ show (PSQ.prio m) ++ ", cutoff="+ ++ show (now - tIMEOUT)++ if now - PSQ.prio m >= tIMEOUT then do killThread $ PSQ.key m killOlderThan now $ PSQ.deleteMin table@@ -180,15 +229,22 @@ stop :: Backend -> IO ()-stop (Backend s _ t) = do- debug $ "Backend.stop"- sClose s+stop backend = do+ debug $ "Backend.stop: killing accept thread"+ killThread acthr - -- kill timeout thread and current thread- readMVar t >>= killThread- myThreadId >>= killThread+ debug $ "Backend.stop: killing timeout thread" + -- kill timeout thread; timeout thread handler will stop all of the running+ -- connection threads+ readMVar tthr >>= killThread+ debug $ "Backend.stop: exiting.." + where+ acthr = _acceptThread backend+ tthr = _timeoutThread backend++ data AddressNotSupportedException = AddressNotSupportedException String deriving (Typeable) @@ -200,14 +256,19 @@ withConnection :: Backend -> Int -> (Connection -> IO ()) -> IO () withConnection backend cpu proc = do- debug $ "Backend.withConnection: calling accept()"- let asock = _acceptSocket backend- (sock,addr) <- accept asock+ debug $ "Backend.withConnection: reading from chan" + qelem <- readChan $ _connectionQueue backend+ when (qelem == Nothing) $ do+ debug $ "Backend.withConnection: channel terminated, throwing "+ ++ "BackendTerminatedException"+ throwIO BackendTerminatedException++ let (Just (sock,addr)) = qelem let fd = fdSocket sock - debug $ "Backend.withConnection: accepted connection"- debug $ "Backend.withConnection: remote: " ++ show addr+ debug $ "Backend.withConnection: dequeued connection from remote: "+ ++ show addr (port,host) <- case addr of@@ -233,7 +294,7 @@ labelMe $ "connHndl " ++ show fd bracket (return c) (\_ -> block $ do- debug "sClose sock"+ debug "thread killed, closing socket" thr <- readMVar tmvar -- remove thread from timeout table@@ -312,8 +373,9 @@ tedits = _timeoutEdits $ _backend conn -cancelTimeout :: Connection -> IO ()-cancelTimeout conn = do+_cancelTimeout :: Connection -> IO ()+_cancelTimeout conn = do+ debug "Backend.cancelTimeout" tid <- readMVar $ _connTid conn atomicModifyIORef tedits $ \es -> (D.snoc es (PSQ.delete tid), ())
test/benchmark/Benchmark.hs view
@@ -1,15 +1,7 @@ module Main where -import Criterion.Main-+import Criterion.Main import qualified Snap.Internal.Http.Parser.Benchmark as PB -fib :: Int -> Int-fib 0 = 0-fib 1 = 1-fib n = fib (n-1) + fib (n-2)- main :: IO ()-main = defaultMain [- PB.benchmarks- ]+main = defaultMain [ PB.benchmarks ]
test/benchmark/Snap/Internal/Http/Parser/Benchmark.hs view
@@ -1,36 +1,40 @@ {-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE PackageImports #-} module Snap.Internal.Http.Parser.Benchmark ( benchmarks ) where -import Criterion.Main-import Snap.Internal.Http.Parser-import Data.ByteString (ByteString)-import qualified Data.ByteString as S-import qualified Snap.Iteratee as SI-import qualified Control.Exception as E-import Data.Attoparsec hiding (Result(..))+import Criterion.Main hiding (run)+import Snap.Internal.Http.Parser+import Data.ByteString (ByteString)+import qualified Data.ByteString as S+import qualified Snap.Iteratee as SI+import qualified Control.Exception as E+import Data.Attoparsec hiding (Result(..))+import Snap.Internal.Http.Parser.Data+import "monads-fd" Control.Monad.Identity+import Data.Iteratee+import Data.Iteratee.WrappedByteString+import Snap.Iteratee hiding (take, foldl', filter)+import qualified Data.ByteString.Lazy.Char8 as L -req1 = S.concat - [ "GET /favicon.ico HTTP/1.1\r\n"- , "Host: 0.0.0.0=5000\r\n"- , "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n"- , "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"- , "Accept-Language: en-us,en;q=0.5\r\n"- , "Accept-Encoding: gzip,deflate\r\n"- , "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"- , "Keep-Alive: 300\r\n"- , "Connection: keep-alive\r\n"- , "\r\n" ]+parseGet :: IO ()+parseGet = SI.enumBS parseGetData parseRequest >>= SI.run >> return () -test1 :: IO ()-test1 = do- i <- SI.enumBS req1 parseRequest+parseChunked :: IO ()+parseChunked = do+ c <- toChunked parseChunkedData+ i <- SI.enumLBS c (readChunkedTransferEncoding stream2stream) f <- SI.run i return () +-- utils+toChunked lbs = writeChunkedTransferEncoding stream2stream >>=+ enumLBS lbs >>= run >>= return . fromWrap+ benchmarks = bgroup "parser"- [ bench "firefoxget" $ whnfIO test1 ]+ [ bench "firefoxget" $ whnfIO parseGet+ , bench "readChunkedTransferEncoding" $ whnfIO parseChunked ]
+ test/benchmark/Snap/Internal/Http/Parser/Data.hs view
@@ -0,0 +1,24 @@+{-# LANGUAGE OverloadedStrings #-}++module Snap.Internal.Http.Parser.Data + ( parseGetData+ , parseChunkedData+ )+ where++import Data.ByteString (ByteString)+import qualified Data.ByteString.Char8 as S+import qualified Data.ByteString.Lazy.Char8 as L+parseGetData = S.concat + [ "GET /favicon.ico HTTP/1.1\r\n"+ , "Host: 0.0.0.0=5000\r\n"+ , "User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9) Gecko/2008061015 Firefox/3.0\r\n"+ , "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"+ , "Accept-Language: en-us,en;q=0.5\r\n"+ , "Accept-Encoding: gzip,deflate\r\n"+ , "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n"+ , "Keep-Alive: 300\r\n"+ , "Connection: keep-alive\r\n"+ , "\r\n" ]++parseChunkedData = L.fromChunks ["In the beginning, everything was void, and J.H.W.H. Conway began to create numbers.", "Conway said, \"Let there be two rules which bring forth all numbers larege and small.", "This shall be the first rule: Every number corresponds to two sets of previously created numbers, such that no member of the left set is greater than or equal to any member of the right set.", "And the second rule shall be this: One number is less than or equal to another number if and only if no member of the first number \'s left set is greater than or equal to the second number, and no member of the second number\'s right set is less than or equal to the first number.\" And Conway examined these two rules he had made, and behold! They were very good.", "And the first number was created from the void left set and the void right set. Conway called this number \"zero,\" and said that it shall be a sign to separate positive numbers from negative numbers.", "Conway proved that zero was less than or equal to zero, end he saw that it was good.", "And the evening and the morning were the day of zero.", "On the next day, two more numbers were created, one with zero as its left set and one with zero as its right set. And Conway called the former number \"one,\" and the latter he called \"minus one.\" And he proved that minus one is less than but not equal to zero and zero is less than but not equal to one.", "And the evening day.", "And Conway said, \"Let the numbers be added to each other in this wise: The left set of the sum of two numbers shall be the sums of all left parts of each number with the other; and in like manner the right set shall be from the right parts, each according to its kind.\" Conway proved that every number plus zero is unchanged, and he saw that addition was good.", "And the evening and the morning were the third day."]
+ test/common/Paths_snap_server.hs view
@@ -0,0 +1,9 @@+module Paths_snap_server (+ version+ ) where++import Data.Version (Version(..))++version :: Version+version = Version {versionBranch = [0,0,0], versionTags = ["unknown"]}+
+ test/common/Snap/Test/Common.hs view
@@ -0,0 +1,88 @@+{-# OPTIONS_GHC -fno-warn-orphans #-}++{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+++module Snap.Test.Common where++import Control.Exception (SomeException)+import Control.Monad+import Control.Monad.CatchIO+import Data.ByteString (ByteString)+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+import Data.ByteString.Internal (c2w)+import qualified Data.DList as D+import Network.Socket+import qualified Network.Socket.ByteString as N+import Prelude hiding (catch)+import Test.QuickCheck+import System.Timeout++import Snap.Internal.Iteratee.Debug ()+++instance Arbitrary S.ByteString where+ arbitrary = liftM (S.pack . map c2w) arbitrary++instance Arbitrary L.ByteString where+ arbitrary = do+ n <- choose(0,5)+ chunks <- replicateM n arbitrary+ return $ L.fromChunks chunks++++expectExceptionBeforeTimeout :: IO a -- ^ action to run+ -> Int -- ^ number of seconds to expect+ -- exception by+ -> IO Bool+expectExceptionBeforeTimeout act nsecs = do+ x <- timeout (nsecs * (10::Int)^(6::Int)) f+ case x of+ Nothing -> return False+ (Just y) -> return y++ where+ f = (act >> return False) `catch` \(e::SomeException) -> do+ if show e == "<<timeout>>"+ then return False+ else return True+ ++withSock :: Int -> (Socket -> IO a) -> IO a+withSock port go = do+ addr <- liftM (addrAddress . Prelude.head) $+ getAddrInfo (Just myHints)+ (Just "127.0.0.1")+ (Just $ show port)++ sock <- socket AF_INET Stream defaultProtocol+ connect sock addr++ go sock `finally` sClose sock++ where+ myHints = defaultHints { addrFlags = [ AI_NUMERICHOST ] }+++recvAll :: Socket -> IO ByteString+recvAll sock = do+ d <- f D.empty sock+ return $ S.concat $ D.toList d++ where+ f d sk = do+ s <- N.recv sk 100000+ if S.null s+ then return d+ else f (D.snoc d s) sk+++ditchHeaders :: [ByteString] -> [ByteString]+ditchHeaders ("":xs) = xs+ditchHeaders ("\r":xs) = xs+ditchHeaders (_:xs) = ditchHeaders xs+ditchHeaders [] = []+
+ test/common/Test/Common/Rot13.hs view
@@ -0,0 +1,19 @@+module Test.Common.Rot13 (rot13) where++import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as S+import Data.Char++rotone :: Char -> Char+rotone x | acc x = f+ | otherwise = x+ where+ aA = ord 'A'+ aa = ord 'a'+ xx = ord x+ f = g $ if isAsciiUpper x then aA else aa+ g st = chr $ st + (xx - st + 13) `mod` 26+ acc c = isAlpha c && (isAsciiUpper c || isAsciiLower c)++rot13 :: ByteString -> ByteString+rot13 = S.map rotone
+ test/common/Test/Common/TestHandler.hs view
@@ -0,0 +1,73 @@+{-# LANGUAGE BangPatterns #-}+{-# LANGUAGE OverloadedStrings #-}++module Test.Common.TestHandler (testHandler) where+++import Control.Monad++import qualified Data.ByteString.Char8 as S+import qualified Data.ByteString.Lazy.Char8 as L+import Data.Iteratee.WrappedByteString+import Data.Maybe++import Snap.Iteratee hiding (Enumerator)+import Snap.Types+import Snap.Http.Server+import Snap.Util.FileServe+import Snap.Internal.Iteratee.Debug+import Test.Common.Rot13 (rot13)+++pongHandler :: Snap ()+pongHandler = modifyResponse $ setResponseBody (enumBS "PONG") .+ setContentType "text/plain" .+ setContentLength 4++echoUriHandler :: Snap ()+echoUriHandler = do+ req <- getRequest+ writeBS $ rqURI req+++echoHandler :: Snap ()+echoHandler = transformRequestBody return+++rot13Handler :: Snap ()+rot13Handler = transformRequestBody $ return . f+ where+ f i = IterateeG $ \ch -> do+ case ch of+ (EOF _) -> runIter i ch+ (Chunk (WrapBS s)) -> do+ i' <- liftM liftI $ runIter i $ Chunk $ WrapBS $ rot13 s+ return $ Cont (f i') Nothing+++bigResponseHandler :: Snap ()+bigResponseHandler = do+ let sz = 4000000+ let s = L.take sz $ L.cycle $ L.fromChunks [S.replicate 400000 '.']+ modifyResponse $ setContentLength sz+ writeLBS s+++responseHandler :: Snap ()+responseHandler = do+ !code <- liftM (read . S.unpack . fromMaybe "503") $ getParam "code"+ modifyResponse $ setResponseCode code+ writeBS $ S.pack $ show code+++testHandler :: Snap ()+testHandler =+ route [ ("pong" , pongHandler )+ , ("echo" , echoHandler )+ , ("rot13" , rot13Handler )+ , ("echoUri" , echoUriHandler )+ , ("fileserve" , fileServe "testserver/static")+ , ("bigresponse" , bigResponseHandler )+ , ("respcode/:code" , responseHandler )+ ]+
− test/pongserver/Paths_snap_server.hs
@@ -1,9 +0,0 @@-module Paths_snap_server (- version- ) where--import Data.Version (Version(..))--version :: Version-version = Version {versionBranch = [0,0,0], versionTags = ["unknown"]}-
test/runTestsAndCoverage.sh view
@@ -2,6 +2,10 @@ set -e +if [ "x$DEBUG" == "x" ]; then+ export DEBUG=testsuite+fi+ SUITE=./dist/build/testsuite/testsuite rm -f *.tix@@ -25,15 +29,18 @@ EXCLUDES='Main Data.CIByteString-Data.HashMap.Concurrent.Internal-Data.HashMap.Concurrent.Tests+Data.Concurrent.HashMap.Internal+Data.Concurrent.HashMap.Tests Paths_snap_server Snap.Internal.Http.Parser.Tests Snap.Internal.Http.Server.Tests Snap.Internal.Http.Types.Tests Snap.Internal.Iteratee.Tests Text.Snap.Templates.Tests-Snap.Test.Common'+Snap.Test.Common+Test.Blackbox+Test.Common.Rot13+Test.Common.TestHandler' EXCL=""
test/snap-server-testsuite.cabal view
@@ -13,7 +13,7 @@ Default: False Executable testsuite- hs-source-dirs: suite ../src + hs-source-dirs: suite common ../src main-is: TestSuite.hs build-depends:@@ -40,7 +40,8 @@ old-locale, parallel > 2, iteratee >= 0.3.1 && < 0.4,- snap-core >= 0.2.10 && <0.3,+ snap-core >= 0.2.12 && <0.3,+ template-haskell, test-framework >= 0.3.1 && <0.4, test-framework-hunit >= 0.2.5 && < 0.3, test-framework-quickcheck2 >= 0.2.6 && < 0.3,@@ -64,12 +65,13 @@ if flag(portable) || os(windows) cpp-options: -DPORTABLE - ghc-options: -O2 -Wall -fhpc -fwarn-tabs -funbox-strict-fields -threaded+ ghc-options: -O2 -Wall -fhpc -fwarn-tabs+ -funbox-strict-fields -threaded -fno-warn-unused-do-bind Executable pongserver- hs-source-dirs: pongserver ../src+ hs-source-dirs: pongserver common ../src main-is: Main.hs build-depends:@@ -92,10 +94,12 @@ old-locale, parallel > 2, iteratee >= 0.3.1 && < 0.4,+ MonadCatchIO-transformers >= 0.2.1 && < 0.3, murmur-hash >= 0.1 && < 0.2, network == 2.2.1.7, network-bytestring >= 0.1.2 && < 0.2,- snap-core >= 0.2.10 && <0.3,+ snap-core >= 0.2.12 && <0.3,+ template-haskell, time, transformers, unix-compat,@@ -143,7 +147,7 @@ Executable testserver- hs-source-dirs: testserver ../src+ hs-source-dirs: testserver common ../src main-is: Main.hs build-depends:@@ -152,33 +156,35 @@ attoparsec >= 0.8.1 && < 0.9, attoparsec-iteratee >= 0.1.1 && <0.2, base >= 4 && < 5,+ binary >= 0.5 && < 0.6, bytestring, bytestring-nums >= 0.3.1 && < 0.4, bytestring-show >= 0.3.2 && < 0.4,- cereal >= 0.3 && < 0.4, containers, directory-tree, dlist >= 0.5 && < 0.6, filepath, haskell98,+ HTTP >= 4000.0.9 && < 4001, HUnit >= 1.2 && < 2,+ MonadCatchIO-transformers >= 0.2.1 && < 0.3, monads-fd,- old-locale,- parallel > 2,- iteratee >= 0.3.1 && < 0.4, murmur-hash >= 0.1 && < 0.2, network == 2.2.1.7, network-bytestring >= 0.1.2 && < 0.2,- snap-core >= 0.2.10 && <0.3,+ old-locale,+ parallel > 2,+ iteratee >= 0.3.1 && < 0.4,+ snap-core >= 0.2.12 && <0.3, template-haskell,+ test-framework >= 0.3.1 && <0.4,+ test-framework-hunit >= 0.2.5 && < 0.3,+ test-framework-quickcheck2 >= 0.2.6 && < 0.3, time, transformers,- unix-compat, vector >= 0.6.0.1 && < 0.7 - if flag(portable) || os(windows)- cpp-options: -DPORTABLE- else+ if !os(windows) build-depends: unix if flag(libev)@@ -191,38 +197,19 @@ other-modules: Snap.Internal.Http.Server.SimpleBackend - if os(linux) && !flag(portable)- cpp-options: -DLINUX -DHAS_SENDFILE- other-modules:- System.SendFile,- System.SendFile.Linux-- if os(darwin) && !flag(portable)- cpp-options: -DOSX -DHAS_SENDFILE- other-modules:- System.SendFile,- System.SendFile.Darwin-- if os(freebsd) && !flag(portable)- cpp-options: -DFREEBSD -DHAS_SENDFILE- other-modules:- System.SendFile,- System.SendFile.FreeBSD- if flag(portable) || os(windows) cpp-options: -DPORTABLE - ghc-options: -Wall -O2 -fwarn-tabs -funbox-strict-fields -threaded+ ghc-options: -O2 -Wall -fwarn-tabs+ -funbox-strict-fields -threaded -fno-warn-unused-do-bind- ghc-prof-options: -prof -auto-all Executable benchmark- hs-source-dirs: benchmark ../src+ hs-source-dirs: benchmark common ../src main-is: Benchmark.hs build-depends: base >= 4 && < 5, network == 2.2.1.7, HTTP >= 4000.0.9 && < 4001, criterion >= 0.5 && <0.6-
− test/suite/Paths_snap_server.hs
@@ -1,9 +0,0 @@-module Paths_snap_server (- version- ) where--import Data.Version (Version(..))--version :: Version-version = Version {versionBranch = [0,0,0], versionTags = ["unknown"]}-
test/suite/Snap/Internal/Http/Parser/Tests.hs view
@@ -20,6 +20,7 @@ import Data.List import qualified Data.Map as Map import Data.Maybe (isNothing)+import Data.Monoid import Test.Framework import Test.Framework.Providers.HUnit import Test.Framework.Providers.QuickCheck2@@ -32,6 +33,7 @@ import Snap.Internal.Http.Parser import Snap.Internal.Http.Types hiding (Enumerator) import Snap.Iteratee hiding (foldl')+import qualified Snap.Iteratee as I import Snap.Test.Common() @@ -41,7 +43,6 @@ , testChunked , testBothChunked , testBothChunkedBuffered1- , testBothChunkedBuffered2 , testBothChunkedPipelined , testBothChunkedEmpty , testP2I@@ -152,9 +153,10 @@ monadicIO $ forAllM arbitrary prop where prop s = do- buf <- QC.run mkIterateeBuffer+ it <- QC.run $ writeChunkedTransferEncoding stream2stream+ bs <- QC.run $- writeChunkedTransferEncoding buf (enumBS s) stream2stream+ enumBS s it >>= run >>= return . unWrap let enum = enumBS bs@@ -168,7 +170,7 @@ testBothChunkedBuffered1 :: Test-testBothChunkedBuffered1 = testProperty "testBothChunkedBuffered1" $+testBothChunkedBuffered1 = testProperty "testBothChunkedBuffered2" $ monadicIO prop where prop = do@@ -177,62 +179,42 @@ ntimes <- QC.pick (choose (4,7)) let e = enumLBS s'-- buf <- QC.run mkIterateeBuffer-- enums <- QC.run $- replicateM ntimes- (mkIterateeBuffer >>=- return . flip writeChunkedTransferEncoding e)+ let n = fromEnum $ L.length s' - let mothra = foldl' (>.) (enumBS "") enums+ let enum = foldl' (>.) (enumBS "") (replicate ntimes e) - ----------------------------------------------------------------------- -- first go, buffer, no cancellation- (inputIter1,_) <- QC.run $ bufferIteratee stream2stream- bs1 <- QC.run $ mothra inputIter1- >>= run >>= return . unWrap- let e1 = enumBS bs1- let pcrlf = \s -> parserToIteratee $ string "\r\n" >> return s- iters <- QC.run $- replicateM ntimes $- readChunkedTransferEncoding stream2stream- let godzilla = sequence $ map (>>= pcrlf) iters- outiter1 <- QC.run $ e1 godzilla- x1 <- QC.run $ liftM (map unWrap) $ run outiter1+ (bufi,_) <- QC.run $ bufferIteratee stream2stream+ iter' <- QC.run $ writeChunkedTransferEncoding bufi+ let iter = I.joinI $ I.take n iter'+ let iters = replicate ntimes iter - QC.assert $- (map (L.fromChunks . (:[])) x1) == (replicate ntimes s')+ let mothra = foldM (\s it -> it >>= \t -> return $ s `mappend` t)+ mempty+ iters + bs <- QC.run $ enum mothra+ >>= run >>= return . unWrap -testBothChunkedBuffered2 :: Test-testBothChunkedBuffered2 = testProperty "testBothChunkedBuffered2" $- monadicIO prop- where- prop = do- sz <- QC.pick (choose (1000,4000))- s' <- QC.pick $ resize sz arbitrary- ntimes <- QC.pick (choose (4,7))-- let e = enumLBS s'-- buf <- QC.run mkIterateeBuffer-- enums <- QC.run $- replicateM ntimes- (mkIterateeBuffer >>=- return . flip writeChunkedTransferEncoding e)-- let mothra = foldl' (>.) (enumBS "") enums- ---------------------------------------------------------------------- -- 2nd pass, cancellation let pcrlf = \s -> parserToIteratee $ string "\r\n" >> return s (inputIter2,esc) <- QC.run $ bufferIteratee stream2stream QC.run $ writeIORef esc True- bs2 <- QC.run $ mothra inputIter2++ iter2' <- QC.run $ writeChunkedTransferEncoding inputIter2+ let iter2 = I.joinI $ I.take n iter2'+ let iters2 = replicate ntimes iter2++ let mothra2 = foldM (\s it -> it >>= \t -> return $ s `mappend` t)+ mempty+ iters2+++ bs2 <- QC.run $ enum mothra2 >>= run >>= return . unWrap++ let e2 = enumBS bs2 iters' <- QC.run $ replicateM ntimes $@@ -257,19 +239,21 @@ --let s' = L.take 2000 $ L.fromChunks $ repeat s let e = enumLBS s'+ let n = fromEnum $ L.length s' - buf <- QC.run mkIterateeBuffer+ let enum = foldl' (>.) (enumBS "") (replicate ntimes e) - enums <- QC.run $- replicateM ntimes- (mkIterateeBuffer >>=- return . flip writeChunkedTransferEncoding e)+ (bufi,_) <- QC.run $ bufferIteratee stream2stream - let mothra = foldl' (>.) (enumBS "") enums+ iter' <- QC.run $ writeChunkedTransferEncoding bufi+ let iter = I.joinI $ I.take n iter' - (bufi,_) <- QC.run $ bufferIteratee stream2stream+ let iters = replicate ntimes iter+ let mothra = foldM (\s it -> it >>= \t -> return $ s `mappend` t)+ mempty+ iters - bs <- QC.run $ mothra bufi+ bs <- QC.run $ enum mothra >>= run >>= return . unWrap let e2 = enumBS bs@@ -296,18 +280,20 @@ prop = do let s' = "" let e = enumLBS s'+ let n = fromEnum $ L.length s' let ntimes = 5-- buf <- mkIterateeBuffer+ let enum = foldl' (>.) (enumBS "") (replicate ntimes e) - enums <- replicateM ntimes- (mkIterateeBuffer >>=- return . flip writeChunkedTransferEncoding e)+ iter' <- writeChunkedTransferEncoding stream2stream+ let iter = I.joinI $ I.take n iter' - let mothra = foldl' (>.) (enumBS "") enums+ let iters = replicate ntimes iter+ let mothra = foldM (\s it -> it >>= \t -> return $ s `mappend` t)+ mempty+ iters - bs <- mothra stream2stream+ bs <- enum mothra >>= run >>= return . unWrap let e2 = enumBS bs
test/suite/Snap/Internal/Http/Server/Tests.hs view
@@ -1,3 +1,4 @@+{-# LANGUAGE CPP #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE PackageImports #-}@@ -5,39 +6,49 @@ module Snap.Internal.Http.Server.Tests ( tests ) where -import Control.Concurrent-import Control.Exception (try, SomeException)-import Control.Monad+import Control.Concurrent+import Control.Exception (try, throwIO, SomeException)+import Control.Monad import "monads-fd" Control.Monad.Trans-import qualified Data.ByteString as S-import qualified Data.ByteString.Lazy as L-import qualified Data.ByteString.Lazy.Char8 as LC-import Data.ByteString (ByteString)-import Data.ByteString.Internal (c2w, w2c)-import Data.Char-import Data.Int-import Data.IORef-import Data.Iteratee.WrappedByteString-import qualified Data.Map as Map-import Data.Maybe (fromJust)-import Data.Monoid-import Data.Time.Calendar-import Data.Time.Clock-import Data.Word-import qualified Network.HTTP as HTTP-import Prelude hiding (take)-import qualified Prelude-import Test.Framework-import Test.Framework.Providers.HUnit-import Test.HUnit hiding (Test, path)+import qualified Data.ByteString.Char8 as S+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString.Lazy.Char8 as LC+import Data.ByteString (ByteString)+import Data.ByteString.Internal (c2w, w2c)+import Data.Char+import Data.Int+import Data.IORef+import Data.Iteratee.WrappedByteString+import qualified Data.Map as Map+import Data.Maybe (fromJust)+import Data.Monoid+import Data.Time.Calendar+import Data.Time.Clock+import Data.Word+import qualified Network.HTTP as HTTP+import qualified Network.Socket.ByteString as N+import Prelude hiding (take)+import qualified Prelude+import Test.Framework+import Test.Framework.Providers.HUnit+import Test.HUnit hiding (Test, path) +import qualified Snap.Http.Server as Svr -import Snap.Internal.Http.Types-import Snap.Internal.Http.Server-import Snap.Iteratee-import Snap.Types+import Snap.Internal.Debug+import Snap.Internal.Http.Types+import Snap.Internal.Http.Server+import Snap.Iteratee+import Snap.Test.Common+import Snap.Types +#ifdef LIBEV+import qualified Snap.Internal.Http.Server.LibevBackend as Backend+#else+import qualified Snap.Internal.Http.Server.SimpleBackend as Backend+#endif + tests :: [Test] tests = [ testHttpRequest1 , testMultiRequest@@ -51,13 +62,24 @@ , testHttp1 , testHttp2 , testHttp100+ , testExpectGarbage , testPartialParse , testMethodParsing , testServerStartupShutdown+ , testServerShutdownWithOpenConns , testChunkOn1_0- , testSendFile ]+ , testSendFile+ , testTrivials] +testTrivials :: Test+testTrivials = testCase "server/trivials" $ do+ let !v = Svr.snapServerVersion+ let !s1 = show Backend.BackendTerminatedException+ let !s2 = show Backend.TimeoutException++ return $! v `seq` s1 `seq` s2 `seq` ()+ ------------------------------------------------------------------------------ -- HTTP request tests @@ -84,6 +106,17 @@ , "\r\n" , "0123456789" ] +sampleRequestExpectGarbage :: ByteString+sampleRequestExpectGarbage =+ S.concat [ "\r\nGET /foo/bar.html?param1=abc¶m2=def%20+¶m1=abc HTTP/1.1\r\n"+ , "Host: www.zabble.com:7777\r\n"+ , "Content-Length: 10\r\n"+ , "Expect: wuzzawuzzawuzza\r\n"+ , "X-Random-Other-Header: foo\r\n bar\r\n"+ , "Cookie: foo=\"bar\\\"\"\r\n"+ , "\r\n"+ , "0123456789" ]+ sampleRequest1_0 :: ByteString sampleRequest1_0 = S.concat [ "\r\nGET /foo/bar.html?param1=abc¶m2=def%20+¶m1=abc HTTP/1.0\r\n"@@ -96,7 +129,7 @@ testMethodParsing :: Test testMethodParsing =- testCase "method parsing" $ Prelude.mapM_ testOneMethod ms+ testCase "server/method parsing" $ Prelude.mapM_ testOneMethod ms where ms = [ GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT ] @@ -117,7 +150,7 @@ testHttpRequest1 :: Test testHttpRequest1 =- testCase "HttpRequest1" $ do+ testCase "server/HttpRequest1" $ do iter <- enumBS sampleRequest $ do r <- liftM fromJust $ rsm receiveRequest@@ -160,7 +193,7 @@ testMultiRequest :: Test testMultiRequest =- testCase "MultiRequest" $ do+ testCase "server/MultiRequest" $ do iter <- (enumBS sampleRequest >. enumBS sampleRequest) $ do r1 <- liftM fromJust $ rsm receiveRequest@@ -211,7 +244,7 @@ testPartialParse :: Test-testPartialParse = testCase "Short" $ do+testPartialParse = testCase "server/short" $ do iter <- enumBS sampleShortRequest $ liftM fromJust $ rsm receiveRequest expectException $ run iter@@ -237,7 +270,7 @@ testHttpRequest2 :: Test testHttpRequest2 =- testCase "HttpRequest2" $ do+ testCase "server/HttpRequest2" $ do iter <- enumBS sampleRequest2 $ do r <- liftM fromJust $ rsm receiveRequest@@ -253,7 +286,7 @@ testHttpRequest3 :: Test testHttpRequest3 =- testCase "HttpRequest3" $ do+ testCase "server/HttpRequest3" $ do iter <- enumBS sampleRequest3 $ do r <- liftM fromJust $ rsm receiveRequest@@ -286,7 +319,7 @@ testHttpRequest3' :: Test testHttpRequest3' =- testCase "HttpRequest3'" $ do+ testCase "server/HttpRequest3'" $ do iter <- enumBS sampleRequest3' $ do r <- liftM fromJust $ rsm receiveRequest@@ -347,13 +380,11 @@ testHttpResponse1 :: Test-testHttpResponse1 = testCase "HttpResponse1" $ do+testHttpResponse1 = testCase "server/HttpResponse1" $ do let onSendFile = \f _ -> enumFile f copyingStream2stream >>= run - buf <- mkIterateeBuffer- b <- run $ rsm $- sendResponse rsp1 copyingStream2stream buf (return ()) onSendFile >>=+ sendResponse rsp1 copyingStream2stream onSendFile >>= return . fromWrap . snd assertEqual "http response" (L.concat [@@ -373,13 +404,11 @@ testHttpResponse2 :: Test-testHttpResponse2 = testCase "HttpResponse2" $ do+testHttpResponse2 = testCase "server/HttpResponse2" $ do let onSendFile = \f _ -> enumFile f copyingStream2stream >>= run - buf <- mkIterateeBuffer- b2 <- run $ rsm $- sendResponse rsp2 copyingStream2stream buf (return ()) onSendFile >>=+ sendResponse rsp2 copyingStream2stream onSendFile >>= return . fromWrap . snd assertEqual "http response" (L.concat [@@ -399,13 +428,11 @@ testHttpResponse3 :: Test-testHttpResponse3 = testCase "HttpResponse3" $ do+testHttpResponse3 = testCase "server/HttpResponse3" $ do let onSendFile = \f _ -> enumFile f copyingStream2stream >>= run - buf <- mkIterateeBuffer- b3 <- run $ rsm $- sendResponse rsp3 copyingStream2stream buf (return ()) onSendFile >>=+ sendResponse rsp3 copyingStream2stream onSendFile >>= return . fromWrap . snd assertEqual "http response" b3 $ L.concat [@@ -431,13 +458,11 @@ testHttpResponse4 :: Test-testHttpResponse4 = testCase "HttpResponse4" $ do+testHttpResponse4 = testCase "server/HttpResponse4" $ do let onSendFile = \f _ -> enumFile f copyingStream2stream >>= run - buf <- mkIterateeBuffer- b <- run $ rsm $- sendResponse rsp1 copyingStream2stream buf (return ()) onSendFile >>=+ sendResponse rsp1 copyingStream2stream onSendFile >>= return . fromWrap . snd assertEqual "http response" (L.concat [@@ -480,7 +505,7 @@ testHttp1 :: Test-testHttp1 = testCase "http session" $ do+testHttp1 = testCase "server/http session" $ do let enumBody = enumBS sampleRequest >. enumBS sampleRequest2 ref <- newIORef ""@@ -526,7 +551,7 @@ testChunkOn1_0 :: Test-testChunkOn1_0 = testCase "transfer-encoding chunked" $ do+testChunkOn1_0 = testCase "server/transfer-encoding chunked" $ do let enumBody = enumBS sampleRequest1_0 ref <- newIORef ""@@ -543,7 +568,7 @@ assertBool "connection close" $ S.isInfixOf "connection: close" output where- lower = S.map (c2w . toLower . w2c) . S.concat . L.toChunks+ lower = S.map toLower . S.concat . L.toChunks f :: ServerHandler f _ req = do@@ -565,7 +590,7 @@ testHttp2 :: Test-testHttp2 = testCase "connection: close" $ do+testHttp2 = testCase "server/connection: close" $ do let enumBody = enumBS sampleRequest4 >. enumBS sampleRequest2 ref <- newIORef ""@@ -607,7 +632,7 @@ testHttp100 :: Test-testHttp100 = testCase "Expect: 100-continue" $ do+testHttp100 = testCase "server/Expect: 100-continue" $ do let enumBody = enumBS sampleRequestExpectContinue ref <- newIORef ""@@ -648,8 +673,48 @@ assertBool "100 Continue" ok +testExpectGarbage :: Test+testExpectGarbage = testCase "server/Expect: garbage" $ do+ let enumBody = enumBS sampleRequestExpectGarbage + ref <- newIORef "" + let (iter,onSendFile) = mkIter ref++ runHTTP "localhost"+ "127.0.0.1"+ 80+ "127.0.0.1"+ 58384+ Nothing+ Nothing+ enumBody+ iter+ onSendFile+ (return ())+ echoServer2++ s <- readIORef ref++ let lns = LC.lines s++ let ok = case lns of+ ([ "HTTP/1.1 200 OK\r"+ , "Content-Length: 10\r"+ , d1+ , s1+ , "Set-Cookie: foo=bar; path=/; expires=Sat, 30-Jan-2010 00:00:00 GMT; domain=.foo.com\r"+ , "\r"+ , "0123456789" ]) -> (("Date" `L.isPrefixOf` d1) &&+ ("Server" `L.isPrefixOf` s1))++ _ -> False++ assertBool "random expect: header" ok++++ pongServer :: Snap () pongServer = modifyResponse $ setResponseBody (enumBS "PONG") . setContentType "text/plain" .@@ -660,7 +725,7 @@ testSendFile :: Test-testSendFile = testCase "sendFile" $ do+testSendFile = testCase "server/sendFile" $ do tid <- forkIO $ httpServe "*" port "localhost" Nothing Nothing $ runSnap sendFileFoo@@ -680,7 +745,7 @@ testServerStartupShutdown :: Test-testServerStartupShutdown = testCase "startup/shutdown" $ do+testServerStartupShutdown = testCase "server/startup/shutdown" $ do tid <- forkIO $ httpServe "*" port@@ -703,4 +768,49 @@ where waitabit = threadDelay $ 2*((10::Int)^(6::Int)) port = 8145+++testServerShutdownWithOpenConns :: Test+testServerShutdownWithOpenConns = testCase "server/shutdown-open-conns" $ do+ tid <- forkIO $+ httpServe "127.0.0.1"+ port+ "localhost"+ Nothing+ Nothing+ (runSnap pongServer)++ waitabit++ result <- newEmptyMVar++ forkIO $ do+ e <- try $ withSock port $ \sock -> do+ N.sendAll sock "GET /"+ waitabit+ killThread tid+ waitabit+ N.sendAll sock "pong HTTP/1.1\r\n"+ N.sendAll sock "Host: 127.0.0.1\r\n"+ N.sendAll sock "Content-Length: 0\r\n"+ N.sendAll sock "Connection: close\r\n\r\n"++ resp <- recvAll sock+ when (S.null resp) $ throwIO Backend.BackendTerminatedException++ let s = S.unpack $ Prelude.head $ ditchHeaders $ S.lines resp+ debug $ "got HTTP response " ++ s ++ ", we shouldn't be here...."++ putMVar result e++ r <- takeMVar result++ case r of+ (Left (_::SomeException)) -> return ()+ (Right _) -> assertFailure "socket didn't get killed"+++ where+ waitabit = threadDelay $ 2*((10::Int)^(6::Int))+ port = 8146
− test/suite/Snap/Test/Common.hs
@@ -1,20 +0,0 @@-{-# OPTIONS_GHC -fno-warn-orphans #-}--module Snap.Test.Common where--import Control.Monad-import qualified Data.ByteString as S-import qualified Data.ByteString.Lazy as L-import Data.ByteString.Internal (c2w)-import Test.QuickCheck---instance Arbitrary S.ByteString where- arbitrary = liftM (S.pack . map c2w) arbitrary--instance Arbitrary L.ByteString where- arbitrary = do- n <- choose(0,5)- chunks <- replicateM n arbitrary- return $ L.fromChunks chunks-
+ test/suite/Test/Blackbox.hs view
@@ -0,0 +1,185 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-}+{-# LANGUAGE PackageImports #-}++module Test.Blackbox+ ( tests+ , startTestServer ) where+++import Control.Concurrent+import Control.Monad+import Control.Monad.CatchIO+import Data.ByteString.Char8 (ByteString)+import qualified Data.ByteString.Char8 as S+import qualified Data.DList as D+import Data.Int+import Data.Maybe (fromJust)+import qualified Network.HTTP as HTTP+import qualified Network.URI as URI+import Network.Socket+import qualified Network.Socket.ByteString as N+import Prelude hiding (take)+import Test.Framework+import Test.Framework.Providers.HUnit+import Test.Framework.Providers.QuickCheck2+import Test.HUnit hiding (Test, path)+import Test.QuickCheck+import qualified Test.QuickCheck.Monadic as QC+import Test.QuickCheck.Monadic hiding (run, assert)++import Snap.Http.Server+import Snap.Test.Common++import Test.Common.Rot13+import Test.Common.TestHandler+++tests :: Int -> [Test]+tests port = map ($ port) [ testPong+ , testEcho+ , testRot13+ , testSlowLoris+ , testBlockingRead+ , testBigResponse+ , testPartial ]+++startTestServer :: IO (ThreadId,Int)+startTestServer = do+ tid <- forkIO $+ httpServe "*"+ port+ "localhost"+ (Just "ts-access.log")+ (Just "ts-error.log")+ testHandler+ waitabit++ return $ (tid, port)++ where+ port = 8199+++doPong :: Int -> IO String+doPong port = do+ rsp <- HTTP.simpleHTTP $+ HTTP.getRequest $+ "http://localhost:" ++ show port ++ "/pong"++ HTTP.getResponseBody rsp+++testPong :: Int -> Test+testPong port = testCase "blackbox/pong" $ do+ doc <- doPong port+ assertEqual "pong response" "PONG" doc+++testEcho :: Int -> Test+testEcho port = testProperty "blackbox/echo" $+ monadicIO $ forAllM arbitrary prop+ where+ prop txt = do+ let uri = fromJust $+ URI.parseURI $+ "http://localhost:" ++ show port ++ "/echo"++ let len = S.length txt++ let req' = (HTTP.mkRequest HTTP.POST uri) :: HTTP.Request S.ByteString+ let req = HTTP.replaceHeader HTTP.HdrContentLength (show len) req'++ rsp <- QC.run $ HTTP.simpleHTTP $ req { HTTP.rqBody = (txt::S.ByteString) }+ doc <- QC.run $ HTTP.getResponseBody rsp++ QC.assert $ txt == doc+++testRot13 :: Int -> Test+testRot13 port = testProperty "blackbox/rot13" $+ monadicIO $ forAllM arbitrary prop+ where+ prop txt = do+ let uri = fromJust $+ URI.parseURI $+ "http://localhost:" ++ show port ++ "/rot13"++ let len = S.length txt++ let req' = (HTTP.mkRequest HTTP.POST uri) :: HTTP.Request S.ByteString+ let req = HTTP.replaceHeader HTTP.HdrContentLength (show len) req'++ rsp <- QC.run $ HTTP.simpleHTTP $ req { HTTP.rqBody = (txt::S.ByteString) }+ doc <- QC.run $ HTTP.getResponseBody rsp++ QC.assert $ txt == rot13 doc+++testSlowLoris :: Int -> Test+testSlowLoris port = testCase "blackbox/slowloris" $ withSock port go++ where+ go sock = do+ N.sendAll sock "POST /echo HTTP/1.1\r\n"+ N.sendAll sock "Host: 127.0.0.1\r\n"+ N.sendAll sock "Content-Length: 2500000\r\n"+ N.sendAll sock "Connection: close\r\n\r\n"++ b <- expectExceptionBeforeTimeout (loris sock) 60++ assertBool "didn't catch slow loris attack" b++ loris sock = do+ N.sendAll sock "."+ waitabit+ loris sock+++testBlockingRead :: Int -> Test+testBlockingRead port = testCase "blackbox/testBlockingRead" $+ withSock port $ \sock -> do+ N.sendAll sock "GET /"+ waitabit+ N.sendAll sock "pong HTTP/1.1\r\n"+ N.sendAll sock "Host: 127.0.0.1\r\n"+ N.sendAll sock "Content-Length: 0\r\n"+ N.sendAll sock "Connection: close\r\n\r\n"++ resp <- recvAll sock++ let s = head $ ditchHeaders $ S.lines resp++ assertEqual "pong response" "PONG" s+++-- test server's ability to trap/recover from IO errors+testPartial :: Int -> Test+testPartial port = testCase "blackbox/testPartial" $ do+ withSock port $ \sock ->+ N.sendAll sock "GET /pong HTTP/1.1\r\n"++ doc <- doPong port+ assertEqual "pong response" "PONG" doc+++testBigResponse :: Int -> Test+testBigResponse port = testCase "blackbox/testBigResponse" $+ withSock port $ \sock -> do+ N.sendAll sock "GET /bigresponse HTTP/1.1\r\n"+ N.sendAll sock "Host: 127.0.0.1\r\n"+ N.sendAll sock "Content-Length: 0\r\n"+ N.sendAll sock "Connection: close\r\n\r\n"++ let body = S.replicate 4000000 '.'+ resp <- recvAll sock++ let s = head $ ditchHeaders $ S.lines resp++ assertBool "big response" $ body == s+++------------------------------------------------------------------------------+waitabit :: IO ()+waitabit = threadDelay $ 2*((10::Int)^(6::Int))
test/suite/TestSuite.hs view
@@ -1,17 +1,28 @@ module Main where -import Test.Framework (defaultMain, testGroup)+import Control.Concurrent (killThread)+import Test.Framework (defaultMain, testGroup) ++ import qualified Data.Concurrent.HashMap.Tests import qualified Snap.Internal.Http.Parser.Tests import qualified Snap.Internal.Http.Server.Tests+import qualified Test.Blackbox main :: IO ()-main = defaultMain tests- where tests = [ testGroup "Data.Concurrent.HashMap.Tests"- Data.Concurrent.HashMap.Tests.tests- , testGroup "Snap.Internal.Http.Parser.Tests"- Snap.Internal.Http.Parser.Tests.tests- , testGroup "Snap.Internal.Http.Server.Tests"- Snap.Internal.Http.Server.Tests.tests- ]+main = do+ (tid,pt) <- Test.Blackbox.startTestServer+ defaultMain $ tests pt+ killThread tid++ where tests pt =+ [ testGroup "Data.Concurrent.HashMap.Tests"+ Data.Concurrent.HashMap.Tests.tests+ , testGroup "Snap.Internal.Http.Parser.Tests"+ Snap.Internal.Http.Parser.Tests.tests+ , testGroup "Snap.Internal.Http.Server.Tests"+ Snap.Internal.Http.Server.Tests.tests+ , testGroup "Test.Blackbox"+ $ Test.Blackbox.tests pt+ ]
test/testserver/Main.hs view
@@ -1,13 +1,15 @@+{-# LANGUAGE BangPatterns #-} {-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE RankNTypes #-}+ module Main where import Control.Concurrent -import Snap.Iteratee-import Snap.Types import Snap.Http.Server-import Snap.Util.FileServe+import Test.Common.TestHandler + {- /pong@@ -18,33 +20,7 @@ -} -pongHandler :: Snap ()-pongHandler = modifyResponse $ setResponseBody (enumBS "PONG") .- setContentType "text/plain" .- setContentLength 4 -echoHandler :: Snap ()-echoHandler = do- req <- getRequest- writeBS $ rqPathInfo req--responseHandler = do- code <- getParam "code"- case code of- Nothing -> undefined- Just code -> f code- where- f "300" = undefined- f "304" = undefined--handlers :: Snap ()-handlers =- route [ ("pong", pongHandler)- , ("echo", echoHandler)- , ("fileserve", fileServe "static")- , ("respcode/:code", responseHandler)- ]- main :: IO () main = do m <- newEmptyMVar@@ -56,6 +32,7 @@ where go m = do- httpServe "*" 3000 "localhost" Nothing Nothing handlers + httpServe "*" 3000 "localhost" (Just "ts-access.log")+ (Just "ts-error.log") testHandler putMVar m ()
− test/testserver/Paths_snap_server.hs
@@ -1,9 +0,0 @@-module Paths_snap_server (- version- ) where--import Data.Version (Version(..))--version :: Version-version = Version {versionBranch = [0,0,0], versionTags = ["unknown"]}-