@@ -37,6 +37,16 @@ module Network.Wai.Handler.Warp
37 -- * Datatypes
37 -- * Datatypes
39 , InvalidRequest (..)
39 , InvalidRequest (..)
41 , takeHeaders
51 , takeHeaders
@@ -61,7 +71,7 @@ import qualified Network.Socket
61import qualified Network.Socket.ByteString as Sock
71import qualified Network.Socket.ByteString as Sock
62import Control.Exception
72import Control.Exception
63 ( bracket, finally, Exception, SomeException, catch
73 ( bracket, finally, Exception, SomeException, catch
64− , fromException
74+ , fromException, AsyncException (ThreadKilled)
66import Control.Concurrent (forkIO)
76import Control.Concurrent (forkIO)
67import qualified Data.Char as C
77import qualified Data.Char as C
@@ -86,6 +96,7 @@ import qualified System.PosixCompat.Files as P
87import Control.Monad.IO.Class (liftIO)
97import Control.Monad.IO.Class (liftIO)
88import qualified Timeout as T
98import qualified Timeout as T
99+import Timeout (Manager, registerKillThread, pause, resume)
89import Data.Word (Word8)
100import Data.Word (Word8)
90import Data.List (foldl')
101import Data.List (foldl')
91import Control.Monad (forever)
102import Control.Monad (forever)
@@ -173,7 +184,7 @@ serveConnection th onException port app conn remoteHost' = do
173 liftIO $ T.pause th
184 liftIO $ T.pause th
174 res <- E.joinI $ EB.isolate len $$ app env
185 res <- E.joinI $ EB.isolate len $$ app env
175 liftIO $ T.resume th
186 liftIO $ T.resume th
176− keepAlive <- liftIO $ sendResponse th env (httpVersion env) conn res
187+ keepAlive <- liftIO $ sendResponse th env conn res
177 if keepAlive then serveConnection' else return ()
188 if keepAlive then serveConnection' else return ()
179parseRequest :: Port -> SockAddr -> E.Iteratee S.ByteString IO (Integer, Request)
190parseRequest :: Port -> SockAddr -> E.Iteratee S.ByteString IO (Integer, Request)
@@ -297,8 +308,8 @@ hasBody :: H.Status -> Request -> Bool
297hasBody s req = s /= (H.Status 204 "") && requestMethod req /= "HEAD"
308hasBody s req = s /= (H.Status 204 "") && requestMethod req /= "HEAD"
299sendResponse :: T.Handle
310sendResponse :: T.Handle
300− -> Request -> H.HttpVersion -> Socket -> Response -> IO Bool
311+ -> Request -> Socket -> Response -> IO Bool
301−sendResponse th req hv socket (ResponseFile s hs fp mpart) = do
312+sendResponse th req socket (ResponseFile s hs fp mpart) = do
302 (hs', cl) <-
313 (hs', cl) <-
303 case (readInt `fmap` lookup "content-length" hs, mpart) of
314 case (readInt `fmap` lookup "content-length" hs, mpart) of
304 (Just cl, _) -> return (hs, cl)
315 (Just cl, _) -> return (hs, cl)
@@ -308,7 +319,7 @@ sendResponse th req hv socket (ResponseFile s hs fp mpart) = do
308 (Nothing, Just part) -> do
319 (Nothing, Just part) -> do
309 let cl = filePartByteCount part
320 let cl = filePartByteCount part
310 return (("Content-Length", B.pack $ show cl):hs, fromIntegral cl)
321 return (("Content-Length", B.pack $ show cl):hs, fromIntegral cl)
311− Sock.sendMany socket $ L.toChunks $ toLazyByteString $ headers hv s hs' False
322+ Sock.sendMany socket $ L.toChunks $ toLazyByteString $ headers (httpVersion req) s hs' False
312 if hasBody s req
323 if hasBody s req
314 case mpart of
325 case mpart of
@@ -323,7 +334,7 @@ sendResponse th req hv socket (ResponseFile s hs fp mpart) = do
323 T.tickle th
334 T.tickle th
324 return True
335 return True
325 else return True
336 else return True
326−sendResponse th req hv socket (ResponseBuilder s hs b)
337+sendResponse th req socket (ResponseBuilder s hs b)
327 | hasBody s req = do
338 | hasBody s req = do
328 toByteStringIO (\bs -> do
339 toByteStringIO (\bs -> do
329 Sock.sendAll socket bs
340 Sock.sendAll socket bs
@@ -333,35 +344,35 @@ sendResponse th req hv socket (ResponseBuilder s hs b)
333 Sock.sendMany socket
344 Sock.sendMany socket
334 $ L.toChunks
345 $ L.toChunks
335 $ toLazyByteString
346 $ toLazyByteString
336− $ headers hv s hs False
347+ $ headers (httpVersion req) s hs False
337 T.tickle th
348 T.tickle th
338 return True
349 return True
340− headers' = headers hv s hs isChunked'
351+ headers' = headers (httpVersion req) s hs isChunked'
341 b' = if isChunked'
352 b' = if isChunked'
342 then headers'
353 then headers'
343 `mappend` chunkedTransferEncoding b
354 `mappend` chunkedTransferEncoding b
344 `mappend` chunkedTransferTerminator
355 `mappend` chunkedTransferTerminator
345− else headers hv s hs False `mappend` b
356+ else headers (httpVersion req) s hs False `mappend` b
346 hasLength = lookup "content-length" hs /= Nothing
357 hasLength = lookup "content-length" hs /= Nothing
347− isChunked' = isChunked hv && not hasLength
358+ isChunked' = isChunked (httpVersion req) && not hasLength
348 isKeepAlive = isChunked' || hasLength
359 isKeepAlive = isChunked' || hasLength
349−sendResponse th req hv socket (ResponseEnumerator res) =
360+sendResponse th req socket (ResponseEnumerator res) =
352 -- FIXME perhaps alloca a buffer per thread and reuse that in all functiosn below. Should lessen greatly the GC burden (I hope)
363 -- FIXME perhaps alloca a buffer per thread and reuse that in all functiosn below. Should lessen greatly the GC burden (I hope)
353 go s hs | not (hasBody s req) = do
364 go s hs | not (hasBody s req) = do
354 liftIO $ Sock.sendMany socket
365 liftIO $ Sock.sendMany socket
355 $ L.toChunks $ toLazyByteString
366 $ L.toChunks $ toLazyByteString
356− $ headers hv s hs False
367+ $ headers (httpVersion req) s hs False
357 return True
368 return True
358 go s hs = chunk'
369 go s hs = chunk'
359− $ E.enumList 1 [headers hv s hs isChunked']
370+ $ E.enumList 1 [headers (httpVersion req) s hs isChunked']
360 $$ E.joinI $ builderToByteString -- FIXME unsafeBuilderToByteString
371 $$ E.joinI $ builderToByteString -- FIXME unsafeBuilderToByteString
361 $$ (iterSocket th socket >> return isKeepAlive)
372 $$ (iterSocket th socket >> return isKeepAlive)
363 hasLength = lookup "content-length" hs /= Nothing
374 hasLength = lookup "content-length" hs /= Nothing
364− isChunked' = isChunked hv && not hasLength
375+ isChunked' = isChunked (httpVersion req) && not hasLength
365 isKeepAlive = isChunked' || hasLength
376 isKeepAlive = isChunked' || hasLength
366 chunk' i = if isChunked'
377 chunk' i = if isChunked'
367 then E.joinI $ chunk $$ i
378 then E.joinI $ chunk $$ i
@@ -437,12 +448,17 @@ defaultSettings = Settings
437 , settingsOnException = \e ->
448 , settingsOnException = \e ->
438 case fromException e of
449 case fromException e of
439 Just x -> go x
450 Just x -> go x
452+ if go' $ fromException e
440− Nothing -> hPutStrLn stderr $ show e
453+ then hPutStrLn stderr $ show e
441 , settingsTimeout = 30
455 , settingsTimeout = 30
444 go :: InvalidRequest -> IO ()
458 go :: InvalidRequest -> IO ()
445 go _ = return ()
459 go _ = return ()
460+ go' (Just ThreadKilled) = False
447takeHeaders :: E.Iteratee ByteString IO [ByteString]
463takeHeaders :: E.Iteratee ByteString IO [ByteString]
448takeHeaders = do
464takeHeaders = do
@@ -517,3 +533,12 @@ checkCR bs pos =
517-- invalid content-length is a bad idea, mkay?
533-- invalid content-length is a bad idea, mkay?
518readInt :: S.ByteString -> Integer
534readInt :: S.ByteString -> Integer
519readInt = S.foldl' (\x w -> x * 10 + fromIntegral w - 48) 0
535readInt = S.foldl' (\x w -> x * 10 + fromIntegral w - 48) 0
537+-- | Call the inner function with a timeout manager.
538+withManager :: Int -- ^ timeout in microseconds
539+ -> (Manager -> IO a)
541+withManager timeout f = do
542+ -- FIXME when stopManager is available, use it
543+ man <- T.initialize timeout