From 7ffc3b3e4df517d95221ab0f1934325fd2845ed3 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 24 Dec 2010 08:22:17 +0200 Subject: [PATCH 01/90] first commit --- README | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 README diff --git a/README b/README new file mode 100644 index 000000000..e69de29bb From 781d4005c8170405a8e0311eead222be6a47b243 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 24 Dec 2010 08:31:15 +0200 Subject: [PATCH 02/90] Initial code import from wai-extra --- LICENSE | 25 ++++ Network/Wai/Handler/Warp.hs | 233 ++++++++++++++++++++++++++++++++++++ Setup.lhs | 7 ++ warp.cabal | 28 +++++ 4 files changed, 293 insertions(+) create mode 100644 LICENSE create mode 100755 Network/Wai/Handler/Warp.hs create mode 100755 Setup.lhs create mode 100644 warp.cabal diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..482531586 --- /dev/null +++ b/LICENSE @@ -0,0 +1,25 @@ +The following license covers this documentation, and the source code, except +where otherwise indicated. + +Copyright 2010, Suite Solutions. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "AS IS" AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs new file mode 100755 index 000000000..0afde0c64 --- /dev/null +++ b/Network/Wai/Handler/Warp.hs @@ -0,0 +1,233 @@ +{-# LANGUAGE DeriveDataTypeable #-} +{-# LANGUAGE FlexibleContexts #-} +{-# LANGUAGE OverloadedStrings #-} +--------------------------------------------------------- +-- | +-- Module : Network.Wai.Handler.Warp +-- Copyright : Michael Snoyman +-- License : BSD3 +-- +-- Maintainer : Michael Snoyman +-- Stability : Stable +-- Portability : portable +-- +-- A fast, light-weight HTTP server handler for WAI. +-- +--------------------------------------------------------- +module Network.Wai.Handler.Warp + ( run + , sendResponse + , parseRequest + ) where + +import Network.Wai +import qualified System.IO + +import Data.ByteString (ByteString) +import qualified Data.ByteString.Char8 as B +import Network + ( listenOn, accept, sClose, PortID(PortNumber), Socket + , withSocketsDo) +import Control.Exception (bracket, finally, Exception, throwIO) +import System.IO (Handle, hClose, hFlush) +import System.IO.Error (isEOFError, ioeGetHandle) +import Control.Concurrent (forkIO) +import Control.Monad (unless) +import Data.Maybe (fromMaybe) + +import Data.Typeable (Typeable) + +import Control.Arrow (first) + +import Data.Enumerator (($$), (>>==)) +import qualified Data.Enumerator as E +import Data.Enumerator.IO (iterHandle) +import Blaze.ByteString.Builder.Enumerator (builderToByteString) +import Blaze.ByteString.Builder.HTTP + (chunkedTransferEncoding, chunkedTransferTerminator) +import Blaze.ByteString.Builder (fromByteString, Builder) +import Blaze.ByteString.Builder.Char8 (fromChar, fromString) +import Data.Monoid (mconcat) + +import Control.Monad.IO.Class (liftIO) +import Data.ByteString.Lazy.Internal (defaultChunkSize) + +run :: Port -> Application -> IO () +run port = withSocketsDo . + bracket + (listenOn $ PortNumber $ fromIntegral port) + sClose . + serveConnections port +type Port = Int + +serveConnections :: Port -> Application -> Socket -> IO () +serveConnections port app socket = do + (conn, remoteHost', _) <- accept socket -- FIXME use sockets directly instead of Handlers? + _ <- forkIO $ serveConnection port app conn remoteHost' + serveConnections port app socket + +serveConnection :: Port -> Application -> Handle -> String -> IO () +serveConnection port app conn remoteHost' = do + catch + (finally + serveConnection' + (hClose conn)) + catchEOFError + where serveConnection' = do + env <- parseRequest port conn remoteHost' + res <- app env + sendResponse (httpVersion env) conn res + -- FIXME drain request body + hFlush conn + serveConnection' + + catchEOFError :: IOError -> IO () + catchEOFError e | isEOFError e = case ioeGetHandle e of + Just h -> unless (h == conn) (ioError e) + Nothing -> ioError e + | otherwise = ioError e + +parseRequest :: Port -> Handle -> String -> IO Request +parseRequest port conn remoteHost' = do + headers' <- takeUntilBlank conn id + parseRequest' port headers' conn remoteHost' + +takeUntilBlank :: Handle + -> ([ByteString] -> [ByteString]) + -> IO [ByteString] +takeUntilBlank h front = do + l <- stripCR `fmap` B.hGetLine h + if B.null l + then return $ front [] + else takeUntilBlank h $ front . (:) l + +stripCR :: ByteString -> ByteString +stripCR bs + | B.null bs = bs + | B.last bs == '\r' = B.init bs + | otherwise = bs + +data InvalidRequest = + NotEnoughLines [String] + | BadFirstLine String + | NonHttp + deriving (Show, Typeable) +instance Exception InvalidRequest + +-- | Parse a set of header lines and body into a 'Request'. +parseRequest' :: Port + -> [ByteString] + -> Handle + -> String + -> IO Request +parseRequest' port lines' handle remoteHost' = do + (firstLine, otherLines) <- + case lines' of + x:xs -> return (x, xs) + [] -> throwIO $ NotEnoughLines $ map B.unpack lines' + (method, rpath', gets, httpversion) <- parseFirst firstLine + let rpath = '/' : case B.unpack rpath' of + ('/':x) -> x + _ -> B.unpack rpath' + let heads = map (first mkCIByteString . parseHeaderNoAttr) otherLines + let host = fromMaybe "" $ lookup "host" heads + let len = fromMaybe 0 $ do + bs <- lookup "Content-Length" heads + let str = B.unpack bs + case reads str of + (x, _):_ -> Just x + _ -> Nothing + let (serverName', _) = B.break (== ':') host + return $ Request + { requestMethod = method + , httpVersion = httpversion + , pathInfo = B.pack rpath + , queryString = gets + , serverName = serverName' + , serverPort = port + , requestHeaders = heads + , isSecure = False + , requestBody = requestBodyHandle handle len + , errorHandler = System.IO.hPutStr System.IO.stderr + , remoteHost = B.pack remoteHost' + } + +parseFirst :: ByteString + -> IO (ByteString, ByteString, ByteString, HttpVersion) +parseFirst s = do + let pieces = B.words s + (method, query, http') <- + case pieces of + [x, y, z] -> return (x, y, z) + _ -> throwIO $ BadFirstLine $ B.unpack s + let (hfirst, hsecond) = B.splitAt 5 http' + unless (hfirst == B.pack "HTTP/") $ throwIO NonHttp + let (rpath, qstring) = B.break (== '?') query + return (method, rpath, qstring, hsecond) + +headers :: HttpVersion -> Status -> ResponseHeaders -> Builder +headers httpversion status responseHeaders = mconcat + [ fromByteString "HTTP/" + , fromByteString httpversion + , fromChar ' ' + , fromString $ show $ statusCode status + , fromChar ' ' + , fromByteString $ statusMessage status + , fromByteString "\r\n" + , mconcat $ map go responseHeaders + , fromByteString "Transfer-Encoding: chunked\r\n\r\n" -- FIXME perhaps put in non-chunked for HTTP 1.0 clients? + ] + where + go (x, y) = mconcat + [ fromByteString $ ciOriginal x + , fromByteString ": " + , fromByteString y + , fromByteString "\r\n" + ] + +sendResponse :: HttpVersion -> Handle -> Response -> IO () +sendResponse hv handle res = do + responseEnumerator res $ \s hs -> + E.joinI $ chunk + $$ E.enumList 1 [headers hv s hs] + $$ E.joinI $ builderToByteString + $$ iterHandle handle + where + chunk :: E.Enumeratee Builder Builder IO () + chunk = E.checkDone $ E.continue . step + step k E.EOF = k (E.Chunks [chunkedTransferTerminator]) >>== return + step k (E.Chunks []) = E.continue $ step k + step k (E.Chunks builders) = + k (E.Chunks [chunked]) >>== chunk + where + chunked = chunkedTransferEncoding $ mconcat builders + +parseHeaderNoAttr :: ByteString -> (ByteString, ByteString) +parseHeaderNoAttr s = + let (k, rest) = B.span (/= ':') s + rest' = if not (B.null rest) && + B.head rest == ':' && + not (B.null $ B.tail rest) && + B.head (B.tail rest) == ' ' + then B.drop 2 rest + else rest + in (k, rest') + +requestBodyHandle :: Handle -> Int -> E.Enumerator B.ByteString IO a +requestBodyHandle h = + requestBodyFunc go + where + go i = Just `fmap` B.hGet h (min i defaultChunkSize) + +requestBodyFunc :: (Int -> IO (Maybe B.ByteString)) + -> Int + -> E.Enumerator B.ByteString IO a +requestBodyFunc _ 0 step = E.returnI step +requestBodyFunc h len (E.Continue k) = do + mbs <- liftIO $ h len + case mbs of + Nothing -> E.continue k + Just bs -> do + let newLen = len - B.length bs + k (E.Chunks [bs]) >>== requestBodyFunc h newLen +requestBodyFunc _ _ step = E.returnI step diff --git a/Setup.lhs b/Setup.lhs new file mode 100755 index 000000000..06e2708f2 --- /dev/null +++ b/Setup.lhs @@ -0,0 +1,7 @@ +#!/usr/bin/env runhaskell + +> module Main where +> import Distribution.Simple + +> main :: IO () +> main = defaultMain diff --git a/warp.cabal b/warp.cabal new file mode 100644 index 000000000..ab9560250 --- /dev/null +++ b/warp.cabal @@ -0,0 +1,28 @@ +Name: warp +Version: 0.3.0 +Synopsis: A fast, light-weight web server for WAI applications. +License: BSD3 +License-file: LICENSE +Author: Michael Snoyman +Maintainer: michael@snoyman.com +Homepage: http://github.com/snoyberg/warp +Category: Web, Yesod +Build-Type: Simple +Cabal-Version: >=1.6 +Stability: Stable + +Library + Build-Depends: base >= 3 && < 5 + , bytestring >= 0.9 && < 0.10 + , wai >= 0.3.0 && < 0.4 + , network >= 2.2.1.5 && < 2.4 + , blaze-builder-enumerator >= 0.2 && < 0.3 + , transformers >= 0.2 && < 0.3 + , enumerator >= 0.4 && < 0.5 + , blaze-builder >= 0.2.1.3 && < 0.3 + Exposed-modules: Network.Wai.Handler.Warp + ghc-options: -Wall + +source-repository head + type: git + location: git://github.com/snoyberg/warp.git From 689ddc9518718cfc4c0ada8f5c6861de67280ec6 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 24 Dec 2010 12:20:20 +0200 Subject: [PATCH 03/90] Disable chunking for HTTP 1.0 connections --- Network/Wai/Handler/Warp.hs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 0afde0c64..5cb4bf8d5 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -32,7 +32,7 @@ import Control.Exception (bracket, finally, Exception, throwIO) import System.IO (Handle, hClose, hFlush) import System.IO.Error (isEOFError, ioeGetHandle) import Control.Concurrent (forkIO) -import Control.Monad (unless) +import Control.Monad (unless, when) import Data.Maybe (fromMaybe) import Data.Typeable (Typeable) @@ -79,7 +79,7 @@ serveConnection port app conn remoteHost' = do sendResponse (httpVersion env) conn res -- FIXME drain request body hFlush conn - serveConnection' + when (isChunked $ httpVersion env) serveConnection' catchEOFError :: IOError -> IO () catchEOFError e | isEOFError e = case ioeGetHandle e of @@ -175,7 +175,9 @@ headers httpversion status responseHeaders = mconcat , fromByteString $ statusMessage status , fromByteString "\r\n" , mconcat $ map go responseHeaders - , fromByteString "Transfer-Encoding: chunked\r\n\r\n" -- FIXME perhaps put in non-chunked for HTTP 1.0 clients? + , if isChunked httpversion + then fromByteString "Transfer-Encoding: chunked\r\n\r\n" + else fromByteString "\r\n" ] where go (x, y) = mconcat @@ -185,6 +187,9 @@ headers httpversion status responseHeaders = mconcat , fromByteString "\r\n" ] +isChunked :: HttpVersion -> Bool +isChunked = (==) http11 + sendResponse :: HttpVersion -> Handle -> Response -> IO () sendResponse hv handle res = do responseEnumerator res $ \s hs -> From c8fc7edc79bb086205d8001d6e38035fb2a8c159 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 24 Dec 2010 13:15:41 +0200 Subject: [PATCH 04/90] Added pong sample --- pong.hs | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 pong.hs diff --git a/pong.hs b/pong.hs new file mode 100644 index 000000000..3bcc3bdf1 --- /dev/null +++ b/pong.hs @@ -0,0 +1,9 @@ +{-# LANGUAGE OverloadedStrings #-} +import Network.Wai +import Network.Wai.Handler.Warp +import Blaze.ByteString.Builder (fromByteString) + +main = run 3000 $ const $ return $ responseBuilder + status200 + [("Content-Type", "text/plain")] + $ fromByteString "PONG" From bef6e378aa91359f60784d6aef030ba65f766985 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 24 Dec 2010 13:40:46 +0200 Subject: [PATCH 05/90] Draining request body --- Network/Wai/Handler/Warp.hs | 48 ++++++++++++++++++------------------- undrained.hs | 9 +++++++ 2 files changed, 33 insertions(+), 24 deletions(-) create mode 100644 undrained.hs diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 5cb4bf8d5..c330f41d3 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -52,6 +52,8 @@ import Data.Monoid (mconcat) import Control.Monad.IO.Class (liftIO) import Data.ByteString.Lazy.Internal (defaultChunkSize) +import Data.IORef (IORef, writeIORef, readIORef, newIORef) + run :: Port -> Application -> IO () run port = withSocketsDo . bracket @@ -74,10 +76,11 @@ serveConnection port app conn remoteHost' = do (hClose conn)) catchEOFError where serveConnection' = do - env <- parseRequest port conn remoteHost' + (ilen, env) <- parseRequest port conn remoteHost' res <- app env + remaining <- readIORef ilen + _ <- B.hGet conn remaining -- FIXME just skip, don't read sendResponse (httpVersion env) conn res - -- FIXME drain request body hFlush conn when (isChunked $ httpVersion env) serveConnection' @@ -87,7 +90,7 @@ serveConnection port app conn remoteHost' = do Nothing -> ioError e | otherwise = ioError e -parseRequest :: Port -> Handle -> String -> IO Request +parseRequest :: Port -> Handle -> String -> IO (IORef Int, Request) parseRequest port conn remoteHost' = do headers' <- takeUntilBlank conn id parseRequest' port headers' conn remoteHost' @@ -119,7 +122,7 @@ parseRequest' :: Port -> [ByteString] -> Handle -> String - -> IO Request + -> IO (IORef Int, Request) parseRequest' port lines' handle remoteHost' = do (firstLine, otherLines) <- case lines' of @@ -138,7 +141,8 @@ parseRequest' port lines' handle remoteHost' = do (x, _):_ -> Just x _ -> Nothing let (serverName', _) = B.break (== ':') host - return $ Request + ilen <- newIORef len + return (ilen, Request { requestMethod = method , httpVersion = httpversion , pathInfo = B.pack rpath @@ -147,10 +151,10 @@ parseRequest' port lines' handle remoteHost' = do , serverPort = port , requestHeaders = heads , isSecure = False - , requestBody = requestBodyHandle handle len + , requestBody = requestBodyHandle handle ilen len , errorHandler = System.IO.hPutStr System.IO.stderr , remoteHost = B.pack remoteHost' - } + }) parseFirst :: ByteString -> IO (ByteString, ByteString, ByteString, HttpVersion) @@ -218,21 +222,17 @@ parseHeaderNoAttr s = else rest in (k, rest') -requestBodyHandle :: Handle -> Int -> E.Enumerator B.ByteString IO a -requestBodyHandle h = - requestBodyFunc go +requestBodyHandle :: Handle + -> IORef Int + -> Int + -> E.Enumerator B.ByteString IO a +requestBodyHandle handle ilen initLen = + go initLen where - go i = Just `fmap` B.hGet h (min i defaultChunkSize) - -requestBodyFunc :: (Int -> IO (Maybe B.ByteString)) - -> Int - -> E.Enumerator B.ByteString IO a -requestBodyFunc _ 0 step = E.returnI step -requestBodyFunc h len (E.Continue k) = do - mbs <- liftIO $ h len - case mbs of - Nothing -> E.continue k - Just bs -> do - let newLen = len - B.length bs - k (E.Chunks [bs]) >>== requestBodyFunc h newLen -requestBodyFunc _ _ step = E.returnI step + go 0 step = E.returnI step + go len (E.Continue k) = do + bs <- liftIO $ B.hGet handle (min len defaultChunkSize) -- FIXME play with defaultChunkSize + let newLen = len - B.length bs + liftIO $ writeIORef ilen newLen + k (E.Chunks [bs]) >>== go newLen + go _ step = E.returnI step diff --git a/undrained.hs b/undrained.hs new file mode 100644 index 000000000..d9c2b32b1 --- /dev/null +++ b/undrained.hs @@ -0,0 +1,9 @@ +{-# LANGUAGE OverloadedStrings #-} +import Network.Wai +import Network.Wai.Handler.Warp +import Blaze.ByteString.Builder (fromByteString) + +main = run 3000 $ const $ return $ responseBuilder + status200 + [("Content-Type", "text/html")] + $ fromByteString "
" From 718e7b72160fa066de91060b1b1e41983f5ba683 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 24 Dec 2010 14:16:29 +0200 Subject: [PATCH 06/90] Fixed HTTP 1.0 non-chunked response --- Network/Wai/Handler/Warp.hs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index c330f41d3..b225bf4fd 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -197,11 +197,15 @@ isChunked = (==) http11 sendResponse :: HttpVersion -> Handle -> Response -> IO () sendResponse hv handle res = do responseEnumerator res $ \s hs -> - E.joinI $ chunk - $$ E.enumList 1 [headers hv s hs] + chunk' + $ E.enumList 1 [headers hv s hs] $$ E.joinI $ builderToByteString $$ iterHandle handle where + chunk' i = + if isChunked hv + then E.joinI $ chunk $$ i + else i chunk :: E.Enumeratee Builder Builder IO () chunk = E.checkDone $ E.continue . step step k E.EOF = k (E.Chunks [chunkedTransferTerminator]) >>== return From 5aec2a6947889f9cdbf8bea5c9b337b1f3311a62 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Sun, 26 Dec 2010 17:35:30 +0200 Subject: [PATCH 07/90] Added warp executable --- warp.cabal | 6 +++++ warp.hs | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 warp.hs diff --git a/warp.cabal b/warp.cabal index ab9560250..244a183df 100644 --- a/warp.cabal +++ b/warp.cabal @@ -23,6 +23,12 @@ Library Exposed-modules: Network.Wai.Handler.Warp ghc-options: -Wall +Executable warp + Main-is: warp.hs + ghc-options: -Wall -O2 + Build-Depends: text >= 0.8 && < 0.12 + , directory >= 1.0 && < 1.2 + source-repository head type: git location: git://github.com/snoyberg/warp.git diff --git a/warp.hs b/warp.hs new file mode 100644 index 000000000..de400221e --- /dev/null +++ b/warp.hs @@ -0,0 +1,69 @@ +{-# LANGUAGE OverloadedStrings #-} +import Data.ByteString.Lazy.Char8 () +import Data.ByteString (ByteString) +import Network.Wai +import Network.Wai.Handler.Warp (run) +import System.Directory (doesFileExist) +import System.Environment (getArgs) +import Data.Text (unpack) +import Data.Text.Encoding (decodeUtf8With) +import Data.Text.Encoding.Error (lenientDecode) + +main :: IO () +main = do + a <- getArgs + let prefix = + case a of + [] -> id + [x] -> (filter (not . null) (split x) ++) + _ -> error "Usage: warp [root dir]" + run 3000 $ app prefix + +app :: ([String] -> [String]) -> Application +app prefix Request { requestMethod = m, pathInfo = p } + | m == "GET" = + case checkUnsafe $ split $ unpack $ decodeUtf8With lenientDecode p of + Nothing -> return $ responseLBS status403 [("Content-Type", "text/plain")] "Permission Denied" + Just pieces -> do + let file = join $ prefix pieces + let ext = reverse $ takeWhile (/= '.') $ reverse file + let ct = getCT ext + e <- doesFileExist file + if e + then return $ ResponseFile status200 [("Content-Type", ct)] file + else return $ responseLBS status404 [("Content-Type", "text/plain")] "File not found" + | otherwise = + return $ responseLBS status405 [("Content-Type", "text/plain")] "Bad method" + +split :: String -> [String] +split "" = [] +split s = + let (x, y) = break (== '/') s + in x : split (drop 1 y) + +checkUnsafe :: [String] -> Maybe [String] +checkUnsafe [] = Nothing +checkUnsafe [""] = Just ["index.html"] +checkUnsafe [x] = Just [x] +checkUnsafe ("..":_) = Nothing +checkUnsafe (".":rest) = checkUnsafe rest +checkUnsafe ("":rest) = checkUnsafe rest +checkUnsafe (x:rest) = ((:) x) `fmap` checkUnsafe rest + +join :: [String] -> String +join [] = [] +join [x] = x +join (x:xs) = x ++ '/' : join xs + +getCT :: String -> ByteString +getCT "jpg" = "image/jpeg" +getCT "jpeg" = "image/jpeg" +getCT "js" = "text/javascript" +getCT "css" = "text/css" +getCT "html" = "text/html" +getCT "png" = "image/png" +getCT "gif" = "image/gif" +getCT "txt" = "text/plain" +getCT "flv" = "video/x-flv" +getCT "ogv" = "video/ogg" +getCT _ = "application/octet-stream" From babcbfeb1b4396cc2471e8a13022ff764b7acd9a Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Sun, 26 Dec 2010 19:48:58 +0200 Subject: [PATCH 08/90] Connection-Length support --- Network/Wai/Handler/Warp.hs | 57 ++++++++++++++++++++++--------------- pong.hs | 4 ++- 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index b225bf4fd..96f23c694 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -80,9 +80,9 @@ serveConnection port app conn remoteHost' = do res <- app env remaining <- readIORef ilen _ <- B.hGet conn remaining -- FIXME just skip, don't read - sendResponse (httpVersion env) conn res + keepAlive <- sendResponse (httpVersion env) conn res hFlush conn - when (isChunked $ httpVersion env) serveConnection' + when keepAlive serveConnection' catchEOFError :: IOError -> IO () catchEOFError e | isEOFError e = case ioeGetHandle e of @@ -169,8 +169,8 @@ parseFirst s = do let (rpath, qstring) = B.break (== '?') query return (method, rpath, qstring, hsecond) -headers :: HttpVersion -> Status -> ResponseHeaders -> Builder -headers httpversion status responseHeaders = mconcat +headers :: HttpVersion -> Status -> ResponseHeaders -> Bool -> Builder +headers httpversion status responseHeaders isChunked' = mconcat [ fromByteString "HTTP/" , fromByteString httpversion , fromChar ' ' @@ -179,7 +179,7 @@ headers httpversion status responseHeaders = mconcat , fromByteString $ statusMessage status , fromByteString "\r\n" , mconcat $ map go responseHeaders - , if isChunked httpversion + , if isChunked' then fromByteString "Transfer-Encoding: chunked\r\n\r\n" else fromByteString "\r\n" ] @@ -194,26 +194,37 @@ headers httpversion status responseHeaders = mconcat isChunked :: HttpVersion -> Bool isChunked = (==) http11 -sendResponse :: HttpVersion -> Handle -> Response -> IO () -sendResponse hv handle res = do - responseEnumerator res $ \s hs -> - chunk' - $ E.enumList 1 [headers hv s hs] - $$ E.joinI $ builderToByteString - $$ iterHandle handle +sendResponse :: HttpVersion -> Handle -> Response -> IO Bool +sendResponse hv handle res = + responseEnumerator res go where - chunk' i = - if isChunked hv - then E.joinI $ chunk $$ i - else i - chunk :: E.Enumeratee Builder Builder IO () - chunk = E.checkDone $ E.continue . step - step k E.EOF = k (E.Chunks [chunkedTransferTerminator]) >>== return - step k (E.Chunks []) = E.continue $ step k - step k (E.Chunks builders) = - k (E.Chunks [chunked]) >>== chunk + go s hs = + chunk' + $ E.enumList 1 [headers hv s hs isChunked'] + $$ E.joinI $ builderToByteString + $$ (iterHandle handle >> return isKeepAlive) where - chunked = chunkedTransferEncoding $ mconcat builders + hasLength = lookup "content-length" hs /= Nothing + isChunked' = isChunked hv && not hasLength + isKeepAlive = isChunked' || hasLength + chunk' i = + if isChunked' + then E.joinI $ chunk $$ i + else + (if isKeepAlive + then E.joinI $ after $$ i + else i) + chunk :: E.Enumeratee Builder Builder IO Bool + chunk = E.checkDone $ E.continue . step + step k E.EOF = k (E.Chunks [chunkedTransferTerminator]) >>== return + step k (E.Chunks []) = E.continue $ step k + step k (E.Chunks builders) = + k (E.Chunks [chunked]) >>== chunk + where + chunked = chunkedTransferEncoding $ mconcat builders + after = E.checkDone $ E.continue . after' + after' k E.EOF = k (E.Chunks [fromByteString "\r\n\r\n"]) >>== return + after' k chunks = k chunks >>== after parseHeaderNoAttr :: ByteString -> (ByteString, ByteString) parseHeaderNoAttr s = diff --git a/pong.hs b/pong.hs index 3bcc3bdf1..2e66f0337 100644 --- a/pong.hs +++ b/pong.hs @@ -5,5 +5,7 @@ import Blaze.ByteString.Builder (fromByteString) main = run 3000 $ const $ return $ responseBuilder status200 - [("Content-Type", "text/plain")] + [ ("Content-Type", "text/plain") + , ("Content-Length", "4") + ] $ fromByteString "PONG" From 65d31360fdaa4d1234fd2d99f18d26b2a7ab2db4 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Sun, 26 Dec 2010 20:05:36 +0200 Subject: [PATCH 09/90] sendfile support --- Network/Wai/Handler/Warp.hs | 15 ++++++++++++--- warp.cabal | 1 + 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 96f23c694..0e46c74db 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -25,6 +25,8 @@ import qualified System.IO import Data.ByteString (ByteString) import qualified Data.ByteString.Char8 as B +import qualified Data.ByteString as S +import qualified Data.ByteString.Lazy as L import Network ( listenOn, accept, sClose, PortID(PortNumber), Socket , withSocketsDo) @@ -45,9 +47,10 @@ import Data.Enumerator.IO (iterHandle) import Blaze.ByteString.Builder.Enumerator (builderToByteString) import Blaze.ByteString.Builder.HTTP (chunkedTransferEncoding, chunkedTransferTerminator) -import Blaze.ByteString.Builder (fromByteString, Builder) +import Blaze.ByteString.Builder (fromByteString, Builder, toLazyByteString) import Blaze.ByteString.Builder.Char8 (fromChar, fromString) import Data.Monoid (mconcat) +import Network.Socket.SendFile (unsafeSendFile) import Control.Monad.IO.Class (liftIO) import Data.ByteString.Lazy.Internal (defaultChunkSize) @@ -195,8 +198,14 @@ isChunked :: HttpVersion -> Bool isChunked = (==) http11 sendResponse :: HttpVersion -> Handle -> Response -> IO Bool -sendResponse hv handle res = - responseEnumerator res go +sendResponse hv handle (ResponseFile s hs fp) = do + mapM_ (S.hPut handle) $ L.toChunks $ toLazyByteString $ headers hv s hs False + unsafeSendFile handle fp + if lookup "content-length" hs == Nothing + then return False + else S.hPut handle "\r\n\r\n" >> return True +sendResponse hv handle (ResponseEnumerator res) = + res go where go s hs = chunk' diff --git a/warp.cabal b/warp.cabal index 244a183df..6ae7ed096 100644 --- a/warp.cabal +++ b/warp.cabal @@ -20,6 +20,7 @@ Library , transformers >= 0.2 && < 0.3 , enumerator >= 0.4 && < 0.5 , blaze-builder >= 0.2.1.3 && < 0.3 + , sendfile >= 0.7.2 && < 0.8 Exposed-modules: Network.Wai.Handler.Warp ghc-options: -Wall From b1da2114ddf8a06a2969c2f592cee24298a99c0a Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Sun, 26 Dec 2010 20:24:21 +0200 Subject: [PATCH 10/90] Fix content-length keepalives --- Network/Wai/Handler/Warp.hs | 12 ++---------- warp.cabal | 2 +- warp.hs | 1 + 3 files changed, 4 insertions(+), 11 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 0e46c74db..099575bb5 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -201,9 +201,7 @@ sendResponse :: HttpVersion -> Handle -> Response -> IO Bool sendResponse hv handle (ResponseFile s hs fp) = do mapM_ (S.hPut handle) $ L.toChunks $ toLazyByteString $ headers hv s hs False unsafeSendFile handle fp - if lookup "content-length" hs == Nothing - then return False - else S.hPut handle "\r\n\r\n" >> return True + return $ lookup "content-length" hs /= Nothing sendResponse hv handle (ResponseEnumerator res) = res go where @@ -219,10 +217,7 @@ sendResponse hv handle (ResponseEnumerator res) = chunk' i = if isChunked' then E.joinI $ chunk $$ i - else - (if isKeepAlive - then E.joinI $ after $$ i - else i) + else i chunk :: E.Enumeratee Builder Builder IO Bool chunk = E.checkDone $ E.continue . step step k E.EOF = k (E.Chunks [chunkedTransferTerminator]) >>== return @@ -231,9 +226,6 @@ sendResponse hv handle (ResponseEnumerator res) = k (E.Chunks [chunked]) >>== chunk where chunked = chunkedTransferEncoding $ mconcat builders - after = E.checkDone $ E.continue . after' - after' k E.EOF = k (E.Chunks [fromByteString "\r\n\r\n"]) >>== return - after' k chunks = k chunks >>== after parseHeaderNoAttr :: ByteString -> (ByteString, ByteString) parseHeaderNoAttr s = diff --git a/warp.cabal b/warp.cabal index 6ae7ed096..3537ffdea 100644 --- a/warp.cabal +++ b/warp.cabal @@ -26,7 +26,7 @@ Library Executable warp Main-is: warp.hs - ghc-options: -Wall -O2 + ghc-options: -Wall -O2 -threaded Build-Depends: text >= 0.8 && < 0.12 , directory >= 1.0 && < 1.2 diff --git a/warp.hs b/warp.hs index de400221e..6c8544825 100644 --- a/warp.hs +++ b/warp.hs @@ -29,6 +29,7 @@ app prefix Request { requestMethod = m, pathInfo = p } let ext = reverse $ takeWhile (/= '.') $ reverse file let ct = getCT ext e <- doesFileExist file + -- FIXME check file size if e then return $ ResponseFile status200 [("Content-Type", ct)] file else return $ responseLBS status404 [("Content-Type", "text/plain")] "File not found" From b18e687d1595ca81af079911fbd63f70078b72aa Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 30 Dec 2010 06:32:03 +0200 Subject: [PATCH 11/90] HEAD and 204 support --- Network/Wai/Handler/Warp.hs | 24 ++++++++++++++++++------ statuses.hs | 18 ++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 statuses.hs diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 099575bb5..f4f704a7c 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -83,7 +83,7 @@ serveConnection port app conn remoteHost' = do res <- app env remaining <- readIORef ilen _ <- B.hGet conn remaining -- FIXME just skip, don't read - keepAlive <- sendResponse (httpVersion env) conn res + keepAlive <- sendResponse env (httpVersion env) conn res hFlush conn when keepAlive serveConnection' @@ -197,14 +197,26 @@ headers httpversion status responseHeaders isChunked' = mconcat isChunked :: HttpVersion -> Bool isChunked = (==) http11 -sendResponse :: HttpVersion -> Handle -> Response -> IO Bool -sendResponse hv handle (ResponseFile s hs fp) = do +hasBody :: Status -> Request -> Bool +hasBody s req = s /= (Status 204 "") && requestMethod req /= "HEAD" + +sendResponse :: Request -> HttpVersion -> Handle -> Response -> IO Bool +sendResponse req hv handle (ResponseFile s hs fp) = do mapM_ (S.hPut handle) $ L.toChunks $ toLazyByteString $ headers hv s hs False - unsafeSendFile handle fp - return $ lookup "content-length" hs /= Nothing -sendResponse hv handle (ResponseEnumerator res) = + if hasBody s req + then do + unsafeSendFile handle fp + return $ lookup "content-length" hs /= Nothing + else return True +sendResponse req hv handle (ResponseEnumerator res) = res go where + go s hs + | not (hasBody s req) = do + liftIO $ mapM_ (S.hPut handle) + $ L.toChunks $ toLazyByteString + $ headers hv s hs False + return True go s hs = chunk' $ E.enumList 1 [headers hv s hs isChunked'] diff --git a/statuses.hs b/statuses.hs new file mode 100644 index 000000000..11d91ebf6 --- /dev/null +++ b/statuses.hs @@ -0,0 +1,18 @@ +{-# LANGUAGE OverloadedStrings #-} +import Network.Wai +import Network.Wai.Handler.Warp +import Data.ByteString.Lazy.Char8 (pack) +import qualified Data.ByteString.Char8 as S + +main = run 3000 app + +app req = + return $ responseLBS (Status s' s) [("Content-Type", "text/plain")] + $ pack $ concat + [ "The status code is " + , S.unpack s + , ". Have a nice day!" + ] + where + s = S.dropWhile (== '/') $ pathInfo req + s' = read $ S.unpack s From 4e053c601ce9e84391b87978236795ffeaf4aca2 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 30 Dec 2010 11:41:29 +0200 Subject: [PATCH 12/90] Exposing drainRequestBody --- Network/Wai/Handler/Warp.hs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index f4f704a7c..7c096614f 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -18,6 +18,7 @@ module Network.Wai.Handler.Warp ( run , sendResponse , parseRequest + , drainRequestBody ) where import Network.Wai @@ -81,8 +82,7 @@ serveConnection port app conn remoteHost' = do where serveConnection' = do (ilen, env) <- parseRequest port conn remoteHost' res <- app env - remaining <- readIORef ilen - _ <- B.hGet conn remaining -- FIXME just skip, don't read + drainRequestBody conn ilen keepAlive <- sendResponse env (httpVersion env) conn res hFlush conn when keepAlive serveConnection' @@ -93,6 +93,12 @@ serveConnection port app conn remoteHost' = do Nothing -> ioError e | otherwise = ioError e +drainRequestBody :: Handle -> IORef Int -> IO () +drainRequestBody conn ilen = do + remaining <- readIORef ilen + _ <- B.hGet conn remaining -- FIXME just skip, don't read + return () + parseRequest :: Port -> Handle -> String -> IO (IORef Int, Request) parseRequest port conn remoteHost' = do headers' <- takeUntilBlank conn id From de699b00616a7e972bf8f0e0b03b5d7617c6f682 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 30 Dec 2010 15:15:43 +0200 Subject: [PATCH 13/90] Enumerator based processing --- Network/Wai/Handler/Warp.hs | 156 +++++++++++++++++++++--------------- 1 file changed, 92 insertions(+), 64 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 7c096614f..9931d69ed 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -18,20 +18,19 @@ module Network.Wai.Handler.Warp ( run , sendResponse , parseRequest - , drainRequestBody ) where import Network.Wai import qualified System.IO import Data.ByteString (ByteString) -import qualified Data.ByteString.Char8 as B import qualified Data.ByteString as S +import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy as L import Network ( listenOn, accept, sClose, PortID(PortNumber), Socket , withSocketsDo) -import Control.Exception (bracket, finally, Exception, throwIO) +import Control.Exception (bracket, finally, Exception) import System.IO (Handle, hClose, hFlush) import System.IO.Error (isEOFError, ioeGetHandle) import Control.Concurrent (forkIO) @@ -44,7 +43,7 @@ import Control.Arrow (first) import Data.Enumerator (($$), (>>==)) import qualified Data.Enumerator as E -import Data.Enumerator.IO (iterHandle) +import Data.Enumerator.IO (iterHandle, enumHandle) import Blaze.ByteString.Builder.Enumerator (builderToByteString) import Blaze.ByteString.Builder.HTTP (chunkedTransferEncoding, chunkedTransferTerminator) @@ -54,9 +53,6 @@ import Data.Monoid (mconcat) import Network.Socket.SendFile (unsafeSendFile) import Control.Monad.IO.Class (liftIO) -import Data.ByteString.Lazy.Internal (defaultChunkSize) - -import Data.IORef (IORef, writeIORef, readIORef, newIORef) run :: Port -> Application -> IO () run port = withSocketsDo . @@ -69,74 +65,96 @@ type Port = Int serveConnections :: Port -> Application -> Socket -> IO () serveConnections port app socket = do (conn, remoteHost', _) <- accept socket -- FIXME use sockets directly instead of Handlers? + -- FIXME disable buffering _ <- forkIO $ serveConnection port app conn remoteHost' serveConnections port app socket serveConnection :: Port -> Application -> Handle -> String -> IO () serveConnection port app conn remoteHost' = do - catch - (finally - serveConnection' - (hClose conn)) - catchEOFError - where serveConnection' = do - (ilen, env) <- parseRequest port conn remoteHost' - res <- app env - drainRequestBody conn ilen - keepAlive <- sendResponse env (httpVersion env) conn res - hFlush conn - when keepAlive serveConnection' + catch + (finally + (E.run_ $ fromClient $$ serveConnection') + (hClose conn)) + catchEOFError + where + fromClient = enumHandle 4096 conn + serveConnection' = do + (enumeratee, env) <- parseRequest port remoteHost' + res <- E.joinI $ enumeratee $$ app env + keepAlive <- liftIO $ sendResponse env (httpVersion env) conn res + liftIO $ hFlush conn + when keepAlive serveConnection' - catchEOFError :: IOError -> IO () - catchEOFError e | isEOFError e = case ioeGetHandle e of - Just h -> unless (h == conn) (ioError e) - Nothing -> ioError e - | otherwise = ioError e + catchEOFError :: IOError -> IO () + catchEOFError e + | isEOFError e = + case ioeGetHandle e of + Just h -> unless (h == conn) (ioError e) + Nothing -> ioError e + | otherwise = ioError e -drainRequestBody :: Handle -> IORef Int -> IO () -drainRequestBody conn ilen = do - remaining <- readIORef ilen - _ <- B.hGet conn remaining -- FIXME just skip, don't read - return () +parseRequest :: Port -> String -> E.Iteratee S.ByteString IO (E.Enumeratee ByteString ByteString IO a, Request) +parseRequest port remoteHost' = do + headers' <- takeUntilBlank 0 id + parseRequest' port headers' remoteHost' -parseRequest :: Port -> Handle -> String -> IO (IORef Int, Request) -parseRequest port conn remoteHost' = do - headers' <- takeUntilBlank conn id - parseRequest' port headers' conn remoteHost' +-- FIXME come up with good values here +maxHeaders, maxHeaderLength :: Int +maxHeaders = 30 +maxHeaderLength = 1024 -takeUntilBlank :: Handle +takeUntilBlank :: Int -> ([ByteString] -> [ByteString]) - -> IO [ByteString] -takeUntilBlank h front = do - l <- stripCR `fmap` B.hGetLine h + -> E.Iteratee S.ByteString IO [ByteString] +takeUntilBlank count _ + | count > maxHeaders = E.throwError TooManyHeaders +takeUntilBlank count front = do + l <- takeLine 0 id if B.null l then return $ front [] - else takeUntilBlank h $ front . (:) l + else takeUntilBlank (count + 1) $ front . (:) l -stripCR :: ByteString -> ByteString -stripCR bs - | B.null bs = bs - | B.last bs == '\r' = B.init bs - | otherwise = bs +takeLine :: Int + -> ([ByteString] -> [ByteString]) + -> E.Iteratee ByteString IO ByteString +takeLine len front = do + mbs <- E.head + case mbs of + Nothing -> E.throwError IncompleteHeaders + Just bs -> do + let (x, y) = S.breakByte 10 bs + x' = if S.length x > 0 && S.last x == 13 + then S.init x + else x + let len' = len + B.length x + case () of + () + | len' > maxHeaderLength -> E.throwError OverLargeHeader + | B.null y -> takeLine len' $ front . (:) x + | otherwise -> do + E.yield () $ E.Chunks [B.drop 1 y] + return $ B.concat $ front [x'] data InvalidRequest = NotEnoughLines [String] | BadFirstLine String | NonHttp + | TooManyHeaders + | IncompleteHeaders + | OverLargeHeader deriving (Show, Typeable) instance Exception InvalidRequest -- | Parse a set of header lines and body into a 'Request'. parseRequest' :: Port -> [ByteString] - -> Handle -> String - -> IO (IORef Int, Request) -parseRequest' port lines' handle remoteHost' = do + -> E.Iteratee S.ByteString IO (E.Enumeratee S.ByteString S.ByteString IO a, Request) +parseRequest' port lines' remoteHost' = do (firstLine, otherLines) <- case lines' of x:xs -> return (x, xs) - [] -> throwIO $ NotEnoughLines $ map B.unpack lines' + [] -> E.throwError $ NotEnoughLines $ map B.unpack lines' (method, rpath', gets, httpversion) <- parseFirst firstLine let rpath = '/' : case B.unpack rpath' of ('/':x) -> x @@ -150,8 +168,7 @@ parseRequest' port lines' handle remoteHost' = do (x, _):_ -> Just x _ -> Nothing let (serverName', _) = B.break (== ':') host - ilen <- newIORef len - return (ilen, Request + return (requestBodyHandle len, Request { requestMethod = method , httpVersion = httpversion , pathInfo = B.pack rpath @@ -160,21 +177,20 @@ parseRequest' port lines' handle remoteHost' = do , serverPort = port , requestHeaders = heads , isSecure = False - , requestBody = requestBodyHandle handle ilen len , errorHandler = System.IO.hPutStr System.IO.stderr , remoteHost = B.pack remoteHost' }) parseFirst :: ByteString - -> IO (ByteString, ByteString, ByteString, HttpVersion) + -> E.Iteratee S.ByteString IO (ByteString, ByteString, ByteString, HttpVersion) parseFirst s = do let pieces = B.words s (method, query, http') <- case pieces of [x, y, z] -> return (x, y, z) - _ -> throwIO $ BadFirstLine $ B.unpack s + _ -> E.throwError $ BadFirstLine $ B.unpack s let (hfirst, hsecond) = B.splitAt 5 http' - unless (hfirst == B.pack "HTTP/") $ throwIO NonHttp + unless (hfirst == "HTTP/") $ E.throwError NonHttp let (rpath, qstring) = B.break (== '?') query return (method, rpath, qstring, hsecond) @@ -256,17 +272,29 @@ parseHeaderNoAttr s = else rest in (k, rest') -requestBodyHandle :: Handle - -> IORef Int - -> Int - -> E.Enumerator B.ByteString IO a -requestBodyHandle handle ilen initLen = +requestBodyHandle :: Int + -> E.Enumeratee ByteString ByteString IO a +requestBodyHandle initLen = go initLen where - go 0 step = E.returnI step + go 0 step = return step go len (E.Continue k) = do - bs <- liftIO $ B.hGet handle (min len defaultChunkSize) -- FIXME play with defaultChunkSize - let newLen = len - B.length bs - liftIO $ writeIORef ilen newLen - k (E.Chunks [bs]) >>== go newLen - go _ step = E.returnI step + x <- E.head + case x of + Nothing -> return $ E.Continue k + Just bs -> do + let newlen = max 0 $ len - B.length bs + k (E.Chunks [bs]) >>== go newlen + go len step = do + drain len + return step + drain 0 = return () + drain len = do + mbs <- E.head + case mbs of + Nothing -> return () + Just bs -> do + let newlen = len - B.length bs + if newlen <= 0 + then return () + else drain newlen From f94a767a0b697ace703a09d116990ed3bd204c35 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 30 Dec 2010 15:30:05 +0200 Subject: [PATCH 14/90] Switch to Sockets from Handles --- Network/Wai/Handler/Warp.hs | 56 +++++++++++++++++++++---------------- warp.cabal | 2 +- 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 9931d69ed..7a4f339f9 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -28,8 +28,12 @@ import qualified Data.ByteString as S import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy as L import Network - ( listenOn, accept, sClose, PortID(PortNumber), Socket + ( listenOn, sClose, PortID(PortNumber), Socket , withSocketsDo) +import Network.Socket + ( accept + ) +import qualified Network.Socket.ByteString as Sock import Control.Exception (bracket, finally, Exception) import System.IO (Handle, hClose, hFlush) import System.IO.Error (isEOFError, ioeGetHandle) @@ -50,7 +54,7 @@ import Blaze.ByteString.Builder.HTTP import Blaze.ByteString.Builder (fromByteString, Builder, toLazyByteString) import Blaze.ByteString.Builder.Char8 (fromChar, fromString) import Data.Monoid (mconcat) -import Network.Socket.SendFile (unsafeSendFile) +import Network.Socket.SendFile (sendFile) import Control.Monad.IO.Class (liftIO) @@ -64,35 +68,26 @@ type Port = Int serveConnections :: Port -> Application -> Socket -> IO () serveConnections port app socket = do - (conn, remoteHost', _) <- accept socket -- FIXME use sockets directly instead of Handlers? - -- FIXME disable buffering + (conn, _) <- accept socket + let remoteHost' = undefined _ <- forkIO $ serveConnection port app conn remoteHost' serveConnections port app socket -serveConnection :: Port -> Application -> Handle -> String -> IO () +serveConnection :: Port -> Application -> Socket -> String -> IO () serveConnection port app conn remoteHost' = do catch (finally (E.run_ $ fromClient $$ serveConnection') - (hClose conn)) - catchEOFError + (sClose conn)) + undefined where - fromClient = enumHandle 4096 conn + fromClient = enumSocket 4096 conn serveConnection' = do (enumeratee, env) <- parseRequest port remoteHost' res <- E.joinI $ enumeratee $$ app env keepAlive <- liftIO $ sendResponse env (httpVersion env) conn res - liftIO $ hFlush conn when keepAlive serveConnection' - catchEOFError :: IOError -> IO () - catchEOFError e - | isEOFError e = - case ioeGetHandle e of - Just h -> unless (h == conn) (ioError e) - Nothing -> ioError e - | otherwise = ioError e - parseRequest :: Port -> String -> E.Iteratee S.ByteString IO (E.Enumeratee ByteString ByteString IO a, Request) parseRequest port remoteHost' = do headers' <- takeUntilBlank 0 id @@ -222,20 +217,20 @@ isChunked = (==) http11 hasBody :: Status -> Request -> Bool hasBody s req = s /= (Status 204 "") && requestMethod req /= "HEAD" -sendResponse :: Request -> HttpVersion -> Handle -> Response -> IO Bool -sendResponse req hv handle (ResponseFile s hs fp) = do - mapM_ (S.hPut handle) $ L.toChunks $ toLazyByteString $ headers hv s hs False +sendResponse :: Request -> HttpVersion -> Socket -> Response -> IO Bool +sendResponse req hv socket (ResponseFile s hs fp) = do + Sock.sendMany socket $ L.toChunks $ toLazyByteString $ headers hv s hs False if hasBody s req then do - unsafeSendFile handle fp + sendFile socket fp return $ lookup "content-length" hs /= Nothing else return True -sendResponse req hv handle (ResponseEnumerator res) = +sendResponse req hv socket (ResponseEnumerator res) = res go where go s hs | not (hasBody s req) = do - liftIO $ mapM_ (S.hPut handle) + liftIO $ Sock.sendMany socket $ L.toChunks $ toLazyByteString $ headers hv s hs False return True @@ -243,7 +238,7 @@ sendResponse req hv handle (ResponseEnumerator res) = chunk' $ E.enumList 1 [headers hv s hs isChunked'] $$ E.joinI $ builderToByteString - $$ (iterHandle handle >> return isKeepAlive) + $$ (iterSocket socket >> return isKeepAlive) where hasLength = lookup "content-length" hs /= Nothing isChunked' = isChunked hv && not hasLength @@ -298,3 +293,16 @@ requestBodyHandle initLen = if newlen <= 0 then return () else drain newlen + +iterSocket socket = + E.continue go + where + go E.EOF = E.yield () E.EOF + go (E.Chunks cs) = liftIO (Sock.sendMany socket cs) >> E.continue go + +enumSocket len socket (E.Continue k) = do + bs <- liftIO $ Sock.recv socket len + if S.length bs == 0 + then E.continue k + else k (E.Chunks [bs]) >>== enumSocket len socket +enumSocket _ _ step = E.returnI step diff --git a/warp.cabal b/warp.cabal index 3537ffdea..b561d6c45 100644 --- a/warp.cabal +++ b/warp.cabal @@ -15,7 +15,7 @@ Library Build-Depends: base >= 3 && < 5 , bytestring >= 0.9 && < 0.10 , wai >= 0.3.0 && < 0.4 - , network >= 2.2.1.5 && < 2.4 + , network >= 2.3 && < 2.4 , blaze-builder-enumerator >= 0.2 && < 0.3 , transformers >= 0.2 && < 0.3 , enumerator >= 0.4 && < 0.5 From 78475b00647f9fac499a3f188f2a944a97c022c5 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 30 Dec 2010 21:40:35 +0200 Subject: [PATCH 15/90] warp executable compiles --- warp.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/warp.hs b/warp.hs index 6c8544825..117fd8514 100644 --- a/warp.hs +++ b/warp.hs @@ -8,6 +8,7 @@ import System.Environment (getArgs) import Data.Text (unpack) import Data.Text.Encoding (decodeUtf8With) import Data.Text.Encoding.Error (lenientDecode) +import Control.Monad.IO.Class (liftIO) main :: IO () main = do @@ -28,7 +29,7 @@ app prefix Request { requestMethod = m, pathInfo = p } let file = join $ prefix pieces let ext = reverse $ takeWhile (/= '.') $ reverse file let ct = getCT ext - e <- doesFileExist file + e <- liftIO $ doesFileExist file -- FIXME check file size if e then return $ ResponseFile status200 [("Content-Type", ct)] file From 6a02c146e8b97f1c436c70c4b1518a7be28fc733 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 30 Dec 2010 23:11:22 +0200 Subject: [PATCH 16/90] Ignoring all exceptions and just closing connection --- Network/Wai/Handler/Warp.hs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 7a4f339f9..41e7e8fc5 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -20,6 +20,7 @@ module Network.Wai.Handler.Warp , parseRequest ) where +import Prelude hiding (catch) import Network.Wai import qualified System.IO @@ -34,7 +35,7 @@ import Network.Socket ( accept ) import qualified Network.Socket.ByteString as Sock -import Control.Exception (bracket, finally, Exception) +import Control.Exception (bracket, finally, Exception, SomeException, catch) import System.IO (Handle, hClose, hFlush) import System.IO.Error (isEOFError, ioeGetHandle) import Control.Concurrent (forkIO) @@ -79,8 +80,10 @@ serveConnection port app conn remoteHost' = do (finally (E.run_ $ fromClient $$ serveConnection') (sClose conn)) - undefined + ignoreAll where + ignoreAll :: SomeException -> IO () + ignoreAll _ = return () fromClient = enumSocket 4096 conn serveConnection' = do (enumeratee, env) <- parseRequest port remoteHost' From 551347070ed09fae09447fac935caf168d90521d Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 30 Dec 2010 23:28:12 +0200 Subject: [PATCH 17/90] Correct remoteHost --- Network/Wai/Handler/Warp.hs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 41e7e8fc5..37c157bb2 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -69,10 +69,15 @@ type Port = Int serveConnections :: Port -> Application -> Socket -> IO () serveConnections port app socket = do - (conn, _) <- accept socket - let remoteHost' = undefined + (conn, sa) <- accept socket + let remoteHost' = stripPort $ show sa -- FIXME _ <- forkIO $ serveConnection port app conn remoteHost' serveConnections port app socket + where + stripPort s = + case break (== ':') $ reverse s of + (_, ':' : rest) -> reverse rest + _ -> s serveConnection :: Port -> Application -> Socket -> String -> IO () serveConnection port app conn remoteHost' = do @@ -84,7 +89,7 @@ serveConnection port app conn remoteHost' = do where ignoreAll :: SomeException -> IO () ignoreAll _ = return () - fromClient = enumSocket 4096 conn + fromClient = enumSocket bytesPerRead conn serveConnection' = do (enumeratee, env) <- parseRequest port remoteHost' res <- E.joinI $ enumeratee $$ app env @@ -97,9 +102,10 @@ parseRequest port remoteHost' = do parseRequest' port headers' remoteHost' -- FIXME come up with good values here -maxHeaders, maxHeaderLength :: Int +maxHeaders, maxHeaderLength, bytesPerRead :: Int maxHeaders = 30 maxHeaderLength = 1024 +bytesPerRead = 4096 takeUntilBlank :: Int -> ([ByteString] -> [ByteString]) From 3745201aed70828a5cfeacbc55d09954c8a99e77 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 30 Dec 2010 23:49:21 +0200 Subject: [PATCH 18/90] Optional timeout protection --- Network/Wai/Handler/Warp.hs | 21 +++++++++++++++++---- warp.cabal | 13 ++++++++++++- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 37c157bb2..c897333d6 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -1,6 +1,7 @@ {-# LANGUAGE DeriveDataTypeable #-} {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE CPP #-} --------------------------------------------------------- -- | -- Module : Network.Wai.Handler.Warp @@ -58,6 +59,7 @@ import Data.Monoid (mconcat) import Network.Socket.SendFile (sendFile) import Control.Monad.IO.Class (liftIO) +import System.Timeout (timeout) run :: Port -> Application -> IO () run port = withSocketsDo . @@ -102,10 +104,11 @@ parseRequest port remoteHost' = do parseRequest' port headers' remoteHost' -- FIXME come up with good values here -maxHeaders, maxHeaderLength, bytesPerRead :: Int +maxHeaders, maxHeaderLength, bytesPerRead, readTimeout :: Int maxHeaders = 30 maxHeaderLength = 1024 bytesPerRead = 4096 +readTimeout = 3000000 takeUntilBlank :: Int -> ([ByteString] -> [ByteString]) @@ -146,6 +149,7 @@ data InvalidRequest = | TooManyHeaders | IncompleteHeaders | OverLargeHeader + | SocketTimeout deriving (Show, Typeable) instance Exception InvalidRequest @@ -310,8 +314,17 @@ iterSocket socket = go (E.Chunks cs) = liftIO (Sock.sendMany socket cs) >> E.continue go enumSocket len socket (E.Continue k) = do +#if NO_TIMEOUT_PROTECTION bs <- liftIO $ Sock.recv socket len - if S.length bs == 0 - then E.continue k - else k (E.Chunks [bs]) >>== enumSocket len socket + go bs +#else + mbs <- liftIO $ timeout readTimeout $ Sock.recv socket len + case mbs of + Nothing -> E.throwError SocketTimeout + Just bs -> go bs +#endif + where + go bs + | S.length bs == 0 = E.continue k + | otherwise = k (E.Chunks [bs]) >>== enumSocket len socket enumSocket _ _ step = E.returnI step diff --git a/warp.cabal b/warp.cabal index b561d6c45..1d07f6653 100644 --- a/warp.cabal +++ b/warp.cabal @@ -11,18 +11,29 @@ Build-Type: Simple Cabal-Version: >=1.6 Stability: Stable +flag timeout-protection + Description: Use timeouts (very performance-costly) to protect against DOS attacks. + Default: True +flag network-bytestring + Library Build-Depends: base >= 3 && < 5 , bytestring >= 0.9 && < 0.10 , wai >= 0.3.0 && < 0.4 - , network >= 2.3 && < 2.4 , blaze-builder-enumerator >= 0.2 && < 0.3 , transformers >= 0.2 && < 0.3 , enumerator >= 0.4 && < 0.5 , blaze-builder >= 0.2.1.3 && < 0.3 , sendfile >= 0.7.2 && < 0.8 + if flag(network-bytestring) + build-depends: network >= 2.2.1 && < 2.2.3 + , network-bytestring >= 0.1.3 && < 0.1.4 + else + build-depends: network >= 2.3 && < 2.4 Exposed-modules: Network.Wai.Handler.Warp ghc-options: -Wall + if !flag(timeout-protection) + Cpp-options: -DNO_TIMEOUT_PROTECTION Executable warp Main-is: warp.hs From bbd67c1fccac15e5caf5ecc87c6a900782700ccf Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Sat, 1 Jan 2011 20:15:25 +0200 Subject: [PATCH 19/90] Proper requestBody enumeratee --- Network/Wai/Handler/Warp.hs | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index c897333d6..39972f631 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -291,8 +291,8 @@ requestBodyHandle initLen = case x of Nothing -> return $ E.Continue k Just bs -> do - let newlen = max 0 $ len - B.length bs - k (E.Chunks [bs]) >>== go newlen + (bs', newlen) <- yieldExtra len bs + k (E.Chunks [bs']) >>== go newlen go len step = do drain len return step @@ -302,10 +302,15 @@ requestBodyHandle initLen = case mbs of Nothing -> return () Just bs -> do - let newlen = len - B.length bs - if newlen <= 0 - then return () - else drain newlen + (bs', newlen) <- yieldExtra len bs + drain newlen + yieldExtra len bs + | B.length bs == len = return (bs, 0) + | B.length bs < len = return (bs, len - B.length bs) + | otherwise = do + let (x, y) = B.splitAt len bs + E.yield () $ E.Chunks [y] + return (x, 0) iterSocket socket = E.continue go From 0567731e996be12eadfd4d94896311317bebdb4e Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 6 Jan 2011 00:09:00 +0200 Subject: [PATCH 20/90] remoteHost is SockAddr; including getFileSize --- Network/Wai/Handler/Warp.hs | 18 ++++++------------ Network/Wai/Handler/Warp/Util.hs | 26 ++++++++++++++++++++++++++ warp.cabal | 5 +++++ 3 files changed, 37 insertions(+), 12 deletions(-) create mode 100644 Network/Wai/Handler/Warp/Util.hs diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 39972f631..0cde7270e 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -33,7 +33,7 @@ import Network ( listenOn, sClose, PortID(PortNumber), Socket , withSocketsDo) import Network.Socket - ( accept + ( accept, SockAddr ) import qualified Network.Socket.ByteString as Sock import Control.Exception (bracket, finally, Exception, SomeException, catch) @@ -72,16 +72,10 @@ type Port = Int serveConnections :: Port -> Application -> Socket -> IO () serveConnections port app socket = do (conn, sa) <- accept socket - let remoteHost' = stripPort $ show sa -- FIXME - _ <- forkIO $ serveConnection port app conn remoteHost' + _ <- forkIO $ serveConnection port app conn sa serveConnections port app socket - where - stripPort s = - case break (== ':') $ reverse s of - (_, ':' : rest) -> reverse rest - _ -> s -serveConnection :: Port -> Application -> Socket -> String -> IO () +serveConnection :: Port -> Application -> Socket -> SockAddr -> IO () serveConnection port app conn remoteHost' = do catch (finally @@ -98,7 +92,7 @@ serveConnection port app conn remoteHost' = do keepAlive <- liftIO $ sendResponse env (httpVersion env) conn res when keepAlive serveConnection' -parseRequest :: Port -> String -> E.Iteratee S.ByteString IO (E.Enumeratee ByteString ByteString IO a, Request) +parseRequest :: Port -> SockAddr -> E.Iteratee S.ByteString IO (E.Enumeratee ByteString ByteString IO a, Request) parseRequest port remoteHost' = do headers' <- takeUntilBlank 0 id parseRequest' port headers' remoteHost' @@ -156,7 +150,7 @@ instance Exception InvalidRequest -- | Parse a set of header lines and body into a 'Request'. parseRequest' :: Port -> [ByteString] - -> String + -> SockAddr -> E.Iteratee S.ByteString IO (E.Enumeratee S.ByteString S.ByteString IO a, Request) parseRequest' port lines' remoteHost' = do (firstLine, otherLines) <- @@ -186,7 +180,7 @@ parseRequest' port lines' remoteHost' = do , requestHeaders = heads , isSecure = False , errorHandler = System.IO.hPutStr System.IO.stderr - , remoteHost = B.pack remoteHost' + , remoteHost = remoteHost' }) parseFirst :: ByteString diff --git a/Network/Wai/Handler/Warp/Util.hs b/Network/Wai/Handler/Warp/Util.hs new file mode 100644 index 000000000..019301834 --- /dev/null +++ b/Network/Wai/Handler/Warp/Util.hs @@ -0,0 +1,26 @@ +{-# LANGUAGE CPP #-} +module Network.Wai.Handler.Warp.Util + ( getFileSize + ) where + + +#if defined(mingw32_HOST_OS) || defined(__MINGW32__) + +import System.Win32.File + +getFileSize :: FilePath -> IO Integer +getFileSize path = do + hnd <- createFile path + gENERIC_READ fILE_SHARE_READ Nothing oPEN_EXISTING 0 Nothing + size <- fmap bhfiSize $ getFileInformationByHandle hnd + closeHandle hnd + return $ fromIntegral size + +#else + +import System.Posix.Files + +getFileSize :: FilePath -> IO Integer +getFileSize = fmap (fromIntegral . fileSize) . getFileStatus + +#endif diff --git a/warp.cabal b/warp.cabal index 1d07f6653..22c0e3c51 100644 --- a/warp.cabal +++ b/warp.cabal @@ -30,7 +30,12 @@ Library , network-bytestring >= 0.1.3 && < 0.1.4 else build-depends: network >= 2.3 && < 2.4 + if os(windows) + Build-Depends: Win32 >= 2.2 && < 2.3 + else + Build-Depends: unix >= 2.4 && < 2.5 Exposed-modules: Network.Wai.Handler.Warp + Network.Wai.Handler.Warp.Util ghc-options: -Wall if !flag(timeout-protection) Cpp-options: -DNO_TIMEOUT_PROTECTION From 41f2b8213e95537a6fb950e74c7d727cf2f3899d Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 6 Jan 2011 00:21:20 +0200 Subject: [PATCH 21/90] Added mimeTypes to Util --- Network/Wai/Handler/Warp/Util.hs | 13 + UtilHelper.hs | 16 + mime.types | 1351 ++++++++++++++++++++++++++++++ warp.cabal | 4 + 4 files changed, 1384 insertions(+) create mode 100644 UtilHelper.hs create mode 100644 mime.types diff --git a/Network/Wai/Handler/Warp/Util.hs b/Network/Wai/Handler/Warp/Util.hs index 019301834..6c15887c3 100644 --- a/Network/Wai/Handler/Warp/Util.hs +++ b/Network/Wai/Handler/Warp/Util.hs @@ -1,8 +1,15 @@ {-# LANGUAGE CPP #-} +{-# LANGUAGE TemplateHaskell #-} module Network.Wai.Handler.Warp.Util ( getFileSize + , mimeTypes ) where +import UtilHelper (mimeTypes') +import qualified Data.Map as Map +import Data.ByteString (ByteString) +import Language.Haskell.TH.Syntax (qRunIO, lift) +import qualified Data.ByteString.Char8 as S8 #if defined(mingw32_HOST_OS) || defined(__MINGW32__) @@ -24,3 +31,9 @@ getFileSize :: FilePath -> IO Integer getFileSize = fmap (fromIntegral . fileSize) . getFileStatus #endif + +mimeTypes :: Map.Map ByteString ByteString +mimeTypes = + Map.fromAscList $ map go $(qRunIO mimeTypes' >>= lift) + where + go (x, y) = (S8.pack x, S8.pack y) diff --git a/UtilHelper.hs b/UtilHelper.hs new file mode 100644 index 000000000..a8b8421d4 --- /dev/null +++ b/UtilHelper.hs @@ -0,0 +1,16 @@ +module UtilHelper where + +import Data.Maybe (mapMaybe) +import Data.List (sortBy) +import Data.Ord (comparing) + +mimeTypes' :: IO [(String, String)] +mimeTypes' = do + s <- readFile "mime.types" + let go ('#':_) = Nothing + go s = + case words s of + [a, b] -> Just (b, a) + _ -> Nothing + let pairs = mapMaybe go $ lines s + return $ sortBy (comparing fst) pairs diff --git a/mime.types b/mime.types new file mode 100644 index 000000000..1ae009729 --- /dev/null +++ b/mime.types @@ -0,0 +1,1351 @@ +# This file maps Internet media types to unique file extension(s). +# Although created for httpd, this file is used by many software systems +# and has been placed in the public domain for unlimited redisribution. +# +# The table below contains both registered and (common) unregistered types. +# A type that has no unique extension can be ignored -- they are listed +# here to guide configurations toward known types and to make it easier to +# identify "new" types. File extensions are also commonly used to indicate +# content languages and encodings, so choose them carefully. +# +# Internet media types should be registered as described in RFC 4288. +# The registry is at . +# +# MIME type Extensions +# application/3gpp-ims+xml +# application/activemessage +application/andrew-inset ez +# application/applefile +application/applixware aw +application/atom+xml atom +application/atomcat+xml atomcat +# application/atomicmail +application/atomsvc+xml atomsvc +# application/auth-policy+xml +# application/batch-smtp +# application/beep+xml +# application/cals-1840 +application/ccxml+xml ccxml +# application/cea-2018+xml +# application/cellml+xml +# application/cnrp+xml +# application/commonground +# application/conference-info+xml +# application/cpl+xml +# application/csta+xml +# application/cstadata+xml +application/cu-seeme cu +# application/cybercash +application/davmount+xml davmount +# application/dca-rft +# application/dec-dx +# application/dialog-info+xml +# application/dicom +# application/dns +application/dssc+der dssc +application/dssc+xml xdssc +# application/dvcs +application/ecmascript ecma +# application/edi-consent +# application/edi-x12 +# application/edifact +application/emma+xml emma +# application/epp+xml +application/epub+zip epub +# application/eshop +# application/example +# application/fastinfoset +# application/fastsoap +# application/fits +application/font-tdpfr pfr +# application/h224 +# application/held+xml +# application/http +application/hyperstudio stk +# application/ibe-key-request+xml +# application/ibe-pkg-reply+xml +# application/ibe-pp-data +# application/iges +# application/im-iscomposing+xml +# application/index +# application/index.cmd +# application/index.obj +# application/index.response +# application/index.vnd +# application/iotp +application/ipfix ipfix +# application/ipp +# application/isup +application/java-archive jar +application/java-serialized-object ser +application/java-vm class +application/javascript js +application/json json +# application/kpml-request+xml +# application/kpml-response+xml +application/lost+xml lostxml +application/mac-binhex40 hqx +application/mac-compactpro cpt +# application/macwriteii +application/marc mrc +application/mathematica ma nb mb +application/mathml+xml mathml +# application/mbms-associated-procedure-description+xml +# application/mbms-deregister+xml +# application/mbms-envelope+xml +# application/mbms-msk+xml +# application/mbms-msk-response+xml +# application/mbms-protection-description+xml +# application/mbms-reception-report+xml +# application/mbms-register+xml +# application/mbms-register-response+xml +# application/mbms-user-service-description+xml +application/mbox mbox +# application/media_control+xml +application/mediaservercontrol+xml mscml +# application/mikey +# application/moss-keys +# application/moss-signature +# application/mosskey-data +# application/mosskey-request +application/mp4 mp4s +# application/mpeg4-generic +# application/mpeg4-iod +# application/mpeg4-iod-xmt +application/msword doc dot +application/mxf mxf +# application/nasdata +# application/news-checkgroups +# application/news-groupinfo +# application/news-transmission +# application/nss +# application/ocsp-request +# application/ocsp-response +application/octet-stream bin dms lha lrf lzh so iso dmg dist distz pkg bpk dump elc deploy +application/oda oda +application/oebps-package+xml opf +application/ogg ogx +application/onenote onetoc onetoc2 onetmp onepkg +# application/parityfec +application/patch-ops-error+xml xer +application/pdf pdf +application/pgp-encrypted pgp +# application/pgp-keys +application/pgp-signature asc sig +application/pics-rules prf +# application/pidf+xml +# application/pidf-diff+xml +application/pkcs10 p10 +application/pkcs7-mime p7m p7c +application/pkcs7-signature p7s +application/pkix-cert cer +application/pkix-crl crl +application/pkix-pkipath pkipath +application/pkixcmp pki +application/pls+xml pls +# application/poc-settings+xml +application/postscript ai eps ps +# application/prs.alvestrand.titrax-sheet +application/prs.cww cww +# application/prs.nprend +# application/prs.plucker +# application/qsig +application/rdf+xml rdf +application/reginfo+xml rif +application/relax-ng-compact-syntax rnc +# application/remote-printing +application/resource-lists+xml rl +application/resource-lists-diff+xml rld +# application/riscos +# application/rlmi+xml +application/rls-services+xml rs +application/rsd+xml rsd +application/rss+xml rss +application/rtf rtf +# application/rtx +# application/samlassertion+xml +# application/samlmetadata+xml +application/sbml+xml sbml +application/scvp-cv-request scq +application/scvp-cv-response scs +application/scvp-vp-request spq +application/scvp-vp-response spp +application/sdp sdp +# application/set-payment +application/set-payment-initiation setpay +# application/set-registration +application/set-registration-initiation setreg +# application/sgml +# application/sgml-open-catalog +application/shf+xml shf +# application/sieve +# application/simple-filter+xml +# application/simple-message-summary +# application/simplesymbolcontainer +# application/slate +# application/smil +application/smil+xml smi smil +# application/soap+fastinfoset +# application/soap+xml +application/sparql-query rq +application/sparql-results+xml srx +# application/spirits-event+xml +application/srgs gram +application/srgs+xml grxml +application/ssml+xml ssml +# application/timestamp-query +# application/timestamp-reply +# application/tve-trigger +# application/ulpfec +# application/vemmi +# application/vividence.scriptfile +# application/vnd.3gpp.bsf+xml +application/vnd.3gpp.pic-bw-large plb +application/vnd.3gpp.pic-bw-small psb +application/vnd.3gpp.pic-bw-var pvb +# application/vnd.3gpp.sms +# application/vnd.3gpp2.bcmcsinfo+xml +# application/vnd.3gpp2.sms +application/vnd.3gpp2.tcap tcap +application/vnd.3m.post-it-notes pwn +application/vnd.accpac.simply.aso aso +application/vnd.accpac.simply.imp imp +application/vnd.acucobol acu +application/vnd.acucorp atc acutc +application/vnd.adobe.air-application-installer-package+zip air +# application/vnd.adobe.partial-upload +application/vnd.adobe.xdp+xml xdp +application/vnd.adobe.xfdf xfdf +# application/vnd.aether.imp +application/vnd.airzip.filesecure.azf azf +application/vnd.airzip.filesecure.azs azs +application/vnd.amazon.ebook azw +application/vnd.americandynamics.acc acc +application/vnd.amiga.ami ami +application/vnd.android.package-archive apk +application/vnd.anser-web-certificate-issue-initiation cii +application/vnd.anser-web-funds-transfer-initiation fti +application/vnd.antix.game-component atx +application/vnd.apple.installer+xml mpkg +application/vnd.apple.mpegurl m3u8 +# application/vnd.arastra.swi +application/vnd.aristanetworks.swi swi +application/vnd.audiograph aep +# application/vnd.autopackage +# application/vnd.avistar+xml +application/vnd.blueice.multipass mpm +# application/vnd.bluetooth.ep.oob +application/vnd.bmi bmi +application/vnd.businessobjects rep +# application/vnd.cab-jscript +# application/vnd.canon-cpdl +# application/vnd.canon-lips +# application/vnd.cendio.thinlinc.clientconf +application/vnd.chemdraw+xml cdxml +application/vnd.chipnuts.karaoke-mmd mmd +application/vnd.cinderella cdy +# application/vnd.cirpack.isdn-ext +application/vnd.claymore cla +application/vnd.cloanto.rp9 rp9 +application/vnd.clonk.c4group c4g c4d c4f c4p c4u +# application/vnd.commerce-battelle +application/vnd.commonspace csp +application/vnd.contact.cmsg cdbcmsg +application/vnd.cosmocaller cmc +application/vnd.crick.clicker clkx +application/vnd.crick.clicker.keyboard clkk +application/vnd.crick.clicker.palette clkp +application/vnd.crick.clicker.template clkt +application/vnd.crick.clicker.wordbank clkw +application/vnd.criticaltools.wbs+xml wbs +application/vnd.ctc-posml pml +# application/vnd.ctct.ws+xml +# application/vnd.cups-pdf +# application/vnd.cups-postscript +application/vnd.cups-ppd ppd +# application/vnd.cups-raster +# application/vnd.cups-raw +application/vnd.curl.car car +application/vnd.curl.pcurl pcurl +# application/vnd.cybank +application/vnd.data-vision.rdz rdz +application/vnd.denovo.fcselayout-link fe_launch +# application/vnd.dir-bi.plate-dl-nosuffix +application/vnd.dna dna +application/vnd.dolby.mlp mlp +# application/vnd.dolby.mobile.1 +# application/vnd.dolby.mobile.2 +application/vnd.dpgraph dpg +application/vnd.dreamfactory dfac +# application/vnd.dvb.esgcontainer +# application/vnd.dvb.ipdcdftnotifaccess +# application/vnd.dvb.ipdcesgaccess +# application/vnd.dvb.ipdcroaming +# application/vnd.dvb.iptv.alfec-base +# application/vnd.dvb.iptv.alfec-enhancement +# application/vnd.dvb.notif-aggregate-root+xml +# application/vnd.dvb.notif-container+xml +# application/vnd.dvb.notif-generic+xml +# application/vnd.dvb.notif-ia-msglist+xml +# application/vnd.dvb.notif-ia-registration-request+xml +# application/vnd.dvb.notif-ia-registration-response+xml +# application/vnd.dvb.notif-init+xml +# application/vnd.dxr +application/vnd.dynageo geo +# application/vnd.ecdis-update +application/vnd.ecowin.chart mag +# application/vnd.ecowin.filerequest +# application/vnd.ecowin.fileupdate +# application/vnd.ecowin.series +# application/vnd.ecowin.seriesrequest +# application/vnd.ecowin.seriesupdate +# application/vnd.emclient.accessrequest+xml +application/vnd.enliven nml +application/vnd.epson.esf esf +application/vnd.epson.msf msf +application/vnd.epson.quickanime qam +application/vnd.epson.salt slt +application/vnd.epson.ssf ssf +# application/vnd.ericsson.quickcall +application/vnd.eszigno3+xml es3 et3 +# application/vnd.etsi.aoc+xml +# application/vnd.etsi.cug+xml +# application/vnd.etsi.iptvcommand+xml +# application/vnd.etsi.iptvdiscovery+xml +# application/vnd.etsi.iptvprofile+xml +# application/vnd.etsi.iptvsad-bc+xml +# application/vnd.etsi.iptvsad-cod+xml +# application/vnd.etsi.iptvsad-npvr+xml +# application/vnd.etsi.iptvueprofile+xml +# application/vnd.etsi.mcid+xml +# application/vnd.etsi.sci+xml +# application/vnd.etsi.simservs+xml +# application/vnd.etsi.tsl+xml +# application/vnd.etsi.tsl.der +# application/vnd.eudora.data +application/vnd.ezpix-album ez2 +application/vnd.ezpix-package ez3 +# application/vnd.f-secure.mobile +application/vnd.fdf fdf +application/vnd.fdsn.mseed mseed +application/vnd.fdsn.seed seed dataless +# application/vnd.ffsns +# application/vnd.fints +application/vnd.flographit gph +application/vnd.fluxtime.clip ftc +# application/vnd.font-fontforge-sfd +application/vnd.framemaker fm frame maker book +application/vnd.frogans.fnc fnc +application/vnd.frogans.ltf ltf +application/vnd.fsc.weblaunch fsc +application/vnd.fujitsu.oasys oas +application/vnd.fujitsu.oasys2 oa2 +application/vnd.fujitsu.oasys3 oa3 +application/vnd.fujitsu.oasysgp fg5 +application/vnd.fujitsu.oasysprs bh2 +# application/vnd.fujixerox.art-ex +# application/vnd.fujixerox.art4 +# application/vnd.fujixerox.hbpl +application/vnd.fujixerox.ddd ddd +application/vnd.fujixerox.docuworks xdw +application/vnd.fujixerox.docuworks.binder xbd +# application/vnd.fut-misnet +application/vnd.fuzzysheet fzs +application/vnd.genomatix.tuxedo txd +# application/vnd.geocube+xml +application/vnd.geogebra.file ggb +application/vnd.geogebra.tool ggt +application/vnd.geometry-explorer gex gre +application/vnd.geonext gxt +application/vnd.geoplan g2w +application/vnd.geospace g3w +# application/vnd.globalplatform.card-content-mgt +# application/vnd.globalplatform.card-content-mgt-response +application/vnd.gmx gmx +application/vnd.google-earth.kml+xml kml +application/vnd.google-earth.kmz kmz +application/vnd.grafeq gqf gqs +# application/vnd.gridmp +application/vnd.groove-account gac +application/vnd.groove-help ghf +application/vnd.groove-identity-message gim +application/vnd.groove-injector grv +application/vnd.groove-tool-message gtm +application/vnd.groove-tool-template tpl +application/vnd.groove-vcard vcg +application/vnd.handheld-entertainment+xml zmm +application/vnd.hbci hbci +# application/vnd.hcl-bireports +application/vnd.hhe.lesson-player les +application/vnd.hp-hpgl hpgl +application/vnd.hp-hpid hpid +application/vnd.hp-hps hps +application/vnd.hp-jlyt jlt +application/vnd.hp-pcl pcl +application/vnd.hp-pclxl pclxl +# application/vnd.httphone +application/vnd.hydrostatix.sof-data sfd-hdstx +application/vnd.hzn-3d-crossword x3d +# application/vnd.ibm.afplinedata +# application/vnd.ibm.electronic-media +application/vnd.ibm.minipay mpy +application/vnd.ibm.modcap afp listafp list3820 +application/vnd.ibm.rights-management irm +application/vnd.ibm.secure-container sc +application/vnd.iccprofile icc icm +application/vnd.igloader igl +application/vnd.immervision-ivp ivp +application/vnd.immervision-ivu ivu +# application/vnd.informedcontrol.rms+xml +# application/vnd.informix-visionary +application/vnd.intercon.formnet xpw xpx +# application/vnd.intertrust.digibox +# application/vnd.intertrust.nncp +application/vnd.intu.qbo qbo +application/vnd.intu.qfx qfx +# application/vnd.iptc.g2.conceptitem+xml +# application/vnd.iptc.g2.knowledgeitem+xml +# application/vnd.iptc.g2.newsitem+xml +# application/vnd.iptc.g2.packageitem+xml +application/vnd.ipunplugged.rcprofile rcprofile +application/vnd.irepository.package+xml irp +application/vnd.is-xpr xpr +application/vnd.jam jam +# application/vnd.japannet-directory-service +# application/vnd.japannet-jpnstore-wakeup +# application/vnd.japannet-payment-wakeup +# application/vnd.japannet-registration +# application/vnd.japannet-registration-wakeup +# application/vnd.japannet-setstore-wakeup +# application/vnd.japannet-verification +# application/vnd.japannet-verification-wakeup +application/vnd.jcp.javame.midlet-rms rms +application/vnd.jisp jisp +application/vnd.joost.joda-archive joda +application/vnd.kahootz ktz ktr +application/vnd.kde.karbon karbon +application/vnd.kde.kchart chrt +application/vnd.kde.kformula kfo +application/vnd.kde.kivio flw +application/vnd.kde.kontour kon +application/vnd.kde.kpresenter kpr kpt +application/vnd.kde.kspread ksp +application/vnd.kde.kword kwd kwt +application/vnd.kenameaapp htke +application/vnd.kidspiration kia +application/vnd.kinar kne knp +application/vnd.koan skp skd skt skm +application/vnd.kodak-descriptor sse +# application/vnd.liberty-request+xml +application/vnd.llamagraphics.life-balance.desktop lbd +application/vnd.llamagraphics.life-balance.exchange+xml lbe +application/vnd.lotus-1-2-3 123 +application/vnd.lotus-approach apr +application/vnd.lotus-freelance pre +application/vnd.lotus-notes nsf +application/vnd.lotus-organizer org +application/vnd.lotus-screencam scm +application/vnd.lotus-wordpro lwp +application/vnd.macports.portpkg portpkg +# application/vnd.marlin.drm.actiontoken+xml +# application/vnd.marlin.drm.conftoken+xml +# application/vnd.marlin.drm.license+xml +# application/vnd.marlin.drm.mdcf +application/vnd.mcd mcd +application/vnd.medcalcdata mc1 +application/vnd.mediastation.cdkey cdkey +# application/vnd.meridian-slingshot +application/vnd.mfer mwf +application/vnd.mfmp mfm +application/vnd.micrografx.flo flo +application/vnd.micrografx.igx igx +application/vnd.mif mif +# application/vnd.minisoft-hp3000-save +# application/vnd.mitsubishi.misty-guard.trustweb +application/vnd.mobius.daf daf +application/vnd.mobius.dis dis +application/vnd.mobius.mbk mbk +application/vnd.mobius.mqy mqy +application/vnd.mobius.msl msl +application/vnd.mobius.plc plc +application/vnd.mobius.txf txf +application/vnd.mophun.application mpn +application/vnd.mophun.certificate mpc +# application/vnd.motorola.flexsuite +# application/vnd.motorola.flexsuite.adsi +# application/vnd.motorola.flexsuite.fis +# application/vnd.motorola.flexsuite.gotap +# application/vnd.motorola.flexsuite.kmr +# application/vnd.motorola.flexsuite.ttc +# application/vnd.motorola.flexsuite.wem +# application/vnd.motorola.iprm +application/vnd.mozilla.xul+xml xul +application/vnd.ms-artgalry cil +# application/vnd.ms-asf +application/vnd.ms-cab-compressed cab +application/vnd.ms-excel xls xlm xla xlc xlt xlw +application/vnd.ms-excel.addin.macroenabled.12 xlam +application/vnd.ms-excel.sheet.binary.macroenabled.12 xlsb +application/vnd.ms-excel.sheet.macroenabled.12 xlsm +application/vnd.ms-excel.template.macroenabled.12 xltm +application/vnd.ms-fontobject eot +application/vnd.ms-htmlhelp chm +application/vnd.ms-ims ims +application/vnd.ms-lrm lrm +application/vnd.ms-pki.seccat cat +application/vnd.ms-pki.stl stl +# application/vnd.ms-playready.initiator+xml +application/vnd.ms-powerpoint ppt pps pot +application/vnd.ms-powerpoint.addin.macroenabled.12 ppam +application/vnd.ms-powerpoint.presentation.macroenabled.12 pptm +application/vnd.ms-powerpoint.slide.macroenabled.12 sldm +application/vnd.ms-powerpoint.slideshow.macroenabled.12 ppsm +application/vnd.ms-powerpoint.template.macroenabled.12 potm +application/vnd.ms-project mpp mpt +# application/vnd.ms-tnef +# application/vnd.ms-wmdrm.lic-chlg-req +# application/vnd.ms-wmdrm.lic-resp +# application/vnd.ms-wmdrm.meter-chlg-req +# application/vnd.ms-wmdrm.meter-resp +application/vnd.ms-word.document.macroenabled.12 docm +application/vnd.ms-word.template.macroenabled.12 dotm +application/vnd.ms-works wps wks wcm wdb +application/vnd.ms-wpl wpl +application/vnd.ms-xpsdocument xps +application/vnd.mseq mseq +# application/vnd.msign +# application/vnd.multiad.creator +# application/vnd.multiad.creator.cif +# application/vnd.music-niff +application/vnd.musician mus +application/vnd.muvee.style msty +# application/vnd.ncd.control +# application/vnd.ncd.reference +# application/vnd.nervana +# application/vnd.netfpx +application/vnd.neurolanguage.nlu nlu +application/vnd.noblenet-directory nnd +application/vnd.noblenet-sealer nns +application/vnd.noblenet-web nnw +# application/vnd.nokia.catalogs +# application/vnd.nokia.conml+wbxml +# application/vnd.nokia.conml+xml +# application/vnd.nokia.isds-radio-presets +# application/vnd.nokia.iptv.config+xml +# application/vnd.nokia.landmark+wbxml +# application/vnd.nokia.landmark+xml +# application/vnd.nokia.landmarkcollection+xml +# application/vnd.nokia.n-gage.ac+xml +application/vnd.nokia.n-gage.data ngdat +application/vnd.nokia.n-gage.symbian.install n-gage +# application/vnd.nokia.ncd +# application/vnd.nokia.pcd+wbxml +# application/vnd.nokia.pcd+xml +application/vnd.nokia.radio-preset rpst +application/vnd.nokia.radio-presets rpss +application/vnd.novadigm.edm edm +application/vnd.novadigm.edx edx +application/vnd.novadigm.ext ext +# application/vnd.ntt-local.file-transfer +application/vnd.oasis.opendocument.chart odc +application/vnd.oasis.opendocument.chart-template otc +application/vnd.oasis.opendocument.database odb +application/vnd.oasis.opendocument.formula odf +application/vnd.oasis.opendocument.formula-template odft +application/vnd.oasis.opendocument.graphics odg +application/vnd.oasis.opendocument.graphics-template otg +application/vnd.oasis.opendocument.image odi +application/vnd.oasis.opendocument.image-template oti +application/vnd.oasis.opendocument.presentation odp +application/vnd.oasis.opendocument.presentation-template otp +application/vnd.oasis.opendocument.spreadsheet ods +application/vnd.oasis.opendocument.spreadsheet-template ots +application/vnd.oasis.opendocument.text odt +application/vnd.oasis.opendocument.text-master otm +application/vnd.oasis.opendocument.text-template ott +application/vnd.oasis.opendocument.text-web oth +# application/vnd.obn +application/vnd.olpc-sugar xo +# application/vnd.oma-scws-config +# application/vnd.oma-scws-http-request +# application/vnd.oma-scws-http-response +# application/vnd.oma.bcast.associated-procedure-parameter+xml +# application/vnd.oma.bcast.drm-trigger+xml +# application/vnd.oma.bcast.imd+xml +# application/vnd.oma.bcast.ltkm +# application/vnd.oma.bcast.notification+xml +# application/vnd.oma.bcast.provisioningtrigger +# application/vnd.oma.bcast.sgboot +# application/vnd.oma.bcast.sgdd+xml +# application/vnd.oma.bcast.sgdu +# application/vnd.oma.bcast.simple-symbol-container +# application/vnd.oma.bcast.smartcard-trigger+xml +# application/vnd.oma.bcast.sprov+xml +# application/vnd.oma.bcast.stkm +# application/vnd.oma.dcd +# application/vnd.oma.dcdc +application/vnd.oma.dd2+xml dd2 +# application/vnd.oma.drm.risd+xml +# application/vnd.oma.group-usage-list+xml +# application/vnd.oma.poc.detailed-progress-report+xml +# application/vnd.oma.poc.final-report+xml +# application/vnd.oma.poc.groups+xml +# application/vnd.oma.poc.invocation-descriptor+xml +# application/vnd.oma.poc.optimized-progress-report+xml +# application/vnd.oma.push +# application/vnd.oma.scidm.messages+xml +# application/vnd.oma.xcap-directory+xml +# application/vnd.omads-email+xml +# application/vnd.omads-file+xml +# application/vnd.omads-folder+xml +# application/vnd.omaloc-supl-init +application/vnd.openofficeorg.extension oxt +# application/vnd.openxmlformats-officedocument.custom-properties+xml +# application/vnd.openxmlformats-officedocument.customxmlproperties+xml +# application/vnd.openxmlformats-officedocument.drawing+xml +# application/vnd.openxmlformats-officedocument.drawingml.chart+xml +# application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml +# application/vnd.openxmlformats-officedocument.drawingml.diagramcolors+xml +# application/vnd.openxmlformats-officedocument.drawingml.diagramdata+xml +# application/vnd.openxmlformats-officedocument.drawingml.diagramlayout+xml +# application/vnd.openxmlformats-officedocument.drawingml.diagramstyle+xml +# application/vnd.openxmlformats-officedocument.extended-properties+xml +# application/vnd.openxmlformats-officedocument.presentationml.commentauthors+xml +# application/vnd.openxmlformats-officedocument.presentationml.comments+xml +# application/vnd.openxmlformats-officedocument.presentationml.handoutmaster+xml +# application/vnd.openxmlformats-officedocument.presentationml.notesmaster+xml +# application/vnd.openxmlformats-officedocument.presentationml.notesslide+xml +application/vnd.openxmlformats-officedocument.presentationml.presentation pptx +# application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml +# application/vnd.openxmlformats-officedocument.presentationml.presprops+xml +application/vnd.openxmlformats-officedocument.presentationml.slide sldx +# application/vnd.openxmlformats-officedocument.presentationml.slide+xml +# application/vnd.openxmlformats-officedocument.presentationml.slidelayout+xml +# application/vnd.openxmlformats-officedocument.presentationml.slidemaster+xml +application/vnd.openxmlformats-officedocument.presentationml.slideshow ppsx +# application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml +# application/vnd.openxmlformats-officedocument.presentationml.slideupdateinfo+xml +# application/vnd.openxmlformats-officedocument.presentationml.tablestyles+xml +# application/vnd.openxmlformats-officedocument.presentationml.tags+xml +application/vnd.openxmlformats-officedocument.presentationml.template potx +# application/vnd.openxmlformats-officedocument.presentationml.template.main+xml +# application/vnd.openxmlformats-officedocument.presentationml.viewprops+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.calcchain+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.externallink+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcachedefinition+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcacherecords+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.pivottable+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.querytable+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.revisionheaders+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.revisionlog+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.sharedstrings+xml +application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx +# application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.sheetmetadata+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.tablesinglecells+xml +application/vnd.openxmlformats-officedocument.spreadsheetml.template xltx +# application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.usernames+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.volatiledependencies+xml +# application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml +# application/vnd.openxmlformats-officedocument.theme+xml +# application/vnd.openxmlformats-officedocument.themeoverride+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml +application/vnd.openxmlformats-officedocument.wordprocessingml.document docx +# application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.fonttable+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml +application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx +# application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml +# application/vnd.openxmlformats-officedocument.wordprocessingml.websettings+xml +# application/vnd.openxmlformats-package.core-properties+xml +# application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml +# application/vnd.osa.netdeploy +# application/vnd.osgi.bundle +application/vnd.osgi.dp dp +# application/vnd.otps.ct-kip+xml +application/vnd.palm pdb pqa oprc +# application/vnd.paos.xml +application/vnd.pawaafile paw +application/vnd.pg.format str +application/vnd.pg.osasli ei6 +# application/vnd.piaccess.application-licence +application/vnd.picsel efif +application/vnd.pmi.widget wg +# application/vnd.poc.group-advertisement+xml +application/vnd.pocketlearn plf +application/vnd.powerbuilder6 pbd +# application/vnd.powerbuilder6-s +# application/vnd.powerbuilder7 +# application/vnd.powerbuilder7-s +# application/vnd.powerbuilder75 +# application/vnd.powerbuilder75-s +# application/vnd.preminet +application/vnd.previewsystems.box box +application/vnd.proteus.magazine mgz +application/vnd.publishare-delta-tree qps +application/vnd.pvi.ptid1 ptid +# application/vnd.pwg-multiplexed +# application/vnd.pwg-xhtml-print+xml +# application/vnd.qualcomm.brew-app-res +application/vnd.quark.quarkxpress qxd qxt qwd qwt qxl qxb +# application/vnd.radisys.moml+xml +# application/vnd.radisys.msml+xml +# application/vnd.radisys.msml-audit+xml +# application/vnd.radisys.msml-audit-conf+xml +# application/vnd.radisys.msml-audit-conn+xml +# application/vnd.radisys.msml-audit-dialog+xml +# application/vnd.radisys.msml-audit-stream+xml +# application/vnd.radisys.msml-conf+xml +# application/vnd.radisys.msml-dialog+xml +# application/vnd.radisys.msml-dialog-base+xml +# application/vnd.radisys.msml-dialog-fax-detect+xml +# application/vnd.radisys.msml-dialog-fax-sendrecv+xml +# application/vnd.radisys.msml-dialog-group+xml +# application/vnd.radisys.msml-dialog-speech+xml +# application/vnd.radisys.msml-dialog-transform+xml +# application/vnd.rapid +application/vnd.realvnc.bed bed +application/vnd.recordare.musicxml mxl +application/vnd.recordare.musicxml+xml musicxml +# application/vnd.renlearn.rlprint +application/vnd.rim.cod cod +application/vnd.rn-realmedia rm +application/vnd.route66.link66+xml link66 +# application/vnd.ruckus.download +# application/vnd.s3sms +application/vnd.sailingtracker.track st +# application/vnd.sbm.cid +# application/vnd.sbm.mid2 +# application/vnd.scribus +# application/vnd.sealed.3df +# application/vnd.sealed.csf +# application/vnd.sealed.doc +# application/vnd.sealed.eml +# application/vnd.sealed.mht +# application/vnd.sealed.net +# application/vnd.sealed.ppt +# application/vnd.sealed.tiff +# application/vnd.sealed.xls +# application/vnd.sealedmedia.softseal.html +# application/vnd.sealedmedia.softseal.pdf +application/vnd.seemail see +application/vnd.sema sema +application/vnd.semd semd +application/vnd.semf semf +application/vnd.shana.informed.formdata ifm +application/vnd.shana.informed.formtemplate itp +application/vnd.shana.informed.interchange iif +application/vnd.shana.informed.package ipk +application/vnd.simtech-mindmapper twd twds +application/vnd.smaf mmf +# application/vnd.smart.notebook +application/vnd.smart.teacher teacher +# application/vnd.software602.filler.form+xml +# application/vnd.software602.filler.form-xml-zip +application/vnd.solent.sdkm+xml sdkm sdkd +application/vnd.spotfire.dxp dxp +application/vnd.spotfire.sfs sfs +# application/vnd.sss-cod +# application/vnd.sss-dtf +# application/vnd.sss-ntf +application/vnd.stardivision.calc sdc +application/vnd.stardivision.draw sda +application/vnd.stardivision.impress sdd +application/vnd.stardivision.math smf +application/vnd.stardivision.writer sdw +application/vnd.stardivision.writer vor +application/vnd.stardivision.writer-global sgl +# application/vnd.street-stream +application/vnd.sun.xml.calc sxc +application/vnd.sun.xml.calc.template stc +application/vnd.sun.xml.draw sxd +application/vnd.sun.xml.draw.template std +application/vnd.sun.xml.impress sxi +application/vnd.sun.xml.impress.template sti +application/vnd.sun.xml.math sxm +application/vnd.sun.xml.writer sxw +application/vnd.sun.xml.writer.global sxg +application/vnd.sun.xml.writer.template stw +# application/vnd.sun.wadl+xml +application/vnd.sus-calendar sus susp +application/vnd.svd svd +# application/vnd.swiftview-ics +application/vnd.symbian.install sis sisx +application/vnd.syncml+xml xsm +application/vnd.syncml.dm+wbxml bdm +application/vnd.syncml.dm+xml xdm +# application/vnd.syncml.dm.notification +# application/vnd.syncml.ds.notification +application/vnd.tao.intent-module-archive tao +application/vnd.tmobile-livetv tmo +application/vnd.trid.tpt tpt +application/vnd.triscape.mxs mxs +application/vnd.trueapp tra +# application/vnd.truedoc +application/vnd.ufdl ufd ufdl +application/vnd.uiq.theme utz +application/vnd.umajin umj +application/vnd.unity unityweb +application/vnd.uoml+xml uoml +# application/vnd.uplanet.alert +# application/vnd.uplanet.alert-wbxml +# application/vnd.uplanet.bearer-choice +# application/vnd.uplanet.bearer-choice-wbxml +# application/vnd.uplanet.cacheop +# application/vnd.uplanet.cacheop-wbxml +# application/vnd.uplanet.channel +# application/vnd.uplanet.channel-wbxml +# application/vnd.uplanet.list +# application/vnd.uplanet.list-wbxml +# application/vnd.uplanet.listcmd +# application/vnd.uplanet.listcmd-wbxml +# application/vnd.uplanet.signal +application/vnd.vcx vcx +# application/vnd.vd-study +# application/vnd.vectorworks +# application/vnd.vidsoft.vidconference +application/vnd.visio vsd vst vss vsw +application/vnd.visionary vis +# application/vnd.vividence.scriptfile +application/vnd.vsf vsf +# application/vnd.wap.sic +# application/vnd.wap.slc +application/vnd.wap.wbxml wbxml +application/vnd.wap.wmlc wmlc +application/vnd.wap.wmlscriptc wmlsc +application/vnd.webturbo wtb +# application/vnd.wfa.wsc +# application/vnd.wmc +# application/vnd.wmf.bootstrap +# application/vnd.wolfram.mathematica +# application/vnd.wolfram.mathematica.package +application/vnd.wolfram.player nbp +application/vnd.wordperfect wpd +application/vnd.wqd wqd +# application/vnd.wrq-hp3000-labelled +application/vnd.wt.stf stf +# application/vnd.wv.csp+wbxml +# application/vnd.wv.csp+xml +# application/vnd.wv.ssp+xml +application/vnd.xara xar +application/vnd.xfdl xfdl +# application/vnd.xfdl.webform +# application/vnd.xmi+xml +# application/vnd.xmpie.cpkg +# application/vnd.xmpie.dpkg +# application/vnd.xmpie.plan +# application/vnd.xmpie.ppkg +# application/vnd.xmpie.xlim +application/vnd.yamaha.hv-dic hvd +application/vnd.yamaha.hv-script hvs +application/vnd.yamaha.hv-voice hvp +application/vnd.yamaha.openscoreformat osf +application/vnd.yamaha.openscoreformat.osfpvg+xml osfpvg +application/vnd.yamaha.smaf-audio saf +application/vnd.yamaha.smaf-phrase spf +application/vnd.yellowriver-custom-menu cmp +application/vnd.zul zir zirz +application/vnd.zzazz.deck+xml zaz +application/voicexml+xml vxml +# application/watcherinfo+xml +# application/whoispp-query +# application/whoispp-response +application/winhlp hlp +# application/wita +# application/wordperfect5.1 +application/wsdl+xml wsdl +application/wspolicy+xml wspolicy +application/x-abiword abw +application/x-ace-compressed ace +application/x-authorware-bin aab x32 u32 vox +application/x-authorware-map aam +application/x-authorware-seg aas +application/x-bcpio bcpio +application/x-bittorrent torrent +application/x-bzip bz +application/x-bzip2 bz2 boz +application/x-cdlink vcd +application/x-chat chat +application/x-chess-pgn pgn +# application/x-compress +application/x-cpio cpio +application/x-csh csh +application/x-debian-package deb udeb +application/x-director dir dcr dxr cst cct cxt w3d fgd swa +application/x-doom wad +application/x-dtbncx+xml ncx +application/x-dtbook+xml dtb +application/x-dtbresource+xml res +application/x-dvi dvi +application/x-font-bdf bdf +# application/x-font-dos +# application/x-font-framemaker +application/x-font-ghostscript gsf +# application/x-font-libgrx +application/x-font-linux-psf psf +application/x-font-otf otf +application/x-font-pcf pcf +application/x-font-snf snf +# application/x-font-speedo +# application/x-font-sunos-news +application/x-font-ttf ttf ttc +application/x-font-type1 pfa pfb pfm afm +# application/x-font-vfont +application/x-futuresplash spl +application/x-gnumeric gnumeric +application/x-gtar gtar +# application/x-gzip +application/x-hdf hdf +application/x-java-jnlp-file jnlp +application/x-latex latex +application/x-mobipocket-ebook prc mobi +application/x-ms-application application +application/x-ms-wmd wmd +application/x-ms-wmz wmz +application/x-ms-xbap xbap +application/x-msaccess mdb +application/x-msbinder obd +application/x-mscardfile crd +application/x-msclip clp +application/x-msdownload exe dll com bat msi +application/x-msmediaview mvb m13 m14 +application/x-msmetafile wmf +application/x-msmoney mny +application/x-mspublisher pub +application/x-msschedule scd +application/x-msterminal trm +application/x-mswrite wri +application/x-netcdf nc cdf +application/x-pkcs12 p12 pfx +application/x-pkcs7-certificates p7b spc +application/x-pkcs7-certreqresp p7r +application/x-rar-compressed rar +application/x-sh sh +application/x-shar shar +application/x-shockwave-flash swf +application/x-silverlight-app xap +application/x-stuffit sit +application/x-stuffitx sitx +application/x-sv4cpio sv4cpio +application/x-sv4crc sv4crc +application/x-tar tar +application/x-tcl tcl +application/x-tex tex +application/x-tex-tfm tfm +application/x-texinfo texinfo texi +application/x-ustar ustar +application/x-wais-source src +application/x-x509-ca-cert der crt +application/x-xfig fig +application/x-xpinstall xpi +# application/x400-bp +# application/xcap-att+xml +# application/xcap-caps+xml +# application/xcap-el+xml +# application/xcap-error+xml +# application/xcap-ns+xml +# application/xcon-conference-info-diff+xml +# application/xcon-conference-info+xml +application/xenc+xml xenc +application/xhtml+xml xhtml xht +# application/xhtml-voice+xml +application/xml xml xsl +application/xml-dtd dtd +# application/xml-external-parsed-entity +# application/xmpp+xml +application/xop+xml xop +application/xslt+xml xslt +application/xspf+xml xspf +application/xv+xml mxml xhvml xvml xvm +application/zip zip +# audio/32kadpcm +# audio/3gpp +# audio/3gpp2 +# audio/ac3 +audio/adpcm adp +# audio/amr +# audio/amr-wb +# audio/amr-wb+ +# audio/asc +# audio/atrac-advanced-lossless +# audio/atrac-x +# audio/atrac3 +audio/basic au snd +# audio/bv16 +# audio/bv32 +# audio/clearmode +# audio/cn +# audio/dat12 +# audio/dls +# audio/dsr-es201108 +# audio/dsr-es202050 +# audio/dsr-es202211 +# audio/dsr-es202212 +# audio/dvi4 +# audio/eac3 +# audio/evrc +# audio/evrc-qcp +# audio/evrc0 +# audio/evrc1 +# audio/evrcb +# audio/evrcb0 +# audio/evrcb1 +# audio/evrcwb +# audio/evrcwb0 +# audio/evrcwb1 +# audio/example +# audio/g719 +# audio/g722 +# audio/g7221 +# audio/g723 +# audio/g726-16 +# audio/g726-24 +# audio/g726-32 +# audio/g726-40 +# audio/g728 +# audio/g729 +# audio/g7291 +# audio/g729d +# audio/g729e +# audio/gsm +# audio/gsm-efr +# audio/ilbc +# audio/l16 +# audio/l20 +# audio/l24 +# audio/l8 +# audio/lpc +audio/midi mid midi kar rmi +# audio/mobile-xmf +audio/mp4 mp4a +# audio/mp4a-latm +# audio/mpa +# audio/mpa-robust +audio/mpeg mpga mp2 mp2a mp3 m2a m3a +# audio/mpeg4-generic +audio/ogg oga ogg spx +# audio/parityfec +# audio/pcma +# audio/pcma-wb +# audio/pcmu-wb +# audio/pcmu +# audio/prs.sid +# audio/qcelp +# audio/red +# audio/rtp-enc-aescm128 +# audio/rtp-midi +# audio/rtx +# audio/smv +# audio/smv0 +# audio/smv-qcp +# audio/sp-midi +# audio/speex +# audio/t140c +# audio/t38 +# audio/telephone-event +# audio/tone +# audio/uemclip +# audio/ulpfec +# audio/vdvi +# audio/vmr-wb +# audio/vnd.3gpp.iufp +# audio/vnd.4sb +# audio/vnd.audiokoz +# audio/vnd.celp +# audio/vnd.cisco.nse +# audio/vnd.cmles.radio-events +# audio/vnd.cns.anp1 +# audio/vnd.cns.inf1 +audio/vnd.digital-winds eol +# audio/vnd.dlna.adts +# audio/vnd.dolby.heaac.1 +# audio/vnd.dolby.heaac.2 +# audio/vnd.dolby.mlp +# audio/vnd.dolby.mps +# audio/vnd.dolby.pl2 +# audio/vnd.dolby.pl2x +# audio/vnd.dolby.pl2z +# audio/vnd.dolby.pulse.1 +audio/vnd.dra dra +audio/vnd.dts dts +audio/vnd.dts.hd dtshd +# audio/vnd.everad.plj +# audio/vnd.hns.audio +audio/vnd.lucent.voice lvp +audio/vnd.ms-playready.media.pya pya +# audio/vnd.nokia.mobile-xmf +# audio/vnd.nortel.vbk +audio/vnd.nuera.ecelp4800 ecelp4800 +audio/vnd.nuera.ecelp7470 ecelp7470 +audio/vnd.nuera.ecelp9600 ecelp9600 +# audio/vnd.octel.sbc +# audio/vnd.qcelp +# audio/vnd.rhetorex.32kadpcm +# audio/vnd.sealedmedia.softseal.mpeg +# audio/vnd.vmx.cvsd +# audio/vorbis +# audio/vorbis-config +audio/x-aac aac +audio/x-aiff aif aiff aifc +audio/x-mpegurl m3u +audio/x-ms-wax wax +audio/x-ms-wma wma +audio/x-pn-realaudio ram ra +audio/x-pn-realaudio-plugin rmp +audio/x-wav wav +chemical/x-cdx cdx +chemical/x-cif cif +chemical/x-cmdf cmdf +chemical/x-cml cml +chemical/x-csml csml +# chemical/x-pdb +chemical/x-xyz xyz +image/bmp bmp +image/cgm cgm +# image/example +# image/fits +image/g3fax g3 +image/gif gif +image/ief ief +# image/jp2 +image/jpeg jpeg jpg jpe +# image/jpm +# image/jpx +# image/naplps +image/png png +image/prs.btif btif +# image/prs.pti +image/svg+xml svg svgz +# image/t38 +image/tiff tiff tif +# image/tiff-fx +image/vnd.adobe.photoshop psd +# image/vnd.cns.inf2 +image/vnd.djvu djvu djv +image/vnd.dwg dwg +image/vnd.dxf dxf +image/vnd.fastbidsheet fbs +image/vnd.fpx fpx +image/vnd.fst fst +image/vnd.fujixerox.edmics-mmr mmr +image/vnd.fujixerox.edmics-rlc rlc +# image/vnd.globalgraphics.pgb +# image/vnd.microsoft.icon +# image/vnd.mix +image/vnd.ms-modi mdi +image/vnd.net-fpx npx +# image/vnd.radiance +# image/vnd.sealed.png +# image/vnd.sealedmedia.softseal.gif +# image/vnd.sealedmedia.softseal.jpg +# image/vnd.svf +image/vnd.wap.wbmp wbmp +image/vnd.xiff xif +image/x-cmu-raster ras +image/x-cmx cmx +image/x-freehand fh fhc fh4 fh5 fh7 +image/x-icon ico +image/x-pcx pcx +image/x-pict pic pct +image/x-portable-anymap pnm +image/x-portable-bitmap pbm +image/x-portable-graymap pgm +image/x-portable-pixmap ppm +image/x-rgb rgb +image/x-xbitmap xbm +image/x-xpixmap xpm +image/x-xwindowdump xwd +# message/cpim +# message/delivery-status +# message/disposition-notification +# message/example +# message/external-body +# message/global +# message/global-delivery-status +# message/global-disposition-notification +# message/global-headers +# message/http +# message/imdn+xml +# message/news +# message/partial +message/rfc822 eml mime +# message/s-http +# message/sip +# message/sipfrag +# message/tracking-status +# message/vnd.si.simp +# model/example +model/iges igs iges +model/mesh msh mesh silo +model/vnd.dwf dwf +# model/vnd.flatland.3dml +model/vnd.gdl gdl +# model/vnd.gs-gdl +# model/vnd.gs.gdl +model/vnd.gtw gtw +# model/vnd.moml+xml +model/vnd.mts mts +# model/vnd.parasolid.transmit.binary +# model/vnd.parasolid.transmit.text +model/vnd.vtu vtu +model/vrml wrl vrml +# multipart/alternative +# multipart/appledouble +# multipart/byteranges +# multipart/digest +# multipart/encrypted +# multipart/example +# multipart/form-data +# multipart/header-set +# multipart/mixed +# multipart/parallel +# multipart/related +# multipart/report +# multipart/signed +# multipart/voice-message +text/calendar ics ifb +text/css css +text/csv csv +# text/directory +# text/dns +# text/ecmascript +# text/enriched +# text/example +text/html html htm +# text/javascript +# text/parityfec +text/plain txt text conf def list log in +# text/prs.fallenstein.rst +text/prs.lines.tag dsc +# text/vnd.radisys.msml-basic-layout +# text/red +# text/rfc822-headers +text/richtext rtx +# text/rtf +# text/rtp-enc-aescm128 +# text/rtx +text/sgml sgml sgm +# text/t140 +text/tab-separated-values tsv +text/troff t tr roff man me ms +# text/ulpfec +text/uri-list uri uris urls +# text/vnd.abc +text/vnd.curl curl +text/vnd.curl.dcurl dcurl +text/vnd.curl.scurl scurl +text/vnd.curl.mcurl mcurl +# text/vnd.dmclientscript +# text/vnd.esmertec.theme-descriptor +text/vnd.fly fly +text/vnd.fmi.flexstor flx +text/vnd.graphviz gv +text/vnd.in3d.3dml 3dml +text/vnd.in3d.spot spot +# text/vnd.iptc.newsml +# text/vnd.iptc.nitf +# text/vnd.latex-z +# text/vnd.motorola.reflex +# text/vnd.ms-mediapackage +# text/vnd.net2phone.commcenter.command +# text/vnd.si.uricatalogue +text/vnd.sun.j2me.app-descriptor jad +# text/vnd.trolltech.linguist +# text/vnd.wap.si +# text/vnd.wap.sl +text/vnd.wap.wml wml +text/vnd.wap.wmlscript wmls +text/x-asm s asm +text/x-c c cc cxx cpp h hh dic +text/x-fortran f for f77 f90 +text/x-pascal p pas +text/x-java-source java +text/x-setext etx +text/x-uuencode uu +text/x-vcalendar vcs +text/x-vcard vcf +# text/xml +# text/xml-external-parsed-entity +video/3gpp 3gp +# video/3gpp-tt +video/3gpp2 3g2 +# video/bmpeg +# video/bt656 +# video/celb +# video/dv +# video/example +video/h261 h261 +video/h263 h263 +# video/h263-1998 +# video/h263-2000 +video/h264 h264 +video/jpeg jpgv +# video/jpeg2000 +video/jpm jpm jpgm +video/mj2 mj2 mjp2 +# video/mp1s +# video/mp2p +# video/mp2t +video/mp4 mp4 mp4v mpg4 +# video/mp4v-es +video/mpeg mpeg mpg mpe m1v m2v +# video/mpeg4-generic +# video/mpv +# video/nv +video/ogg ogv +# video/parityfec +# video/pointer +video/quicktime qt mov +# video/raw +# video/rtp-enc-aescm128 +# video/rtx +# video/smpte292m +# video/ulpfec +# video/vc1 +# video/vnd.cctv +# video/vnd.dlna.mpeg-tts +video/vnd.fvt fvt +# video/vnd.hns.video +# video/vnd.iptvforum.1dparityfec-1010 +# video/vnd.iptvforum.1dparityfec-2005 +# video/vnd.iptvforum.2dparityfec-1010 +# video/vnd.iptvforum.2dparityfec-2005 +# video/vnd.iptvforum.ttsavc +# video/vnd.iptvforum.ttsmpeg2 +# video/vnd.motorola.video +# video/vnd.motorola.videop +video/vnd.mpegurl mxu m4u +video/vnd.ms-playready.media.pyv pyv +# video/vnd.nokia.interleaved-multimedia +# video/vnd.nokia.videovoip +# video/vnd.objectvideo +# video/vnd.sealed.mpeg1 +# video/vnd.sealed.mpeg4 +# video/vnd.sealed.swf +# video/vnd.sealedmedia.softseal.mov +video/vnd.vivo viv +video/x-f4v f4v +video/x-fli fli +video/x-flv flv +video/x-m4v m4v +video/x-ms-asf asf asx +video/x-ms-wm wm +video/x-ms-wmv wmv +video/x-ms-wmx wmx +video/x-ms-wvx wvx +video/x-msvideo avi +video/x-sgi-movie movie +x-conference/x-cooltalk ice diff --git a/warp.cabal b/warp.cabal index 22c0e3c51..697fb360c 100644 --- a/warp.cabal +++ b/warp.cabal @@ -10,6 +10,7 @@ Category: Web, Yesod Build-Type: Simple Cabal-Version: >=1.6 Stability: Stable +Extra-source-files: mime.types flag timeout-protection Description: Use timeouts (very performance-costly) to protect against DOS attacks. @@ -25,6 +26,8 @@ Library , enumerator >= 0.4 && < 0.5 , blaze-builder >= 0.2.1.3 && < 0.3 , sendfile >= 0.7.2 && < 0.8 + , containers >= 0.2 && < 0.5 + , template-haskell if flag(network-bytestring) build-depends: network >= 2.2.1 && < 2.2.3 , network-bytestring >= 0.1.3 && < 0.1.4 @@ -36,6 +39,7 @@ Library Build-Depends: unix >= 2.4 && < 2.5 Exposed-modules: Network.Wai.Handler.Warp Network.Wai.Handler.Warp.Util + Other-modules: UtilHelper ghc-options: -Wall if !flag(timeout-protection) Cpp-options: -DNO_TIMEOUT_PROTECTION From d0ab041555caa250896abc241a4f544c4c443edf Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 6 Jan 2011 00:26:11 +0200 Subject: [PATCH 22/90] Using getFileSize and mimeTypes in warp.hs --- warp.hs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/warp.hs b/warp.hs index 117fd8514..b5eb8839b 100644 --- a/warp.hs +++ b/warp.hs @@ -9,6 +9,9 @@ import Data.Text (unpack) import Data.Text.Encoding (decodeUtf8With) import Data.Text.Encoding.Error (lenientDecode) import Control.Monad.IO.Class (liftIO) +import Network.Wai.Handler.Warp.Util (mimeTypes, getFileSize) +import qualified Data.Map as Map +import qualified Data.ByteString.Char8 as S8 main :: IO () main = do @@ -30,9 +33,13 @@ app prefix Request { requestMethod = m, pathInfo = p } let ext = reverse $ takeWhile (/= '.') $ reverse file let ct = getCT ext e <- liftIO $ doesFileExist file - -- FIXME check file size if e - then return $ ResponseFile status200 [("Content-Type", ct)] file + then do + size <- liftIO $ getFileSize file + return $ ResponseFile status200 + [ ("Content-Type", ct) + , ("Content-Length", S8.pack $ show size) + ] file else return $ responseLBS status404 [("Content-Type", "text/plain")] "File not found" | otherwise = return $ responseLBS status405 [("Content-Type", "text/plain")] "Bad method" @@ -58,14 +65,7 @@ join [x] = x join (x:xs) = x ++ '/' : join xs getCT :: String -> ByteString -getCT "jpg" = "image/jpeg" -getCT "jpeg" = "image/jpeg" -getCT "js" = "text/javascript" -getCT "css" = "text/css" -getCT "html" = "text/html" -getCT "png" = "image/png" -getCT "gif" = "image/gif" -getCT "txt" = "text/plain" -getCT "flv" = "video/x-flv" -getCT "ogv" = "video/ogg" -getCT _ = "application/octet-stream" +getCT s = + case Map.lookup (S8.pack s) mimeTypes of + Just ct -> ct + Nothing -> "application/octet-stream" From 95a4d23a18a5782926afc9053aaa90753be2ba23 Mon Sep 17 00:00:00 2001 From: Michael Date: Thu, 6 Jan 2011 08:09:37 +0200 Subject: [PATCH 23/90] ResponseBuilder constructor --- Network/Wai/Handler/Warp.hs | 18 ++++++++++++++++-- UtilHelper.hs | 4 ++-- pong.hs | 2 +- 3 files changed, 19 insertions(+), 5 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 0cde7270e..7833148a2 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -53,9 +53,10 @@ import Data.Enumerator.IO (iterHandle, enumHandle) import Blaze.ByteString.Builder.Enumerator (builderToByteString) import Blaze.ByteString.Builder.HTTP (chunkedTransferEncoding, chunkedTransferTerminator) -import Blaze.ByteString.Builder (fromByteString, Builder, toLazyByteString) +import Blaze.ByteString.Builder + (fromByteString, Builder, toLazyByteString, toByteStringIO) import Blaze.ByteString.Builder.Char8 (fromChar, fromString) -import Data.Monoid (mconcat) +import Data.Monoid (mconcat, mappend) import Network.Socket.SendFile (sendFile) import Control.Monad.IO.Class (liftIO) @@ -232,6 +233,19 @@ sendResponse req hv socket (ResponseFile s hs fp) = do sendFile socket fp return $ lookup "content-length" hs /= Nothing else return True +sendResponse req hv socket (ResponseBuilder s hs b) = do + toByteStringIO (Sock.sendAll socket) b' + return isKeepAlive + where + b' = + if isChunked' + then headers hv s hs True + `mappend` chunkedTransferEncoding b + `mappend` chunkedTransferTerminator + else headers hv s hs False `mappend` b + hasLength = lookup "content-length" hs /= Nothing + isChunked' = isChunked hv && not hasLength + isKeepAlive = isChunked' || hasLength sendResponse req hv socket (ResponseEnumerator res) = res go where diff --git a/UtilHelper.hs b/UtilHelper.hs index a8b8421d4..57e5f7801 100644 --- a/UtilHelper.hs +++ b/UtilHelper.hs @@ -8,8 +8,8 @@ mimeTypes' :: IO [(String, String)] mimeTypes' = do s <- readFile "mime.types" let go ('#':_) = Nothing - go s = - case words s of + go x = + case words x of [a, b] -> Just (b, a) _ -> Nothing let pairs = mapMaybe go $ lines s diff --git a/pong.hs b/pong.hs index 2e66f0337..47c594df7 100644 --- a/pong.hs +++ b/pong.hs @@ -3,7 +3,7 @@ import Network.Wai import Network.Wai.Handler.Warp import Blaze.ByteString.Builder (fromByteString) -main = run 3000 $ const $ return $ responseBuilder +main = run 3000 $ const $ return $ ResponseBuilder status200 [ ("Content-Type", "text/plain") , ("Content-Length", "4") From fe575867c5d3804faa52ce77918dfe65b6527e2c Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 7 Jan 2011 13:30:33 +0200 Subject: [PATCH 24/90] copyByteString --- Network/Wai/Handler/Warp.hs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 7833148a2..afa7d2622 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -54,7 +54,7 @@ import Blaze.ByteString.Builder.Enumerator (builderToByteString) import Blaze.ByteString.Builder.HTTP (chunkedTransferEncoding, chunkedTransferTerminator) import Blaze.ByteString.Builder - (fromByteString, Builder, toLazyByteString, toByteStringIO) + (copyByteString, Builder, toLazyByteString, toByteStringIO) import Blaze.ByteString.Builder.Char8 (fromChar, fromString) import Data.Monoid (mconcat, mappend) import Network.Socket.SendFile (sendFile) @@ -199,24 +199,24 @@ parseFirst s = do headers :: HttpVersion -> Status -> ResponseHeaders -> Bool -> Builder headers httpversion status responseHeaders isChunked' = mconcat - [ fromByteString "HTTP/" - , fromByteString httpversion + [ copyByteString "HTTP/" + , copyByteString httpversion , fromChar ' ' , fromString $ show $ statusCode status , fromChar ' ' - , fromByteString $ statusMessage status - , fromByteString "\r\n" + , copyByteString $ statusMessage status + , copyByteString "\r\n" , mconcat $ map go responseHeaders , if isChunked' - then fromByteString "Transfer-Encoding: chunked\r\n\r\n" - else fromByteString "\r\n" + then copyByteString "Transfer-Encoding: chunked\r\n\r\n" + else copyByteString "\r\n" ] where go (x, y) = mconcat - [ fromByteString $ ciOriginal x - , fromByteString ": " - , fromByteString y - , fromByteString "\r\n" + [ copyByteString $ ciOriginal x + , copyByteString ": " + , copyByteString y + , copyByteString "\r\n" ] isChunked :: HttpVersion -> Bool From 1c5837e4a00a8096423295a4828dd6b2deed19da Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 7 Jan 2011 13:32:47 +0200 Subject: [PATCH 25/90] blaze-builder bump --- warp.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/warp.cabal b/warp.cabal index 697fb360c..a309c9f47 100644 --- a/warp.cabal +++ b/warp.cabal @@ -24,7 +24,7 @@ Library , blaze-builder-enumerator >= 0.2 && < 0.3 , transformers >= 0.2 && < 0.3 , enumerator >= 0.4 && < 0.5 - , blaze-builder >= 0.2.1.3 && < 0.3 + , blaze-builder >= 0.2.1.4 && < 0.3 , sendfile >= 0.7.2 && < 0.8 , containers >= 0.2 && < 0.5 , template-haskell From 7078cc5633f19268ec6ca06763de67b1d856092c Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 7 Jan 2011 13:33:04 +0200 Subject: [PATCH 26/90] Added bigtable tests --- bigtable-single.hs | 22 ++++++++++++++++++++++ bigtable-stream.hs | 24 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 bigtable-single.hs create mode 100644 bigtable-stream.hs diff --git a/bigtable-single.hs b/bigtable-single.hs new file mode 100644 index 000000000..a834f6cef --- /dev/null +++ b/bigtable-single.hs @@ -0,0 +1,22 @@ +{-# LANGUAGE OverloadedStrings #-} +import Network.Wai +import Blaze.ByteString.Builder (Builder, fromByteString) +import Blaze.ByteString.Builder.Char8 (fromShow) +import Data.Monoid (mappend) +import Network.Wai.Handler.Warp (run) + +bigtable :: Builder +bigtable = + fromByteString "" + `mappend` foldr mappend (fromByteString "
") (replicate 2 row) + where + row = fromByteString "" + `mappend` foldr go (fromByteString "") [1..2] + go i rest = fromByteString "" + `mappend` fromShow i + `mappend` fromByteString "" + `mappend` rest + +main = run 3000 app + +app _ = return $ ResponseBuilder status200 [("Content-Type", "text/html")] bigtable diff --git a/bigtable-stream.hs b/bigtable-stream.hs new file mode 100644 index 000000000..bf9be1982 --- /dev/null +++ b/bigtable-stream.hs @@ -0,0 +1,24 @@ +{-# LANGUAGE OverloadedStrings #-} +import Network.Wai +import Blaze.ByteString.Builder (Builder, fromByteString) +import Blaze.ByteString.Builder.Char8 (fromShow) +import Data.Monoid (mappend) +import Network.Wai.Handler.Warp (run) +import Data.Enumerator (enumList, ($$), run_) + +bigtable :: [Builder] +bigtable = + fromByteString "" + : foldr (:) [fromByteString "
"] (replicate 2 row) + where + row = fromByteString "" + `mappend` foldr go (fromByteString "") [1..2] + go i rest = fromByteString "" + `mappend` fromShow i + `mappend` fromByteString "" + `mappend` rest + +main = run 3000 app + +app _ = return $ ResponseEnumerator $ \f -> + run_ $ enumList 4 bigtable $$ f status200 [("Content-Type", "text/html")] From 85d87f310d3f0e50eec6fb183a1cc313bd9d0ec7 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Tue, 11 Jan 2011 18:56:42 +0200 Subject: [PATCH 27/90] Using unix-compat for file sizes --- Network/Wai/Handler/Warp/Util.hs | 24 ++++-------------------- warp.cabal | 5 +---- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/Network/Wai/Handler/Warp/Util.hs b/Network/Wai/Handler/Warp/Util.hs index 6c15887c3..00151b2d7 100644 --- a/Network/Wai/Handler/Warp/Util.hs +++ b/Network/Wai/Handler/Warp/Util.hs @@ -10,27 +10,11 @@ import qualified Data.Map as Map import Data.ByteString (ByteString) import Language.Haskell.TH.Syntax (qRunIO, lift) import qualified Data.ByteString.Char8 as S8 +import System.PosixCompat.Files (getFileStatus, fileSize) +import System.Posix.Types (FileOffset) -#if defined(mingw32_HOST_OS) || defined(__MINGW32__) - -import System.Win32.File - -getFileSize :: FilePath -> IO Integer -getFileSize path = do - hnd <- createFile path - gENERIC_READ fILE_SHARE_READ Nothing oPEN_EXISTING 0 Nothing - size <- fmap bhfiSize $ getFileInformationByHandle hnd - closeHandle hnd - return $ fromIntegral size - -#else - -import System.Posix.Files - -getFileSize :: FilePath -> IO Integer -getFileSize = fmap (fromIntegral . fileSize) . getFileStatus - -#endif +getFileSize :: FilePath -> IO FileOffset +getFileSize = fmap fileSize . getFileStatus mimeTypes :: Map.Map ByteString ByteString mimeTypes = diff --git a/warp.cabal b/warp.cabal index a309c9f47..16cd9e32d 100644 --- a/warp.cabal +++ b/warp.cabal @@ -28,15 +28,12 @@ Library , sendfile >= 0.7.2 && < 0.8 , containers >= 0.2 && < 0.5 , template-haskell + , unix-compat >= 0.2 && < 0.3 if flag(network-bytestring) build-depends: network >= 2.2.1 && < 2.2.3 , network-bytestring >= 0.1.3 && < 0.1.4 else build-depends: network >= 2.3 && < 2.4 - if os(windows) - Build-Depends: Win32 >= 2.2 && < 2.3 - else - Build-Depends: unix >= 2.4 && < 2.5 Exposed-modules: Network.Wai.Handler.Warp Network.Wai.Handler.Warp.Util Other-modules: UtilHelper From 09a31b41337c6c718ba91761b2074fde8f9c47a3 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Tue, 11 Jan 2011 19:17:09 +0200 Subject: [PATCH 28/90] Removed warp executable and Util lib --- Network/Wai/Handler/Warp/Util.hs | 23 - UtilHelper.hs | 16 - mime.types | 1351 ------------------------------ warp.cabal | 11 - warp.hs | 71 -- 5 files changed, 1472 deletions(-) delete mode 100644 Network/Wai/Handler/Warp/Util.hs delete mode 100644 UtilHelper.hs delete mode 100644 mime.types delete mode 100644 warp.hs diff --git a/Network/Wai/Handler/Warp/Util.hs b/Network/Wai/Handler/Warp/Util.hs deleted file mode 100644 index 00151b2d7..000000000 --- a/Network/Wai/Handler/Warp/Util.hs +++ /dev/null @@ -1,23 +0,0 @@ -{-# LANGUAGE CPP #-} -{-# LANGUAGE TemplateHaskell #-} -module Network.Wai.Handler.Warp.Util - ( getFileSize - , mimeTypes - ) where - -import UtilHelper (mimeTypes') -import qualified Data.Map as Map -import Data.ByteString (ByteString) -import Language.Haskell.TH.Syntax (qRunIO, lift) -import qualified Data.ByteString.Char8 as S8 -import System.PosixCompat.Files (getFileStatus, fileSize) -import System.Posix.Types (FileOffset) - -getFileSize :: FilePath -> IO FileOffset -getFileSize = fmap fileSize . getFileStatus - -mimeTypes :: Map.Map ByteString ByteString -mimeTypes = - Map.fromAscList $ map go $(qRunIO mimeTypes' >>= lift) - where - go (x, y) = (S8.pack x, S8.pack y) diff --git a/UtilHelper.hs b/UtilHelper.hs deleted file mode 100644 index 57e5f7801..000000000 --- a/UtilHelper.hs +++ /dev/null @@ -1,16 +0,0 @@ -module UtilHelper where - -import Data.Maybe (mapMaybe) -import Data.List (sortBy) -import Data.Ord (comparing) - -mimeTypes' :: IO [(String, String)] -mimeTypes' = do - s <- readFile "mime.types" - let go ('#':_) = Nothing - go x = - case words x of - [a, b] -> Just (b, a) - _ -> Nothing - let pairs = mapMaybe go $ lines s - return $ sortBy (comparing fst) pairs diff --git a/mime.types b/mime.types deleted file mode 100644 index 1ae009729..000000000 --- a/mime.types +++ /dev/null @@ -1,1351 +0,0 @@ -# This file maps Internet media types to unique file extension(s). -# Although created for httpd, this file is used by many software systems -# and has been placed in the public domain for unlimited redisribution. -# -# The table below contains both registered and (common) unregistered types. -# A type that has no unique extension can be ignored -- they are listed -# here to guide configurations toward known types and to make it easier to -# identify "new" types. File extensions are also commonly used to indicate -# content languages and encodings, so choose them carefully. -# -# Internet media types should be registered as described in RFC 4288. -# The registry is at . -# -# MIME type Extensions -# application/3gpp-ims+xml -# application/activemessage -application/andrew-inset ez -# application/applefile -application/applixware aw -application/atom+xml atom -application/atomcat+xml atomcat -# application/atomicmail -application/atomsvc+xml atomsvc -# application/auth-policy+xml -# application/batch-smtp -# application/beep+xml -# application/cals-1840 -application/ccxml+xml ccxml -# application/cea-2018+xml -# application/cellml+xml -# application/cnrp+xml -# application/commonground -# application/conference-info+xml -# application/cpl+xml -# application/csta+xml -# application/cstadata+xml -application/cu-seeme cu -# application/cybercash -application/davmount+xml davmount -# application/dca-rft -# application/dec-dx -# application/dialog-info+xml -# application/dicom -# application/dns -application/dssc+der dssc -application/dssc+xml xdssc -# application/dvcs -application/ecmascript ecma -# application/edi-consent -# application/edi-x12 -# application/edifact -application/emma+xml emma -# application/epp+xml -application/epub+zip epub -# application/eshop -# application/example -# application/fastinfoset -# application/fastsoap -# application/fits -application/font-tdpfr pfr -# application/h224 -# application/held+xml -# application/http -application/hyperstudio stk -# application/ibe-key-request+xml -# application/ibe-pkg-reply+xml -# application/ibe-pp-data -# application/iges -# application/im-iscomposing+xml -# application/index -# application/index.cmd -# application/index.obj -# application/index.response -# application/index.vnd -# application/iotp -application/ipfix ipfix -# application/ipp -# application/isup -application/java-archive jar -application/java-serialized-object ser -application/java-vm class -application/javascript js -application/json json -# application/kpml-request+xml -# application/kpml-response+xml -application/lost+xml lostxml -application/mac-binhex40 hqx -application/mac-compactpro cpt -# application/macwriteii -application/marc mrc -application/mathematica ma nb mb -application/mathml+xml mathml -# application/mbms-associated-procedure-description+xml -# application/mbms-deregister+xml -# application/mbms-envelope+xml -# application/mbms-msk+xml -# application/mbms-msk-response+xml -# application/mbms-protection-description+xml -# application/mbms-reception-report+xml -# application/mbms-register+xml -# application/mbms-register-response+xml -# application/mbms-user-service-description+xml -application/mbox mbox -# application/media_control+xml -application/mediaservercontrol+xml mscml -# application/mikey -# application/moss-keys -# application/moss-signature -# application/mosskey-data -# application/mosskey-request -application/mp4 mp4s -# application/mpeg4-generic -# application/mpeg4-iod -# application/mpeg4-iod-xmt -application/msword doc dot -application/mxf mxf -# application/nasdata -# application/news-checkgroups -# application/news-groupinfo -# application/news-transmission -# application/nss -# application/ocsp-request -# application/ocsp-response -application/octet-stream bin dms lha lrf lzh so iso dmg dist distz pkg bpk dump elc deploy -application/oda oda -application/oebps-package+xml opf -application/ogg ogx -application/onenote onetoc onetoc2 onetmp onepkg -# application/parityfec -application/patch-ops-error+xml xer -application/pdf pdf -application/pgp-encrypted pgp -# application/pgp-keys -application/pgp-signature asc sig -application/pics-rules prf -# application/pidf+xml -# application/pidf-diff+xml -application/pkcs10 p10 -application/pkcs7-mime p7m p7c -application/pkcs7-signature p7s -application/pkix-cert cer -application/pkix-crl crl -application/pkix-pkipath pkipath -application/pkixcmp pki -application/pls+xml pls -# application/poc-settings+xml -application/postscript ai eps ps -# application/prs.alvestrand.titrax-sheet -application/prs.cww cww -# application/prs.nprend -# application/prs.plucker -# application/qsig -application/rdf+xml rdf -application/reginfo+xml rif -application/relax-ng-compact-syntax rnc -# application/remote-printing -application/resource-lists+xml rl -application/resource-lists-diff+xml rld -# application/riscos -# application/rlmi+xml -application/rls-services+xml rs -application/rsd+xml rsd -application/rss+xml rss -application/rtf rtf -# application/rtx -# application/samlassertion+xml -# application/samlmetadata+xml -application/sbml+xml sbml -application/scvp-cv-request scq -application/scvp-cv-response scs -application/scvp-vp-request spq -application/scvp-vp-response spp -application/sdp sdp -# application/set-payment -application/set-payment-initiation setpay -# application/set-registration -application/set-registration-initiation setreg -# application/sgml -# application/sgml-open-catalog -application/shf+xml shf -# application/sieve -# application/simple-filter+xml -# application/simple-message-summary -# application/simplesymbolcontainer -# application/slate -# application/smil -application/smil+xml smi smil -# application/soap+fastinfoset -# application/soap+xml -application/sparql-query rq -application/sparql-results+xml srx -# application/spirits-event+xml -application/srgs gram -application/srgs+xml grxml -application/ssml+xml ssml -# application/timestamp-query -# application/timestamp-reply -# application/tve-trigger -# application/ulpfec -# application/vemmi -# application/vividence.scriptfile -# application/vnd.3gpp.bsf+xml -application/vnd.3gpp.pic-bw-large plb -application/vnd.3gpp.pic-bw-small psb -application/vnd.3gpp.pic-bw-var pvb -# application/vnd.3gpp.sms -# application/vnd.3gpp2.bcmcsinfo+xml -# application/vnd.3gpp2.sms -application/vnd.3gpp2.tcap tcap -application/vnd.3m.post-it-notes pwn -application/vnd.accpac.simply.aso aso -application/vnd.accpac.simply.imp imp -application/vnd.acucobol acu -application/vnd.acucorp atc acutc -application/vnd.adobe.air-application-installer-package+zip air -# application/vnd.adobe.partial-upload -application/vnd.adobe.xdp+xml xdp -application/vnd.adobe.xfdf xfdf -# application/vnd.aether.imp -application/vnd.airzip.filesecure.azf azf -application/vnd.airzip.filesecure.azs azs -application/vnd.amazon.ebook azw -application/vnd.americandynamics.acc acc -application/vnd.amiga.ami ami -application/vnd.android.package-archive apk -application/vnd.anser-web-certificate-issue-initiation cii -application/vnd.anser-web-funds-transfer-initiation fti -application/vnd.antix.game-component atx -application/vnd.apple.installer+xml mpkg -application/vnd.apple.mpegurl m3u8 -# application/vnd.arastra.swi -application/vnd.aristanetworks.swi swi -application/vnd.audiograph aep -# application/vnd.autopackage -# application/vnd.avistar+xml -application/vnd.blueice.multipass mpm -# application/vnd.bluetooth.ep.oob -application/vnd.bmi bmi -application/vnd.businessobjects rep -# application/vnd.cab-jscript -# application/vnd.canon-cpdl -# application/vnd.canon-lips -# application/vnd.cendio.thinlinc.clientconf -application/vnd.chemdraw+xml cdxml -application/vnd.chipnuts.karaoke-mmd mmd -application/vnd.cinderella cdy -# application/vnd.cirpack.isdn-ext -application/vnd.claymore cla -application/vnd.cloanto.rp9 rp9 -application/vnd.clonk.c4group c4g c4d c4f c4p c4u -# application/vnd.commerce-battelle -application/vnd.commonspace csp -application/vnd.contact.cmsg cdbcmsg -application/vnd.cosmocaller cmc -application/vnd.crick.clicker clkx -application/vnd.crick.clicker.keyboard clkk -application/vnd.crick.clicker.palette clkp -application/vnd.crick.clicker.template clkt -application/vnd.crick.clicker.wordbank clkw -application/vnd.criticaltools.wbs+xml wbs -application/vnd.ctc-posml pml -# application/vnd.ctct.ws+xml -# application/vnd.cups-pdf -# application/vnd.cups-postscript -application/vnd.cups-ppd ppd -# application/vnd.cups-raster -# application/vnd.cups-raw -application/vnd.curl.car car -application/vnd.curl.pcurl pcurl -# application/vnd.cybank -application/vnd.data-vision.rdz rdz -application/vnd.denovo.fcselayout-link fe_launch -# application/vnd.dir-bi.plate-dl-nosuffix -application/vnd.dna dna -application/vnd.dolby.mlp mlp -# application/vnd.dolby.mobile.1 -# application/vnd.dolby.mobile.2 -application/vnd.dpgraph dpg -application/vnd.dreamfactory dfac -# application/vnd.dvb.esgcontainer -# application/vnd.dvb.ipdcdftnotifaccess -# application/vnd.dvb.ipdcesgaccess -# application/vnd.dvb.ipdcroaming -# application/vnd.dvb.iptv.alfec-base -# application/vnd.dvb.iptv.alfec-enhancement -# application/vnd.dvb.notif-aggregate-root+xml -# application/vnd.dvb.notif-container+xml -# application/vnd.dvb.notif-generic+xml -# application/vnd.dvb.notif-ia-msglist+xml -# application/vnd.dvb.notif-ia-registration-request+xml -# application/vnd.dvb.notif-ia-registration-response+xml -# application/vnd.dvb.notif-init+xml -# application/vnd.dxr -application/vnd.dynageo geo -# application/vnd.ecdis-update -application/vnd.ecowin.chart mag -# application/vnd.ecowin.filerequest -# application/vnd.ecowin.fileupdate -# application/vnd.ecowin.series -# application/vnd.ecowin.seriesrequest -# application/vnd.ecowin.seriesupdate -# application/vnd.emclient.accessrequest+xml -application/vnd.enliven nml -application/vnd.epson.esf esf -application/vnd.epson.msf msf -application/vnd.epson.quickanime qam -application/vnd.epson.salt slt -application/vnd.epson.ssf ssf -# application/vnd.ericsson.quickcall -application/vnd.eszigno3+xml es3 et3 -# application/vnd.etsi.aoc+xml -# application/vnd.etsi.cug+xml -# application/vnd.etsi.iptvcommand+xml -# application/vnd.etsi.iptvdiscovery+xml -# application/vnd.etsi.iptvprofile+xml -# application/vnd.etsi.iptvsad-bc+xml -# application/vnd.etsi.iptvsad-cod+xml -# application/vnd.etsi.iptvsad-npvr+xml -# application/vnd.etsi.iptvueprofile+xml -# application/vnd.etsi.mcid+xml -# application/vnd.etsi.sci+xml -# application/vnd.etsi.simservs+xml -# application/vnd.etsi.tsl+xml -# application/vnd.etsi.tsl.der -# application/vnd.eudora.data -application/vnd.ezpix-album ez2 -application/vnd.ezpix-package ez3 -# application/vnd.f-secure.mobile -application/vnd.fdf fdf -application/vnd.fdsn.mseed mseed -application/vnd.fdsn.seed seed dataless -# application/vnd.ffsns -# application/vnd.fints -application/vnd.flographit gph -application/vnd.fluxtime.clip ftc -# application/vnd.font-fontforge-sfd -application/vnd.framemaker fm frame maker book -application/vnd.frogans.fnc fnc -application/vnd.frogans.ltf ltf -application/vnd.fsc.weblaunch fsc -application/vnd.fujitsu.oasys oas -application/vnd.fujitsu.oasys2 oa2 -application/vnd.fujitsu.oasys3 oa3 -application/vnd.fujitsu.oasysgp fg5 -application/vnd.fujitsu.oasysprs bh2 -# application/vnd.fujixerox.art-ex -# application/vnd.fujixerox.art4 -# application/vnd.fujixerox.hbpl -application/vnd.fujixerox.ddd ddd -application/vnd.fujixerox.docuworks xdw -application/vnd.fujixerox.docuworks.binder xbd -# application/vnd.fut-misnet -application/vnd.fuzzysheet fzs -application/vnd.genomatix.tuxedo txd -# application/vnd.geocube+xml -application/vnd.geogebra.file ggb -application/vnd.geogebra.tool ggt -application/vnd.geometry-explorer gex gre -application/vnd.geonext gxt -application/vnd.geoplan g2w -application/vnd.geospace g3w -# application/vnd.globalplatform.card-content-mgt -# application/vnd.globalplatform.card-content-mgt-response -application/vnd.gmx gmx -application/vnd.google-earth.kml+xml kml -application/vnd.google-earth.kmz kmz -application/vnd.grafeq gqf gqs -# application/vnd.gridmp -application/vnd.groove-account gac -application/vnd.groove-help ghf -application/vnd.groove-identity-message gim -application/vnd.groove-injector grv -application/vnd.groove-tool-message gtm -application/vnd.groove-tool-template tpl -application/vnd.groove-vcard vcg -application/vnd.handheld-entertainment+xml zmm -application/vnd.hbci hbci -# application/vnd.hcl-bireports -application/vnd.hhe.lesson-player les -application/vnd.hp-hpgl hpgl -application/vnd.hp-hpid hpid -application/vnd.hp-hps hps -application/vnd.hp-jlyt jlt -application/vnd.hp-pcl pcl -application/vnd.hp-pclxl pclxl -# application/vnd.httphone -application/vnd.hydrostatix.sof-data sfd-hdstx -application/vnd.hzn-3d-crossword x3d -# application/vnd.ibm.afplinedata -# application/vnd.ibm.electronic-media -application/vnd.ibm.minipay mpy -application/vnd.ibm.modcap afp listafp list3820 -application/vnd.ibm.rights-management irm -application/vnd.ibm.secure-container sc -application/vnd.iccprofile icc icm -application/vnd.igloader igl -application/vnd.immervision-ivp ivp -application/vnd.immervision-ivu ivu -# application/vnd.informedcontrol.rms+xml -# application/vnd.informix-visionary -application/vnd.intercon.formnet xpw xpx -# application/vnd.intertrust.digibox -# application/vnd.intertrust.nncp -application/vnd.intu.qbo qbo -application/vnd.intu.qfx qfx -# application/vnd.iptc.g2.conceptitem+xml -# application/vnd.iptc.g2.knowledgeitem+xml -# application/vnd.iptc.g2.newsitem+xml -# application/vnd.iptc.g2.packageitem+xml -application/vnd.ipunplugged.rcprofile rcprofile -application/vnd.irepository.package+xml irp -application/vnd.is-xpr xpr -application/vnd.jam jam -# application/vnd.japannet-directory-service -# application/vnd.japannet-jpnstore-wakeup -# application/vnd.japannet-payment-wakeup -# application/vnd.japannet-registration -# application/vnd.japannet-registration-wakeup -# application/vnd.japannet-setstore-wakeup -# application/vnd.japannet-verification -# application/vnd.japannet-verification-wakeup -application/vnd.jcp.javame.midlet-rms rms -application/vnd.jisp jisp -application/vnd.joost.joda-archive joda -application/vnd.kahootz ktz ktr -application/vnd.kde.karbon karbon -application/vnd.kde.kchart chrt -application/vnd.kde.kformula kfo -application/vnd.kde.kivio flw -application/vnd.kde.kontour kon -application/vnd.kde.kpresenter kpr kpt -application/vnd.kde.kspread ksp -application/vnd.kde.kword kwd kwt -application/vnd.kenameaapp htke -application/vnd.kidspiration kia -application/vnd.kinar kne knp -application/vnd.koan skp skd skt skm -application/vnd.kodak-descriptor sse -# application/vnd.liberty-request+xml -application/vnd.llamagraphics.life-balance.desktop lbd -application/vnd.llamagraphics.life-balance.exchange+xml lbe -application/vnd.lotus-1-2-3 123 -application/vnd.lotus-approach apr -application/vnd.lotus-freelance pre -application/vnd.lotus-notes nsf -application/vnd.lotus-organizer org -application/vnd.lotus-screencam scm -application/vnd.lotus-wordpro lwp -application/vnd.macports.portpkg portpkg -# application/vnd.marlin.drm.actiontoken+xml -# application/vnd.marlin.drm.conftoken+xml -# application/vnd.marlin.drm.license+xml -# application/vnd.marlin.drm.mdcf -application/vnd.mcd mcd -application/vnd.medcalcdata mc1 -application/vnd.mediastation.cdkey cdkey -# application/vnd.meridian-slingshot -application/vnd.mfer mwf -application/vnd.mfmp mfm -application/vnd.micrografx.flo flo -application/vnd.micrografx.igx igx -application/vnd.mif mif -# application/vnd.minisoft-hp3000-save -# application/vnd.mitsubishi.misty-guard.trustweb -application/vnd.mobius.daf daf -application/vnd.mobius.dis dis -application/vnd.mobius.mbk mbk -application/vnd.mobius.mqy mqy -application/vnd.mobius.msl msl -application/vnd.mobius.plc plc -application/vnd.mobius.txf txf -application/vnd.mophun.application mpn -application/vnd.mophun.certificate mpc -# application/vnd.motorola.flexsuite -# application/vnd.motorola.flexsuite.adsi -# application/vnd.motorola.flexsuite.fis -# application/vnd.motorola.flexsuite.gotap -# application/vnd.motorola.flexsuite.kmr -# application/vnd.motorola.flexsuite.ttc -# application/vnd.motorola.flexsuite.wem -# application/vnd.motorola.iprm -application/vnd.mozilla.xul+xml xul -application/vnd.ms-artgalry cil -# application/vnd.ms-asf -application/vnd.ms-cab-compressed cab -application/vnd.ms-excel xls xlm xla xlc xlt xlw -application/vnd.ms-excel.addin.macroenabled.12 xlam -application/vnd.ms-excel.sheet.binary.macroenabled.12 xlsb -application/vnd.ms-excel.sheet.macroenabled.12 xlsm -application/vnd.ms-excel.template.macroenabled.12 xltm -application/vnd.ms-fontobject eot -application/vnd.ms-htmlhelp chm -application/vnd.ms-ims ims -application/vnd.ms-lrm lrm -application/vnd.ms-pki.seccat cat -application/vnd.ms-pki.stl stl -# application/vnd.ms-playready.initiator+xml -application/vnd.ms-powerpoint ppt pps pot -application/vnd.ms-powerpoint.addin.macroenabled.12 ppam -application/vnd.ms-powerpoint.presentation.macroenabled.12 pptm -application/vnd.ms-powerpoint.slide.macroenabled.12 sldm -application/vnd.ms-powerpoint.slideshow.macroenabled.12 ppsm -application/vnd.ms-powerpoint.template.macroenabled.12 potm -application/vnd.ms-project mpp mpt -# application/vnd.ms-tnef -# application/vnd.ms-wmdrm.lic-chlg-req -# application/vnd.ms-wmdrm.lic-resp -# application/vnd.ms-wmdrm.meter-chlg-req -# application/vnd.ms-wmdrm.meter-resp -application/vnd.ms-word.document.macroenabled.12 docm -application/vnd.ms-word.template.macroenabled.12 dotm -application/vnd.ms-works wps wks wcm wdb -application/vnd.ms-wpl wpl -application/vnd.ms-xpsdocument xps -application/vnd.mseq mseq -# application/vnd.msign -# application/vnd.multiad.creator -# application/vnd.multiad.creator.cif -# application/vnd.music-niff -application/vnd.musician mus -application/vnd.muvee.style msty -# application/vnd.ncd.control -# application/vnd.ncd.reference -# application/vnd.nervana -# application/vnd.netfpx -application/vnd.neurolanguage.nlu nlu -application/vnd.noblenet-directory nnd -application/vnd.noblenet-sealer nns -application/vnd.noblenet-web nnw -# application/vnd.nokia.catalogs -# application/vnd.nokia.conml+wbxml -# application/vnd.nokia.conml+xml -# application/vnd.nokia.isds-radio-presets -# application/vnd.nokia.iptv.config+xml -# application/vnd.nokia.landmark+wbxml -# application/vnd.nokia.landmark+xml -# application/vnd.nokia.landmarkcollection+xml -# application/vnd.nokia.n-gage.ac+xml -application/vnd.nokia.n-gage.data ngdat -application/vnd.nokia.n-gage.symbian.install n-gage -# application/vnd.nokia.ncd -# application/vnd.nokia.pcd+wbxml -# application/vnd.nokia.pcd+xml -application/vnd.nokia.radio-preset rpst -application/vnd.nokia.radio-presets rpss -application/vnd.novadigm.edm edm -application/vnd.novadigm.edx edx -application/vnd.novadigm.ext ext -# application/vnd.ntt-local.file-transfer -application/vnd.oasis.opendocument.chart odc -application/vnd.oasis.opendocument.chart-template otc -application/vnd.oasis.opendocument.database odb -application/vnd.oasis.opendocument.formula odf -application/vnd.oasis.opendocument.formula-template odft -application/vnd.oasis.opendocument.graphics odg -application/vnd.oasis.opendocument.graphics-template otg -application/vnd.oasis.opendocument.image odi -application/vnd.oasis.opendocument.image-template oti -application/vnd.oasis.opendocument.presentation odp -application/vnd.oasis.opendocument.presentation-template otp -application/vnd.oasis.opendocument.spreadsheet ods -application/vnd.oasis.opendocument.spreadsheet-template ots -application/vnd.oasis.opendocument.text odt -application/vnd.oasis.opendocument.text-master otm -application/vnd.oasis.opendocument.text-template ott -application/vnd.oasis.opendocument.text-web oth -# application/vnd.obn -application/vnd.olpc-sugar xo -# application/vnd.oma-scws-config -# application/vnd.oma-scws-http-request -# application/vnd.oma-scws-http-response -# application/vnd.oma.bcast.associated-procedure-parameter+xml -# application/vnd.oma.bcast.drm-trigger+xml -# application/vnd.oma.bcast.imd+xml -# application/vnd.oma.bcast.ltkm -# application/vnd.oma.bcast.notification+xml -# application/vnd.oma.bcast.provisioningtrigger -# application/vnd.oma.bcast.sgboot -# application/vnd.oma.bcast.sgdd+xml -# application/vnd.oma.bcast.sgdu -# application/vnd.oma.bcast.simple-symbol-container -# application/vnd.oma.bcast.smartcard-trigger+xml -# application/vnd.oma.bcast.sprov+xml -# application/vnd.oma.bcast.stkm -# application/vnd.oma.dcd -# application/vnd.oma.dcdc -application/vnd.oma.dd2+xml dd2 -# application/vnd.oma.drm.risd+xml -# application/vnd.oma.group-usage-list+xml -# application/vnd.oma.poc.detailed-progress-report+xml -# application/vnd.oma.poc.final-report+xml -# application/vnd.oma.poc.groups+xml -# application/vnd.oma.poc.invocation-descriptor+xml -# application/vnd.oma.poc.optimized-progress-report+xml -# application/vnd.oma.push -# application/vnd.oma.scidm.messages+xml -# application/vnd.oma.xcap-directory+xml -# application/vnd.omads-email+xml -# application/vnd.omads-file+xml -# application/vnd.omads-folder+xml -# application/vnd.omaloc-supl-init -application/vnd.openofficeorg.extension oxt -# application/vnd.openxmlformats-officedocument.custom-properties+xml -# application/vnd.openxmlformats-officedocument.customxmlproperties+xml -# application/vnd.openxmlformats-officedocument.drawing+xml -# application/vnd.openxmlformats-officedocument.drawingml.chart+xml -# application/vnd.openxmlformats-officedocument.drawingml.chartshapes+xml -# application/vnd.openxmlformats-officedocument.drawingml.diagramcolors+xml -# application/vnd.openxmlformats-officedocument.drawingml.diagramdata+xml -# application/vnd.openxmlformats-officedocument.drawingml.diagramlayout+xml -# application/vnd.openxmlformats-officedocument.drawingml.diagramstyle+xml -# application/vnd.openxmlformats-officedocument.extended-properties+xml -# application/vnd.openxmlformats-officedocument.presentationml.commentauthors+xml -# application/vnd.openxmlformats-officedocument.presentationml.comments+xml -# application/vnd.openxmlformats-officedocument.presentationml.handoutmaster+xml -# application/vnd.openxmlformats-officedocument.presentationml.notesmaster+xml -# application/vnd.openxmlformats-officedocument.presentationml.notesslide+xml -application/vnd.openxmlformats-officedocument.presentationml.presentation pptx -# application/vnd.openxmlformats-officedocument.presentationml.presentation.main+xml -# application/vnd.openxmlformats-officedocument.presentationml.presprops+xml -application/vnd.openxmlformats-officedocument.presentationml.slide sldx -# application/vnd.openxmlformats-officedocument.presentationml.slide+xml -# application/vnd.openxmlformats-officedocument.presentationml.slidelayout+xml -# application/vnd.openxmlformats-officedocument.presentationml.slidemaster+xml -application/vnd.openxmlformats-officedocument.presentationml.slideshow ppsx -# application/vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml -# application/vnd.openxmlformats-officedocument.presentationml.slideupdateinfo+xml -# application/vnd.openxmlformats-officedocument.presentationml.tablestyles+xml -# application/vnd.openxmlformats-officedocument.presentationml.tags+xml -application/vnd.openxmlformats-officedocument.presentationml.template potx -# application/vnd.openxmlformats-officedocument.presentationml.template.main+xml -# application/vnd.openxmlformats-officedocument.presentationml.viewprops+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.calcchain+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.connections+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.externallink+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcachedefinition+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.pivotcacherecords+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.pivottable+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.querytable+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.revisionheaders+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.revisionlog+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.sharedstrings+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx -# application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.sheetmetadata+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.tablesinglecells+xml -application/vnd.openxmlformats-officedocument.spreadsheetml.template xltx -# application/vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.usernames+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.volatiledependencies+xml -# application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml -# application/vnd.openxmlformats-officedocument.theme+xml -# application/vnd.openxmlformats-officedocument.themeoverride+xml -# application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml -application/vnd.openxmlformats-officedocument.wordprocessingml.document docx -# application/vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml -# application/vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml -# application/vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml -# application/vnd.openxmlformats-officedocument.wordprocessingml.fonttable+xml -# application/vnd.openxmlformats-officedocument.wordprocessingml.footer+xml -# application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml -# application/vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml -# application/vnd.openxmlformats-officedocument.wordprocessingml.settings+xml -# application/vnd.openxmlformats-officedocument.wordprocessingml.styles+xml -application/vnd.openxmlformats-officedocument.wordprocessingml.template dotx -# application/vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml -# application/vnd.openxmlformats-officedocument.wordprocessingml.websettings+xml -# application/vnd.openxmlformats-package.core-properties+xml -# application/vnd.openxmlformats-package.digital-signature-xmlsignature+xml -# application/vnd.osa.netdeploy -# application/vnd.osgi.bundle -application/vnd.osgi.dp dp -# application/vnd.otps.ct-kip+xml -application/vnd.palm pdb pqa oprc -# application/vnd.paos.xml -application/vnd.pawaafile paw -application/vnd.pg.format str -application/vnd.pg.osasli ei6 -# application/vnd.piaccess.application-licence -application/vnd.picsel efif -application/vnd.pmi.widget wg -# application/vnd.poc.group-advertisement+xml -application/vnd.pocketlearn plf -application/vnd.powerbuilder6 pbd -# application/vnd.powerbuilder6-s -# application/vnd.powerbuilder7 -# application/vnd.powerbuilder7-s -# application/vnd.powerbuilder75 -# application/vnd.powerbuilder75-s -# application/vnd.preminet -application/vnd.previewsystems.box box -application/vnd.proteus.magazine mgz -application/vnd.publishare-delta-tree qps -application/vnd.pvi.ptid1 ptid -# application/vnd.pwg-multiplexed -# application/vnd.pwg-xhtml-print+xml -# application/vnd.qualcomm.brew-app-res -application/vnd.quark.quarkxpress qxd qxt qwd qwt qxl qxb -# application/vnd.radisys.moml+xml -# application/vnd.radisys.msml+xml -# application/vnd.radisys.msml-audit+xml -# application/vnd.radisys.msml-audit-conf+xml -# application/vnd.radisys.msml-audit-conn+xml -# application/vnd.radisys.msml-audit-dialog+xml -# application/vnd.radisys.msml-audit-stream+xml -# application/vnd.radisys.msml-conf+xml -# application/vnd.radisys.msml-dialog+xml -# application/vnd.radisys.msml-dialog-base+xml -# application/vnd.radisys.msml-dialog-fax-detect+xml -# application/vnd.radisys.msml-dialog-fax-sendrecv+xml -# application/vnd.radisys.msml-dialog-group+xml -# application/vnd.radisys.msml-dialog-speech+xml -# application/vnd.radisys.msml-dialog-transform+xml -# application/vnd.rapid -application/vnd.realvnc.bed bed -application/vnd.recordare.musicxml mxl -application/vnd.recordare.musicxml+xml musicxml -# application/vnd.renlearn.rlprint -application/vnd.rim.cod cod -application/vnd.rn-realmedia rm -application/vnd.route66.link66+xml link66 -# application/vnd.ruckus.download -# application/vnd.s3sms -application/vnd.sailingtracker.track st -# application/vnd.sbm.cid -# application/vnd.sbm.mid2 -# application/vnd.scribus -# application/vnd.sealed.3df -# application/vnd.sealed.csf -# application/vnd.sealed.doc -# application/vnd.sealed.eml -# application/vnd.sealed.mht -# application/vnd.sealed.net -# application/vnd.sealed.ppt -# application/vnd.sealed.tiff -# application/vnd.sealed.xls -# application/vnd.sealedmedia.softseal.html -# application/vnd.sealedmedia.softseal.pdf -application/vnd.seemail see -application/vnd.sema sema -application/vnd.semd semd -application/vnd.semf semf -application/vnd.shana.informed.formdata ifm -application/vnd.shana.informed.formtemplate itp -application/vnd.shana.informed.interchange iif -application/vnd.shana.informed.package ipk -application/vnd.simtech-mindmapper twd twds -application/vnd.smaf mmf -# application/vnd.smart.notebook -application/vnd.smart.teacher teacher -# application/vnd.software602.filler.form+xml -# application/vnd.software602.filler.form-xml-zip -application/vnd.solent.sdkm+xml sdkm sdkd -application/vnd.spotfire.dxp dxp -application/vnd.spotfire.sfs sfs -# application/vnd.sss-cod -# application/vnd.sss-dtf -# application/vnd.sss-ntf -application/vnd.stardivision.calc sdc -application/vnd.stardivision.draw sda -application/vnd.stardivision.impress sdd -application/vnd.stardivision.math smf -application/vnd.stardivision.writer sdw -application/vnd.stardivision.writer vor -application/vnd.stardivision.writer-global sgl -# application/vnd.street-stream -application/vnd.sun.xml.calc sxc -application/vnd.sun.xml.calc.template stc -application/vnd.sun.xml.draw sxd -application/vnd.sun.xml.draw.template std -application/vnd.sun.xml.impress sxi -application/vnd.sun.xml.impress.template sti -application/vnd.sun.xml.math sxm -application/vnd.sun.xml.writer sxw -application/vnd.sun.xml.writer.global sxg -application/vnd.sun.xml.writer.template stw -# application/vnd.sun.wadl+xml -application/vnd.sus-calendar sus susp -application/vnd.svd svd -# application/vnd.swiftview-ics -application/vnd.symbian.install sis sisx -application/vnd.syncml+xml xsm -application/vnd.syncml.dm+wbxml bdm -application/vnd.syncml.dm+xml xdm -# application/vnd.syncml.dm.notification -# application/vnd.syncml.ds.notification -application/vnd.tao.intent-module-archive tao -application/vnd.tmobile-livetv tmo -application/vnd.trid.tpt tpt -application/vnd.triscape.mxs mxs -application/vnd.trueapp tra -# application/vnd.truedoc -application/vnd.ufdl ufd ufdl -application/vnd.uiq.theme utz -application/vnd.umajin umj -application/vnd.unity unityweb -application/vnd.uoml+xml uoml -# application/vnd.uplanet.alert -# application/vnd.uplanet.alert-wbxml -# application/vnd.uplanet.bearer-choice -# application/vnd.uplanet.bearer-choice-wbxml -# application/vnd.uplanet.cacheop -# application/vnd.uplanet.cacheop-wbxml -# application/vnd.uplanet.channel -# application/vnd.uplanet.channel-wbxml -# application/vnd.uplanet.list -# application/vnd.uplanet.list-wbxml -# application/vnd.uplanet.listcmd -# application/vnd.uplanet.listcmd-wbxml -# application/vnd.uplanet.signal -application/vnd.vcx vcx -# application/vnd.vd-study -# application/vnd.vectorworks -# application/vnd.vidsoft.vidconference -application/vnd.visio vsd vst vss vsw -application/vnd.visionary vis -# application/vnd.vividence.scriptfile -application/vnd.vsf vsf -# application/vnd.wap.sic -# application/vnd.wap.slc -application/vnd.wap.wbxml wbxml -application/vnd.wap.wmlc wmlc -application/vnd.wap.wmlscriptc wmlsc -application/vnd.webturbo wtb -# application/vnd.wfa.wsc -# application/vnd.wmc -# application/vnd.wmf.bootstrap -# application/vnd.wolfram.mathematica -# application/vnd.wolfram.mathematica.package -application/vnd.wolfram.player nbp -application/vnd.wordperfect wpd -application/vnd.wqd wqd -# application/vnd.wrq-hp3000-labelled -application/vnd.wt.stf stf -# application/vnd.wv.csp+wbxml -# application/vnd.wv.csp+xml -# application/vnd.wv.ssp+xml -application/vnd.xara xar -application/vnd.xfdl xfdl -# application/vnd.xfdl.webform -# application/vnd.xmi+xml -# application/vnd.xmpie.cpkg -# application/vnd.xmpie.dpkg -# application/vnd.xmpie.plan -# application/vnd.xmpie.ppkg -# application/vnd.xmpie.xlim -application/vnd.yamaha.hv-dic hvd -application/vnd.yamaha.hv-script hvs -application/vnd.yamaha.hv-voice hvp -application/vnd.yamaha.openscoreformat osf -application/vnd.yamaha.openscoreformat.osfpvg+xml osfpvg -application/vnd.yamaha.smaf-audio saf -application/vnd.yamaha.smaf-phrase spf -application/vnd.yellowriver-custom-menu cmp -application/vnd.zul zir zirz -application/vnd.zzazz.deck+xml zaz -application/voicexml+xml vxml -# application/watcherinfo+xml -# application/whoispp-query -# application/whoispp-response -application/winhlp hlp -# application/wita -# application/wordperfect5.1 -application/wsdl+xml wsdl -application/wspolicy+xml wspolicy -application/x-abiword abw -application/x-ace-compressed ace -application/x-authorware-bin aab x32 u32 vox -application/x-authorware-map aam -application/x-authorware-seg aas -application/x-bcpio bcpio -application/x-bittorrent torrent -application/x-bzip bz -application/x-bzip2 bz2 boz -application/x-cdlink vcd -application/x-chat chat -application/x-chess-pgn pgn -# application/x-compress -application/x-cpio cpio -application/x-csh csh -application/x-debian-package deb udeb -application/x-director dir dcr dxr cst cct cxt w3d fgd swa -application/x-doom wad -application/x-dtbncx+xml ncx -application/x-dtbook+xml dtb -application/x-dtbresource+xml res -application/x-dvi dvi -application/x-font-bdf bdf -# application/x-font-dos -# application/x-font-framemaker -application/x-font-ghostscript gsf -# application/x-font-libgrx -application/x-font-linux-psf psf -application/x-font-otf otf -application/x-font-pcf pcf -application/x-font-snf snf -# application/x-font-speedo -# application/x-font-sunos-news -application/x-font-ttf ttf ttc -application/x-font-type1 pfa pfb pfm afm -# application/x-font-vfont -application/x-futuresplash spl -application/x-gnumeric gnumeric -application/x-gtar gtar -# application/x-gzip -application/x-hdf hdf -application/x-java-jnlp-file jnlp -application/x-latex latex -application/x-mobipocket-ebook prc mobi -application/x-ms-application application -application/x-ms-wmd wmd -application/x-ms-wmz wmz -application/x-ms-xbap xbap -application/x-msaccess mdb -application/x-msbinder obd -application/x-mscardfile crd -application/x-msclip clp -application/x-msdownload exe dll com bat msi -application/x-msmediaview mvb m13 m14 -application/x-msmetafile wmf -application/x-msmoney mny -application/x-mspublisher pub -application/x-msschedule scd -application/x-msterminal trm -application/x-mswrite wri -application/x-netcdf nc cdf -application/x-pkcs12 p12 pfx -application/x-pkcs7-certificates p7b spc -application/x-pkcs7-certreqresp p7r -application/x-rar-compressed rar -application/x-sh sh -application/x-shar shar -application/x-shockwave-flash swf -application/x-silverlight-app xap -application/x-stuffit sit -application/x-stuffitx sitx -application/x-sv4cpio sv4cpio -application/x-sv4crc sv4crc -application/x-tar tar -application/x-tcl tcl -application/x-tex tex -application/x-tex-tfm tfm -application/x-texinfo texinfo texi -application/x-ustar ustar -application/x-wais-source src -application/x-x509-ca-cert der crt -application/x-xfig fig -application/x-xpinstall xpi -# application/x400-bp -# application/xcap-att+xml -# application/xcap-caps+xml -# application/xcap-el+xml -# application/xcap-error+xml -# application/xcap-ns+xml -# application/xcon-conference-info-diff+xml -# application/xcon-conference-info+xml -application/xenc+xml xenc -application/xhtml+xml xhtml xht -# application/xhtml-voice+xml -application/xml xml xsl -application/xml-dtd dtd -# application/xml-external-parsed-entity -# application/xmpp+xml -application/xop+xml xop -application/xslt+xml xslt -application/xspf+xml xspf -application/xv+xml mxml xhvml xvml xvm -application/zip zip -# audio/32kadpcm -# audio/3gpp -# audio/3gpp2 -# audio/ac3 -audio/adpcm adp -# audio/amr -# audio/amr-wb -# audio/amr-wb+ -# audio/asc -# audio/atrac-advanced-lossless -# audio/atrac-x -# audio/atrac3 -audio/basic au snd -# audio/bv16 -# audio/bv32 -# audio/clearmode -# audio/cn -# audio/dat12 -# audio/dls -# audio/dsr-es201108 -# audio/dsr-es202050 -# audio/dsr-es202211 -# audio/dsr-es202212 -# audio/dvi4 -# audio/eac3 -# audio/evrc -# audio/evrc-qcp -# audio/evrc0 -# audio/evrc1 -# audio/evrcb -# audio/evrcb0 -# audio/evrcb1 -# audio/evrcwb -# audio/evrcwb0 -# audio/evrcwb1 -# audio/example -# audio/g719 -# audio/g722 -# audio/g7221 -# audio/g723 -# audio/g726-16 -# audio/g726-24 -# audio/g726-32 -# audio/g726-40 -# audio/g728 -# audio/g729 -# audio/g7291 -# audio/g729d -# audio/g729e -# audio/gsm -# audio/gsm-efr -# audio/ilbc -# audio/l16 -# audio/l20 -# audio/l24 -# audio/l8 -# audio/lpc -audio/midi mid midi kar rmi -# audio/mobile-xmf -audio/mp4 mp4a -# audio/mp4a-latm -# audio/mpa -# audio/mpa-robust -audio/mpeg mpga mp2 mp2a mp3 m2a m3a -# audio/mpeg4-generic -audio/ogg oga ogg spx -# audio/parityfec -# audio/pcma -# audio/pcma-wb -# audio/pcmu-wb -# audio/pcmu -# audio/prs.sid -# audio/qcelp -# audio/red -# audio/rtp-enc-aescm128 -# audio/rtp-midi -# audio/rtx -# audio/smv -# audio/smv0 -# audio/smv-qcp -# audio/sp-midi -# audio/speex -# audio/t140c -# audio/t38 -# audio/telephone-event -# audio/tone -# audio/uemclip -# audio/ulpfec -# audio/vdvi -# audio/vmr-wb -# audio/vnd.3gpp.iufp -# audio/vnd.4sb -# audio/vnd.audiokoz -# audio/vnd.celp -# audio/vnd.cisco.nse -# audio/vnd.cmles.radio-events -# audio/vnd.cns.anp1 -# audio/vnd.cns.inf1 -audio/vnd.digital-winds eol -# audio/vnd.dlna.adts -# audio/vnd.dolby.heaac.1 -# audio/vnd.dolby.heaac.2 -# audio/vnd.dolby.mlp -# audio/vnd.dolby.mps -# audio/vnd.dolby.pl2 -# audio/vnd.dolby.pl2x -# audio/vnd.dolby.pl2z -# audio/vnd.dolby.pulse.1 -audio/vnd.dra dra -audio/vnd.dts dts -audio/vnd.dts.hd dtshd -# audio/vnd.everad.plj -# audio/vnd.hns.audio -audio/vnd.lucent.voice lvp -audio/vnd.ms-playready.media.pya pya -# audio/vnd.nokia.mobile-xmf -# audio/vnd.nortel.vbk -audio/vnd.nuera.ecelp4800 ecelp4800 -audio/vnd.nuera.ecelp7470 ecelp7470 -audio/vnd.nuera.ecelp9600 ecelp9600 -# audio/vnd.octel.sbc -# audio/vnd.qcelp -# audio/vnd.rhetorex.32kadpcm -# audio/vnd.sealedmedia.softseal.mpeg -# audio/vnd.vmx.cvsd -# audio/vorbis -# audio/vorbis-config -audio/x-aac aac -audio/x-aiff aif aiff aifc -audio/x-mpegurl m3u -audio/x-ms-wax wax -audio/x-ms-wma wma -audio/x-pn-realaudio ram ra -audio/x-pn-realaudio-plugin rmp -audio/x-wav wav -chemical/x-cdx cdx -chemical/x-cif cif -chemical/x-cmdf cmdf -chemical/x-cml cml -chemical/x-csml csml -# chemical/x-pdb -chemical/x-xyz xyz -image/bmp bmp -image/cgm cgm -# image/example -# image/fits -image/g3fax g3 -image/gif gif -image/ief ief -# image/jp2 -image/jpeg jpeg jpg jpe -# image/jpm -# image/jpx -# image/naplps -image/png png -image/prs.btif btif -# image/prs.pti -image/svg+xml svg svgz -# image/t38 -image/tiff tiff tif -# image/tiff-fx -image/vnd.adobe.photoshop psd -# image/vnd.cns.inf2 -image/vnd.djvu djvu djv -image/vnd.dwg dwg -image/vnd.dxf dxf -image/vnd.fastbidsheet fbs -image/vnd.fpx fpx -image/vnd.fst fst -image/vnd.fujixerox.edmics-mmr mmr -image/vnd.fujixerox.edmics-rlc rlc -# image/vnd.globalgraphics.pgb -# image/vnd.microsoft.icon -# image/vnd.mix -image/vnd.ms-modi mdi -image/vnd.net-fpx npx -# image/vnd.radiance -# image/vnd.sealed.png -# image/vnd.sealedmedia.softseal.gif -# image/vnd.sealedmedia.softseal.jpg -# image/vnd.svf -image/vnd.wap.wbmp wbmp -image/vnd.xiff xif -image/x-cmu-raster ras -image/x-cmx cmx -image/x-freehand fh fhc fh4 fh5 fh7 -image/x-icon ico -image/x-pcx pcx -image/x-pict pic pct -image/x-portable-anymap pnm -image/x-portable-bitmap pbm -image/x-portable-graymap pgm -image/x-portable-pixmap ppm -image/x-rgb rgb -image/x-xbitmap xbm -image/x-xpixmap xpm -image/x-xwindowdump xwd -# message/cpim -# message/delivery-status -# message/disposition-notification -# message/example -# message/external-body -# message/global -# message/global-delivery-status -# message/global-disposition-notification -# message/global-headers -# message/http -# message/imdn+xml -# message/news -# message/partial -message/rfc822 eml mime -# message/s-http -# message/sip -# message/sipfrag -# message/tracking-status -# message/vnd.si.simp -# model/example -model/iges igs iges -model/mesh msh mesh silo -model/vnd.dwf dwf -# model/vnd.flatland.3dml -model/vnd.gdl gdl -# model/vnd.gs-gdl -# model/vnd.gs.gdl -model/vnd.gtw gtw -# model/vnd.moml+xml -model/vnd.mts mts -# model/vnd.parasolid.transmit.binary -# model/vnd.parasolid.transmit.text -model/vnd.vtu vtu -model/vrml wrl vrml -# multipart/alternative -# multipart/appledouble -# multipart/byteranges -# multipart/digest -# multipart/encrypted -# multipart/example -# multipart/form-data -# multipart/header-set -# multipart/mixed -# multipart/parallel -# multipart/related -# multipart/report -# multipart/signed -# multipart/voice-message -text/calendar ics ifb -text/css css -text/csv csv -# text/directory -# text/dns -# text/ecmascript -# text/enriched -# text/example -text/html html htm -# text/javascript -# text/parityfec -text/plain txt text conf def list log in -# text/prs.fallenstein.rst -text/prs.lines.tag dsc -# text/vnd.radisys.msml-basic-layout -# text/red -# text/rfc822-headers -text/richtext rtx -# text/rtf -# text/rtp-enc-aescm128 -# text/rtx -text/sgml sgml sgm -# text/t140 -text/tab-separated-values tsv -text/troff t tr roff man me ms -# text/ulpfec -text/uri-list uri uris urls -# text/vnd.abc -text/vnd.curl curl -text/vnd.curl.dcurl dcurl -text/vnd.curl.scurl scurl -text/vnd.curl.mcurl mcurl -# text/vnd.dmclientscript -# text/vnd.esmertec.theme-descriptor -text/vnd.fly fly -text/vnd.fmi.flexstor flx -text/vnd.graphviz gv -text/vnd.in3d.3dml 3dml -text/vnd.in3d.spot spot -# text/vnd.iptc.newsml -# text/vnd.iptc.nitf -# text/vnd.latex-z -# text/vnd.motorola.reflex -# text/vnd.ms-mediapackage -# text/vnd.net2phone.commcenter.command -# text/vnd.si.uricatalogue -text/vnd.sun.j2me.app-descriptor jad -# text/vnd.trolltech.linguist -# text/vnd.wap.si -# text/vnd.wap.sl -text/vnd.wap.wml wml -text/vnd.wap.wmlscript wmls -text/x-asm s asm -text/x-c c cc cxx cpp h hh dic -text/x-fortran f for f77 f90 -text/x-pascal p pas -text/x-java-source java -text/x-setext etx -text/x-uuencode uu -text/x-vcalendar vcs -text/x-vcard vcf -# text/xml -# text/xml-external-parsed-entity -video/3gpp 3gp -# video/3gpp-tt -video/3gpp2 3g2 -# video/bmpeg -# video/bt656 -# video/celb -# video/dv -# video/example -video/h261 h261 -video/h263 h263 -# video/h263-1998 -# video/h263-2000 -video/h264 h264 -video/jpeg jpgv -# video/jpeg2000 -video/jpm jpm jpgm -video/mj2 mj2 mjp2 -# video/mp1s -# video/mp2p -# video/mp2t -video/mp4 mp4 mp4v mpg4 -# video/mp4v-es -video/mpeg mpeg mpg mpe m1v m2v -# video/mpeg4-generic -# video/mpv -# video/nv -video/ogg ogv -# video/parityfec -# video/pointer -video/quicktime qt mov -# video/raw -# video/rtp-enc-aescm128 -# video/rtx -# video/smpte292m -# video/ulpfec -# video/vc1 -# video/vnd.cctv -# video/vnd.dlna.mpeg-tts -video/vnd.fvt fvt -# video/vnd.hns.video -# video/vnd.iptvforum.1dparityfec-1010 -# video/vnd.iptvforum.1dparityfec-2005 -# video/vnd.iptvforum.2dparityfec-1010 -# video/vnd.iptvforum.2dparityfec-2005 -# video/vnd.iptvforum.ttsavc -# video/vnd.iptvforum.ttsmpeg2 -# video/vnd.motorola.video -# video/vnd.motorola.videop -video/vnd.mpegurl mxu m4u -video/vnd.ms-playready.media.pyv pyv -# video/vnd.nokia.interleaved-multimedia -# video/vnd.nokia.videovoip -# video/vnd.objectvideo -# video/vnd.sealed.mpeg1 -# video/vnd.sealed.mpeg4 -# video/vnd.sealed.swf -# video/vnd.sealedmedia.softseal.mov -video/vnd.vivo viv -video/x-f4v f4v -video/x-fli fli -video/x-flv flv -video/x-m4v m4v -video/x-ms-asf asf asx -video/x-ms-wm wm -video/x-ms-wmv wmv -video/x-ms-wmx wmx -video/x-ms-wvx wvx -video/x-msvideo avi -video/x-sgi-movie movie -x-conference/x-cooltalk ice diff --git a/warp.cabal b/warp.cabal index 16cd9e32d..235205f1f 100644 --- a/warp.cabal +++ b/warp.cabal @@ -26,27 +26,16 @@ Library , enumerator >= 0.4 && < 0.5 , blaze-builder >= 0.2.1.4 && < 0.3 , sendfile >= 0.7.2 && < 0.8 - , containers >= 0.2 && < 0.5 - , template-haskell - , unix-compat >= 0.2 && < 0.3 if flag(network-bytestring) build-depends: network >= 2.2.1 && < 2.2.3 , network-bytestring >= 0.1.3 && < 0.1.4 else build-depends: network >= 2.3 && < 2.4 Exposed-modules: Network.Wai.Handler.Warp - Network.Wai.Handler.Warp.Util - Other-modules: UtilHelper ghc-options: -Wall if !flag(timeout-protection) Cpp-options: -DNO_TIMEOUT_PROTECTION -Executable warp - Main-is: warp.hs - ghc-options: -Wall -O2 -threaded - Build-Depends: text >= 0.8 && < 0.12 - , directory >= 1.0 && < 1.2 - source-repository head type: git location: git://github.com/snoyberg/warp.git diff --git a/warp.hs b/warp.hs deleted file mode 100644 index b5eb8839b..000000000 --- a/warp.hs +++ /dev/null @@ -1,71 +0,0 @@ -{-# LANGUAGE OverloadedStrings #-} -import Data.ByteString.Lazy.Char8 () -import Data.ByteString (ByteString) -import Network.Wai -import Network.Wai.Handler.Warp (run) -import System.Directory (doesFileExist) -import System.Environment (getArgs) -import Data.Text (unpack) -import Data.Text.Encoding (decodeUtf8With) -import Data.Text.Encoding.Error (lenientDecode) -import Control.Monad.IO.Class (liftIO) -import Network.Wai.Handler.Warp.Util (mimeTypes, getFileSize) -import qualified Data.Map as Map -import qualified Data.ByteString.Char8 as S8 - -main :: IO () -main = do - a <- getArgs - let prefix = - case a of - [] -> id - [x] -> (filter (not . null) (split x) ++) - _ -> error "Usage: warp [root dir]" - run 3000 $ app prefix - -app :: ([String] -> [String]) -> Application -app prefix Request { requestMethod = m, pathInfo = p } - | m == "GET" = - case checkUnsafe $ split $ unpack $ decodeUtf8With lenientDecode p of - Nothing -> return $ responseLBS status403 [("Content-Type", "text/plain")] "Permission Denied" - Just pieces -> do - let file = join $ prefix pieces - let ext = reverse $ takeWhile (/= '.') $ reverse file - let ct = getCT ext - e <- liftIO $ doesFileExist file - if e - then do - size <- liftIO $ getFileSize file - return $ ResponseFile status200 - [ ("Content-Type", ct) - , ("Content-Length", S8.pack $ show size) - ] file - else return $ responseLBS status404 [("Content-Type", "text/plain")] "File not found" - | otherwise = - return $ responseLBS status405 [("Content-Type", "text/plain")] "Bad method" - -split :: String -> [String] -split "" = [] -split s = - let (x, y) = break (== '/') s - in x : split (drop 1 y) - -checkUnsafe :: [String] -> Maybe [String] -checkUnsafe [] = Nothing -checkUnsafe [""] = Just ["index.html"] -checkUnsafe [x] = Just [x] -checkUnsafe ("..":_) = Nothing -checkUnsafe (".":rest) = checkUnsafe rest -checkUnsafe ("":rest) = checkUnsafe rest -checkUnsafe (x:rest) = ((:) x) `fmap` checkUnsafe rest - -join :: [String] -> String -join [] = [] -join [x] = x -join (x:xs) = x ++ '/' : join xs - -getCT :: String -> ByteString -getCT s = - case Map.lookup (S8.pack s) mimeTypes of - Just ct -> ct - Nothing -> "application/octet-stream" From e86fffa63635dfa47a555d86c7f820fdceb2a61a Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Tue, 11 Jan 2011 19:18:18 +0200 Subject: [PATCH 29/90] Added static file test --- file.hs | 5 +++++ test.txt | 1 + 2 files changed, 6 insertions(+) create mode 100644 file.hs create mode 100644 test.txt diff --git a/file.hs b/file.hs new file mode 100644 index 000000000..d1391f0ff --- /dev/null +++ b/file.hs @@ -0,0 +1,5 @@ +{-# LANGUAGE OverloadedStrings #-} +import Network.Wai +import Network.Wai.Handler.Warp + +main = run 3000 $ const $ return $ ResponseFile status200 [("Content-Type", "text/plain"), ("Content-Length", "16")] "test.txt" diff --git a/test.txt b/test.txt new file mode 100644 index 000000000..484ba93ef --- /dev/null +++ b/test.txt @@ -0,0 +1 @@ +This is a test. From 7705e092efefd27701f97541afba06fd033cd5c9 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Tue, 11 Jan 2011 19:28:16 +0200 Subject: [PATCH 30/90] Fix warnings and a ResponseBuilder bug --- Network/Wai/Handler/Warp.hs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index afa7d2622..42cb97450 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -37,8 +37,6 @@ import Network.Socket ) import qualified Network.Socket.ByteString as Sock import Control.Exception (bracket, finally, Exception, SomeException, catch) -import System.IO (Handle, hClose, hFlush) -import System.IO.Error (isEOFError, ioeGetHandle) import Control.Concurrent (forkIO) import Control.Monad (unless, when) import Data.Maybe (fromMaybe) @@ -49,7 +47,6 @@ import Control.Arrow (first) import Data.Enumerator (($$), (>>==)) import qualified Data.Enumerator as E -import Data.Enumerator.IO (iterHandle, enumHandle) import Blaze.ByteString.Builder.Enumerator (builderToByteString) import Blaze.ByteString.Builder.HTTP (chunkedTransferEncoding, chunkedTransferTerminator) @@ -234,12 +231,16 @@ sendResponse req hv socket (ResponseFile s hs fp) = do return $ lookup "content-length" hs /= Nothing else return True sendResponse req hv socket (ResponseBuilder s hs b) = do - toByteStringIO (Sock.sendAll socket) b' + toByteStringIO (Sock.sendAll socket) $ + if hasBody s req + then b' + else headers' return isKeepAlive where + headers' = headers hv s hs True b' = if isChunked' - then headers hv s hs True + then headers' `mappend` chunkedTransferEncoding b `mappend` chunkedTransferTerminator else headers hv s hs False `mappend` b @@ -310,7 +311,7 @@ requestBodyHandle initLen = case mbs of Nothing -> return () Just bs -> do - (bs', newlen) <- yieldExtra len bs + (_, newlen) <- yieldExtra len bs drain newlen yieldExtra len bs | B.length bs == len = return (bs, 0) @@ -320,12 +321,14 @@ requestBodyHandle initLen = E.yield () $ E.Chunks [y] return (x, 0) +iterSocket :: Socket -> E.Iteratee ByteString IO () iterSocket socket = E.continue go where go E.EOF = E.yield () E.EOF go (E.Chunks cs) = liftIO (Sock.sendMany socket cs) >> E.continue go +enumSocket :: Int -> Socket -> E.Enumerator ByteString IO a enumSocket len socket (E.Continue k) = do #if NO_TIMEOUT_PROTECTION bs <- liftIO $ Sock.recv socket len From 9c07b01d3e1505a178c596291b62d16e9e7e6132 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 13 Jan 2011 18:33:42 +0200 Subject: [PATCH 31/90] Removed mime.types extra-source-files --- warp.cabal | 1 - 1 file changed, 1 deletion(-) diff --git a/warp.cabal b/warp.cabal index 235205f1f..05390d828 100644 --- a/warp.cabal +++ b/warp.cabal @@ -10,7 +10,6 @@ Category: Web, Yesod Build-Type: Simple Cabal-Version: >=1.6 Stability: Stable -Extra-source-files: mime.types flag timeout-protection Description: Use timeouts (very performance-costly) to protect against DOS attacks. From 9b8c2152d2332052580c27e5dbd298fe5e6cbe02 Mon Sep 17 00:00:00 2001 From: Matt Brown Date: Mon, 3 Jan 2011 18:13:08 -0800 Subject: [PATCH 32/90] B.words => S.split 32 --- Network/Wai/Handler/Warp.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 42cb97450..33df276ef 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -184,7 +184,7 @@ parseRequest' port lines' remoteHost' = do parseFirst :: ByteString -> E.Iteratee S.ByteString IO (ByteString, ByteString, ByteString, HttpVersion) parseFirst s = do - let pieces = B.words s + let pieces = S.split 32 s -- ' ' (method, query, http') <- case pieces of [x, y, z] -> return (x, y, z) From 66180a8a7cbfec4d75182e42250ff87bab8f9060 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 13 Jan 2011 21:03:18 +0200 Subject: [PATCH 33/90] Unroll static mconcats --- Network/Wai/Handler/Warp.hs | 43 +++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 33df276ef..d5e8c199d 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -195,26 +195,25 @@ parseFirst s = do return (method, rpath, qstring, hsecond) headers :: HttpVersion -> Status -> ResponseHeaders -> Bool -> Builder -headers httpversion status responseHeaders isChunked' = mconcat - [ copyByteString "HTTP/" - , copyByteString httpversion - , fromChar ' ' - , fromString $ show $ statusCode status - , fromChar ' ' - , copyByteString $ statusMessage status - , copyByteString "\r\n" - , mconcat $ map go responseHeaders - , if isChunked' - then copyByteString "Transfer-Encoding: chunked\r\n\r\n" - else copyByteString "\r\n" - ] +headers httpversion status responseHeaders isChunked' = + copyByteString "HTTP/" + `mappend` copyByteString httpversion + `mappend` fromChar ' ' + `mappend` fromString $ show $ statusCode status + `mappend` fromChar ' ' + `mappend` copyByteString $ statusMessage status + `mappend` copyByteString "\r\n" + `mappend` mconcat (map go responseHeaders) + `mappend` + if isChunked' + then copyByteString "Transfer-Encoding: chunked\r\n\r\n" + else copyByteString "\r\n" where - go (x, y) = mconcat - [ copyByteString $ ciOriginal x - , copyByteString ": " - , copyByteString y - , copyByteString "\r\n" - ] + go (x, y) = + copyByteString $ ciOriginal x + `mappend` copyByteString ": " + `mappend` copyByteString y + `mappend` copyByteString "\r\n" isChunked :: HttpVersion -> Bool isChunked = (==) http11 @@ -273,10 +272,8 @@ sendResponse req hv socket (ResponseEnumerator res) = chunk = E.checkDone $ E.continue . step step k E.EOF = k (E.Chunks [chunkedTransferTerminator]) >>== return step k (E.Chunks []) = E.continue $ step k - step k (E.Chunks builders) = - k (E.Chunks [chunked]) >>== chunk - where - chunked = chunkedTransferEncoding $ mconcat builders + -- FIXME ensure that the mconcat on the concents here is unnecessary + step k x = k x >>== chunk parseHeaderNoAttr :: ByteString -> (ByteString, ByteString) parseHeaderNoAttr s = From c5d1fb39feb9a779184c1aff8c83e1760ef9e890 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 13 Jan 2011 21:07:12 +0200 Subject: [PATCH 34/90] Fix mappend unrolling --- Network/Wai/Handler/Warp.hs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index d5e8c199d..68aaf808e 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -52,7 +52,7 @@ import Blaze.ByteString.Builder.HTTP (chunkedTransferEncoding, chunkedTransferTerminator) import Blaze.ByteString.Builder (copyByteString, Builder, toLazyByteString, toByteStringIO) -import Blaze.ByteString.Builder.Char8 (fromChar, fromString) +import Blaze.ByteString.Builder.Char8 (fromChar, fromShow) import Data.Monoid (mconcat, mappend) import Network.Socket.SendFile (sendFile) @@ -190,18 +190,20 @@ parseFirst s = do [x, y, z] -> return (x, y, z) _ -> E.throwError $ BadFirstLine $ B.unpack s let (hfirst, hsecond) = B.splitAt 5 http' - unless (hfirst == "HTTP/") $ E.throwError NonHttp - let (rpath, qstring) = B.break (== '?') query - return (method, rpath, qstring, hsecond) + if (hfirst == "HTTP/") + then + let (rpath, qstring) = B.break (== '?') query + in return (method, rpath, qstring, hsecond) + else E.throwError NonHttp headers :: HttpVersion -> Status -> ResponseHeaders -> Bool -> Builder headers httpversion status responseHeaders isChunked' = copyByteString "HTTP/" `mappend` copyByteString httpversion `mappend` fromChar ' ' - `mappend` fromString $ show $ statusCode status + `mappend` fromShow (statusCode status) `mappend` fromChar ' ' - `mappend` copyByteString $ statusMessage status + `mappend` copyByteString (statusMessage status) `mappend` copyByteString "\r\n" `mappend` mconcat (map go responseHeaders) `mappend` @@ -210,7 +212,7 @@ headers httpversion status responseHeaders isChunked' = else copyByteString "\r\n" where go (x, y) = - copyByteString $ ciOriginal x + copyByteString (ciOriginal x) `mappend` copyByteString ": " `mappend` copyByteString y `mappend` copyByteString "\r\n" From eba4d14062d6a94fea615258c5cc41dcfbc15995 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 13 Jan 2011 21:07:26 +0200 Subject: [PATCH 35/90] Remove when and unless --- Network/Wai/Handler/Warp.hs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 68aaf808e..dc51c5078 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -38,7 +38,6 @@ import Network.Socket import qualified Network.Socket.ByteString as Sock import Control.Exception (bracket, finally, Exception, SomeException, catch) import Control.Concurrent (forkIO) -import Control.Monad (unless, when) import Data.Maybe (fromMaybe) import Data.Typeable (Typeable) @@ -88,7 +87,7 @@ serveConnection port app conn remoteHost' = do (enumeratee, env) <- parseRequest port remoteHost' res <- E.joinI $ enumeratee $$ app env keepAlive <- liftIO $ sendResponse env (httpVersion env) conn res - when keepAlive serveConnection' + if keepAlive then serveConnection' else return () parseRequest :: Port -> SockAddr -> E.Iteratee S.ByteString IO (E.Enumeratee ByteString ByteString IO a, Request) parseRequest port remoteHost' = do From 4816758f47076775fc5acf4827672f55f5288576 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 14 Jan 2011 11:39:36 +0200 Subject: [PATCH 36/90] Optimize parseRequest (code from Matt) --- Network/Wai/Handler/Warp.hs | 44 +++++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index dc51c5078..839f38170 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -2,6 +2,7 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE CPP #-} +{-# LANGUAGE BangPatterns #-} --------------------------------------------------------- -- | -- Module : Network.Wai.Handler.Warp @@ -27,6 +28,7 @@ import qualified System.IO import Data.ByteString (ByteString) import qualified Data.ByteString as S +import qualified Data.ByteString.Unsafe as SU import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy as L import Network @@ -57,6 +59,7 @@ import Network.Socket.SendFile (sendFile) import Control.Monad.IO.Class (liftIO) import System.Timeout (timeout) +import Data.Word (Word8) run :: Port -> Application -> IO () run port = withSocketsDo . @@ -149,28 +152,29 @@ parseRequest' :: Port -> [ByteString] -> SockAddr -> E.Iteratee S.ByteString IO (E.Enumeratee S.ByteString S.ByteString IO a, Request) -parseRequest' port lines' remoteHost' = do - (firstLine, otherLines) <- - case lines' of - x:xs -> return (x, xs) - [] -> E.throwError $ NotEnoughLines $ map B.unpack lines' +parseRequest' _ [] _ = E.throwError $ NotEnoughLines [] +parseRequest' port (firstLine:otherLines) remoteHost' = do (method, rpath', gets, httpversion) <- parseFirst firstLine - let rpath = '/' : case B.unpack rpath' of - ('/':x) -> x - _ -> B.unpack rpath' + let rpath = + if S.null rpath' + then "/" + else if '/' == B.head rpath' + then "/" + else B.cons '/' rpath' let heads = map (first mkCIByteString . parseHeaderNoAttr) otherLines let host = fromMaybe "" $ lookup "host" heads - let len = fromMaybe 0 $ do - bs <- lookup "Content-Length" heads - let str = B.unpack bs - case reads str of - (x, _):_ -> Just x - _ -> Nothing - let (serverName', _) = B.break (== ':') host + let len = + case lookup "content-length" heads of + Nothing -> 0 + Just bs -> + case reads $ B.unpack bs of -- FIXME could probably be optimized + (x, _):_ -> x + [] -> 0 + let serverName' = takeUntil 58 host -- ':' return (requestBodyHandle len, Request { requestMethod = method , httpVersion = httpversion - , pathInfo = B.pack rpath + , pathInfo = rpath , queryString = gets , serverName = serverName' , serverPort = port @@ -180,6 +184,14 @@ parseRequest' port lines' remoteHost' = do , remoteHost = remoteHost' }) + +takeUntil :: Word8 -> ByteString -> ByteString +takeUntil c bs = + case S.elemIndex c bs of + Just !idx -> SU.unsafeTake idx bs + Nothing -> bs +{-# INLINE takeUntil #-} + parseFirst :: ByteString -> E.Iteratee S.ByteString IO (ByteString, ByteString, ByteString, HttpVersion) parseFirst s = do From 2277409524c2005da5f6593f913fa06cbc098327 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 14 Jan 2011 11:42:51 +0200 Subject: [PATCH 37/90] Optimize parseHeaderNoAttr (code from Matt) --- Network/Wai/Handler/Warp.hs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 839f38170..7c9f1be68 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -44,8 +44,6 @@ import Data.Maybe (fromMaybe) import Data.Typeable (Typeable) -import Control.Arrow (first) - import Data.Enumerator (($$), (>>==)) import qualified Data.Enumerator as E import Blaze.ByteString.Builder.Enumerator (builderToByteString) @@ -161,7 +159,7 @@ parseRequest' port (firstLine:otherLines) remoteHost' = do else if '/' == B.head rpath' then "/" else B.cons '/' rpath' - let heads = map (first mkCIByteString . parseHeaderNoAttr) otherLines + let heads = map parseHeaderNoAttr otherLines let host = fromMaybe "" $ lookup "host" heads let len = case lookup "content-length" heads of @@ -288,16 +286,15 @@ sendResponse req hv socket (ResponseEnumerator res) = -- FIXME ensure that the mconcat on the concents here is unnecessary step k x = k x >>== chunk -parseHeaderNoAttr :: ByteString -> (ByteString, ByteString) +parseHeaderNoAttr :: ByteString -> (CIByteString, ByteString) parseHeaderNoAttr s = - let (k, rest) = B.span (/= ':') s - rest' = if not (B.null rest) && - B.head rest == ':' && - not (B.null $ B.tail rest) && - B.head (B.tail rest) == ' ' - then B.drop 2 rest + let (k, rest) = S.breakByte 58 s -- ':' + restLen = S.length rest + -- FIXME check for colon without following space? + rest' = if restLen > 1 && SU.unsafeTake 2 rest == ": " + then SU.unsafeDrop 2 rest else rest - in (k, rest') + in (mkCIByteString k, rest') requestBodyHandle :: Int -> E.Enumeratee ByteString ByteString IO a From 215b4b4384ffd3ea04a3a35263721c40deddee86 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 14 Jan 2011 11:44:19 +0200 Subject: [PATCH 38/90] enumerator 0.4.5 --- Network/Wai/Handler/Warp.hs | 3 ++- warp.cabal | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 7c9f1be68..c8002fef8 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -46,6 +46,7 @@ import Data.Typeable (Typeable) import Data.Enumerator (($$), (>>==)) import qualified Data.Enumerator as E +import qualified Data.Enumerator.List as EL import Blaze.ByteString.Builder.Enumerator (builderToByteString) import Blaze.ByteString.Builder.HTTP (chunkedTransferEncoding, chunkedTransferTerminator) @@ -117,7 +118,7 @@ takeLine :: Int -> ([ByteString] -> [ByteString]) -> E.Iteratee ByteString IO ByteString takeLine len front = do - mbs <- E.head + mbs <- EL.head case mbs of Nothing -> E.throwError IncompleteHeaders Just bs -> do diff --git a/warp.cabal b/warp.cabal index 05390d828..a91c30b9d 100644 --- a/warp.cabal +++ b/warp.cabal @@ -22,7 +22,7 @@ Library , wai >= 0.3.0 && < 0.4 , blaze-builder-enumerator >= 0.2 && < 0.3 , transformers >= 0.2 && < 0.3 - , enumerator >= 0.4 && < 0.5 + , enumerator >= 0.4.5 && < 0.5 , blaze-builder >= 0.2.1.4 && < 0.3 , sendfile >= 0.7.2 && < 0.8 if flag(network-bytestring) From 8d1ba29b8d053d3621ae26f16e9272c2309e3573 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 14 Jan 2011 12:13:45 +0200 Subject: [PATCH 39/90] Using more enumerator functions, added test suite --- Network/Wai/Handler/Warp.hs | 121 ++++++++++++++---------------------- runtests.hs | 71 +++++++++++++++++++++ warp.cabal | 1 + 3 files changed, 120 insertions(+), 73 deletions(-) create mode 100644 runtests.hs diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index c8002fef8..3f638bcd6 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -20,6 +20,11 @@ module Network.Wai.Handler.Warp ( run , sendResponse , parseRequest +#if TEST + , takeLineMax + , takeUntilBlank + , InvalidRequest (..) +#endif ) where import Prelude hiding (catch) @@ -47,6 +52,7 @@ import Data.Typeable (Typeable) import Data.Enumerator (($$), (>>==)) import qualified Data.Enumerator as E import qualified Data.Enumerator.List as EL +import qualified Data.Enumerator.Binary as EB import Blaze.ByteString.Builder.Enumerator (builderToByteString) import Blaze.ByteString.Builder.HTTP (chunkedTransferEncoding, chunkedTransferTerminator) @@ -55,6 +61,7 @@ import Blaze.ByteString.Builder import Blaze.ByteString.Builder.Char8 (fromChar, fromShow) import Data.Monoid (mconcat, mappend) import Network.Socket.SendFile (sendFile) +import Network.Socket.Enumerator (iterSocket) import Control.Monad.IO.Class (liftIO) import System.Timeout (timeout) @@ -103,38 +110,6 @@ maxHeaderLength = 1024 bytesPerRead = 4096 readTimeout = 3000000 -takeUntilBlank :: Int - -> ([ByteString] -> [ByteString]) - -> E.Iteratee S.ByteString IO [ByteString] -takeUntilBlank count _ - | count > maxHeaders = E.throwError TooManyHeaders -takeUntilBlank count front = do - l <- takeLine 0 id - if B.null l - then return $ front [] - else takeUntilBlank (count + 1) $ front . (:) l - -takeLine :: Int - -> ([ByteString] -> [ByteString]) - -> E.Iteratee ByteString IO ByteString -takeLine len front = do - mbs <- EL.head - case mbs of - Nothing -> E.throwError IncompleteHeaders - Just bs -> do - let (x, y) = S.breakByte 10 bs - x' = if S.length x > 0 && S.last x == 13 - then S.init x - else x - let len' = len + B.length x - case () of - () - | len' > maxHeaderLength -> E.throwError OverLargeHeader - | B.null y -> takeLine len' $ front . (:) x - | otherwise -> do - E.yield () $ E.Chunks [B.drop 1 y] - return $ B.concat $ front [x'] - data InvalidRequest = NotEnoughLines [String] | BadFirstLine String @@ -143,7 +118,7 @@ data InvalidRequest = | IncompleteHeaders | OverLargeHeader | SocketTimeout - deriving (Show, Typeable) + deriving (Show, Typeable, Eq) instance Exception InvalidRequest -- | Parse a set of header lines and body into a 'Request'. @@ -170,7 +145,9 @@ parseRequest' port (firstLine:otherLines) remoteHost' = do (x, _):_ -> x [] -> 0 let serverName' = takeUntil 58 host -- ':' - return (requestBodyHandle len, Request + -- FIXME isolate takes an Integer instead of Int or Int64. If this is a + -- performance penalty, we may need our own version. + return (EB.isolate len, Request { requestMethod = method , httpVersion = httpversion , pathInfo = rpath @@ -297,45 +274,8 @@ parseHeaderNoAttr s = else rest in (mkCIByteString k, rest') -requestBodyHandle :: Int - -> E.Enumeratee ByteString ByteString IO a -requestBodyHandle initLen = - go initLen - where - go 0 step = return step - go len (E.Continue k) = do - x <- E.head - case x of - Nothing -> return $ E.Continue k - Just bs -> do - (bs', newlen) <- yieldExtra len bs - k (E.Chunks [bs']) >>== go newlen - go len step = do - drain len - return step - drain 0 = return () - drain len = do - mbs <- E.head - case mbs of - Nothing -> return () - Just bs -> do - (_, newlen) <- yieldExtra len bs - drain newlen - yieldExtra len bs - | B.length bs == len = return (bs, 0) - | B.length bs < len = return (bs, len - B.length bs) - | otherwise = do - let (x, y) = B.splitAt len bs - E.yield () $ E.Chunks [y] - return (x, 0) - -iterSocket :: Socket -> E.Iteratee ByteString IO () -iterSocket socket = - E.continue go - where - go E.EOF = E.yield () E.EOF - go (E.Chunks cs) = liftIO (Sock.sendMany socket cs) >> E.continue go - +-- FIXME when we switch to Jeremy's timeout code, we can probably start using +-- network-enumerator's enumSocket enumSocket :: Int -> Socket -> E.Enumerator ByteString IO a enumSocket len socket (E.Continue k) = do #if NO_TIMEOUT_PROTECTION @@ -352,3 +292,38 @@ enumSocket len socket (E.Continue k) = do | S.length bs == 0 = E.continue k | otherwise = k (E.Chunks [bs]) >>== enumSocket len socket enumSocket _ _ step = E.returnI step + +------ The functions below are not warp-specific and could be split out into a +--separate package. + +takeUntilBlank :: Int + -> ([ByteString] -> [ByteString]) + -> E.Iteratee S.ByteString IO [ByteString] +takeUntilBlank count _ + | count > maxHeaders = E.throwError TooManyHeaders +takeUntilBlank count front = do + l <- takeLineMax 0 id + if B.null l + then return $ front [] + else takeUntilBlank (count + 1) $ front . (:) l + +takeLineMax :: Int + -> ([ByteString] -> [ByteString]) + -> E.Iteratee ByteString IO ByteString +takeLineMax len front = do + mbs <- EL.head + case mbs of + Nothing -> E.throwError IncompleteHeaders + Just bs -> do + let (x, y) = S.breakByte 10 bs + x' = if S.length x > 0 && S.last x == 13 + then S.init x + else x + let len' = len + B.length x + case () of + () + | len' > maxHeaderLength -> E.throwError OverLargeHeader + | B.null y -> takeLineMax len' $ front . (:) x + | otherwise -> do + E.yield () $ E.Chunks [B.drop 1 y] + return $ B.concat $ front [x'] diff --git a/runtests.hs b/runtests.hs new file mode 100644 index 000000000..bfce1d830 --- /dev/null +++ b/runtests.hs @@ -0,0 +1,71 @@ +{-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE OverloadedStrings #-} +import Test.Framework (defaultMain, testGroup, Test) +import Test.Framework.Providers.HUnit +import Test.HUnit hiding (Test) + +import Network.Wai.Handler.Warp (takeLineMax, takeUntilBlank, InvalidRequest (..)) +import Data.Enumerator (run_, ($$), enumList, run) +import Control.Exception (fromException) + +main :: IO () +main = defaultMain [testSuite] + +testSuite :: Test +testSuite = testGroup "Text.Hamlet" + [ testCase "takeLineMax safe" caseTakeLineMaxSafe + , testCase "takeUntilBlank safe" caseTakeUntilBlankSafe + , testCase "takeLineMax unsafe" caseTakeLineMaxUnsafe + , testCase "takeLineMax incomplete" caseTakeLineMaxIncomplete + , testCase "takeUntilBlank too many lines" caseTakeUntilBlankTooMany + , testCase "takeUntilBlank too large" caseTakeUntilBlankTooLarge + ] + +caseTakeLineMaxSafe = do + x <- run_ $ (enumList 1 ["f", "oo\n\n", "bar\n\nbaz\n"]) $$ do + a <- takeLineMax 0 id + b <- takeLineMax 0 id + c <- takeLineMax 0 id + d <- takeLineMax 0 id + e <- takeLineMax 0 id + return (a, b, c, d, e) + x @?= ("foo", "", "bar", "", "baz") + +caseTakeUntilBlankSafe = do + x <- run_ $ (enumList 1 ["f", "oo\n", "bar\nbaz\n\n"]) $$ takeUntilBlank 0 id + x @?= ["foo", "bar", "baz"] + +caseTakeLineMaxUnsafe = do + x <- run $ (enumList 1 $ repeat "abc") $$ do + a <- takeLineMax 0 id + b <- takeLineMax 0 id + c <- takeLineMax 0 id + d <- takeLineMax 0 id + e <- takeLineMax 0 id + return (a, b, c, d, e) + assertException OverLargeHeader x + +assertException x (Left se) = + case fromException se of + Just e -> e @?= x + Nothing -> assertFailure "Not an exception" +assertException _ _ = assertFailure "Not an exception" + +caseTakeLineMaxIncomplete = do + x <- run $ (enumList 1 ["f", "oo\n\n", "bar\n\nbaz"]) $$ do + a <- takeLineMax 0 id + b <- takeLineMax 0 id + c <- takeLineMax 0 id + d <- takeLineMax 0 id + e <- takeLineMax 0 id + return (a, b, c, d, e) + assertException IncompleteHeaders x + +caseTakeUntilBlankTooMany = do + x <- run $ (enumList 1 $ repeat "f\n") $$ takeUntilBlank 0 id + assertException TooManyHeaders x + +caseTakeUntilBlankTooLarge = do + x <- run $ (enumList 1 $ repeat "f") $$ takeUntilBlank 0 id + assertException OverLargeHeader x diff --git a/warp.cabal b/warp.cabal index a91c30b9d..7266cab7f 100644 --- a/warp.cabal +++ b/warp.cabal @@ -25,6 +25,7 @@ Library , enumerator >= 0.4.5 && < 0.5 , blaze-builder >= 0.2.1.4 && < 0.3 , sendfile >= 0.7.2 && < 0.8 + , network-enumerator >= 0.1.1 && < 0.2 if flag(network-bytestring) build-depends: network >= 2.2.1 && < 2.2.3 , network-bytestring >= 0.1.3 && < 0.1.4 From ab5b540c54ab12da3df976255f5d6e3b84774075 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 14 Jan 2011 12:20:27 +0200 Subject: [PATCH 40/90] Optimized sendResponse for ResponseBuilder --- Network/Wai/Handler/Warp.hs | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 3f638bcd6..6599faa06 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -218,19 +218,23 @@ sendResponse req hv socket (ResponseFile s hs fp) = do sendFile socket fp return $ lookup "content-length" hs /= Nothing else return True -sendResponse req hv socket (ResponseBuilder s hs b) = do - toByteStringIO (Sock.sendAll socket) $ - if hasBody s req - then b' - else headers' - return isKeepAlive +sendResponse req hv socket (ResponseBuilder s hs b) + | hasBody s req = do + toByteStringIO (Sock.sendAll socket) b' + return isKeepAlive + | otherwise = do + Sock.sendMany socket + $ L.toChunks + $ toLazyByteString + $ headers hv s hs False + return True where - headers' = headers hv s hs True + headers' = headers hv s hs isChunked' b' = if isChunked' then headers' - `mappend` chunkedTransferEncoding b - `mappend` chunkedTransferTerminator + `mappend` chunkedTransferEncoding b + `mappend` chunkedTransferTerminator else headers hv s hs False `mappend` b hasLength = lookup "content-length" hs /= Nothing isChunked' = isChunked hv && not hasLength From 4030c17d9285cfb23cd30aff09cd559e7e60867f Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 14 Jan 2011 12:41:50 +0200 Subject: [PATCH 41/90] General cleanup, Matt's more efficient headers function --- Network/Wai/Handler/Warp.hs | 60 +++++++++++++++++++++++-------------- runtests.hs | 10 +++---- 2 files changed, 42 insertions(+), 28 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 6599faa06..a4ef9071c 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -22,7 +22,7 @@ module Network.Wai.Handler.Warp , parseRequest #if TEST , takeLineMax - , takeUntilBlank + , takeHeaders , InvalidRequest (..) #endif ) where @@ -59,16 +59,17 @@ import Blaze.ByteString.Builder.HTTP import Blaze.ByteString.Builder (copyByteString, Builder, toLazyByteString, toByteStringIO) import Blaze.ByteString.Builder.Char8 (fromChar, fromShow) -import Data.Monoid (mconcat, mappend) +import Data.Monoid (mappend) import Network.Socket.SendFile (sendFile) import Network.Socket.Enumerator (iterSocket) import Control.Monad.IO.Class (liftIO) import System.Timeout (timeout) import Data.Word (Word8) +import Data.List (foldl') run :: Port -> Application -> IO () -run port = withSocketsDo . +run port = withSocketsDo . -- FIXME should this be called by client user instead? bracket (listenOn $ PortNumber $ fromIntegral port) sClose . @@ -100,7 +101,7 @@ serveConnection port app conn remoteHost' = do parseRequest :: Port -> SockAddr -> E.Iteratee S.ByteString IO (E.Enumeratee ByteString ByteString IO a, Request) parseRequest port remoteHost' = do - headers' <- takeUntilBlank 0 id + headers' <- takeHeaders parseRequest' port headers' remoteHost' -- FIXME come up with good values here @@ -182,27 +183,37 @@ parseFirst s = do let (rpath, qstring) = B.break (== '?') query in return (method, rpath, qstring, hsecond) else E.throwError NonHttp +{-# INLINE parseFirst #-} -- FIXME is this inline necessary? the function is only called from one place and not exported + +httpBuilder, spaceBuilder, newlineBuilder, transferEncodingBuilder + , colonSpaceBuilder :: Builder +httpBuilder = copyByteString "HTTP/" +spaceBuilder = fromChar ' ' +newlineBuilder = copyByteString "\r\n" +transferEncodingBuilder = copyByteString "Transfer-Encoding: chunked\r\n\r\n" +colonSpaceBuilder = copyByteString ": " headers :: HttpVersion -> Status -> ResponseHeaders -> Bool -> Builder -headers httpversion status responseHeaders isChunked' = - copyByteString "HTTP/" - `mappend` copyByteString httpversion - `mappend` fromChar ' ' - `mappend` fromShow (statusCode status) - `mappend` fromChar ' ' - `mappend` copyByteString (statusMessage status) - `mappend` copyByteString "\r\n" - `mappend` mconcat (map go responseHeaders) - `mappend` - if isChunked' - then copyByteString "Transfer-Encoding: chunked\r\n\r\n" - else copyByteString "\r\n" - where - go (x, y) = - copyByteString (ciOriginal x) - `mappend` copyByteString ": " - `mappend` copyByteString y - `mappend` copyByteString "\r\n" +headers !httpversion !status !responseHeaders !isChunked' = {-# SCC "headers" #-} + let !start = httpBuilder + `mappend` copyByteString httpversion + `mappend` spaceBuilder + `mappend` fromShow (statusCode status) + `mappend` spaceBuilder + `mappend` copyByteString (statusMessage status) + `mappend` newlineBuilder + !start' = foldl' responseHeaderToBuilder start responseHeaders + !end = if isChunked' + then transferEncodingBuilder + else newlineBuilder + in start' `mappend` end + +responseHeaderToBuilder :: Builder -> (CIByteString, ByteString) -> Builder +responseHeaderToBuilder b (x, y) = b + `mappend` (copyByteString $ ciOriginal x) + `mappend` colonSpaceBuilder + `mappend` copyByteString y + `mappend` newlineBuilder isChunked :: HttpVersion -> Bool isChunked = (==) http11 @@ -300,6 +311,9 @@ enumSocket _ _ step = E.returnI step ------ The functions below are not warp-specific and could be split out into a --separate package. +takeHeaders :: E.Iteratee ByteString IO [ByteString] +takeHeaders = takeUntilBlank 0 id + takeUntilBlank :: Int -> ([ByteString] -> [ByteString]) -> E.Iteratee S.ByteString IO [ByteString] diff --git a/runtests.hs b/runtests.hs index bfce1d830..0d627d68e 100644 --- a/runtests.hs +++ b/runtests.hs @@ -5,7 +5,7 @@ import Test.Framework (defaultMain, testGroup, Test) import Test.Framework.Providers.HUnit import Test.HUnit hiding (Test) -import Network.Wai.Handler.Warp (takeLineMax, takeUntilBlank, InvalidRequest (..)) +import Network.Wai.Handler.Warp (takeLineMax, takeHeaders, InvalidRequest (..)) import Data.Enumerator (run_, ($$), enumList, run) import Control.Exception (fromException) @@ -23,7 +23,7 @@ testSuite = testGroup "Text.Hamlet" ] caseTakeLineMaxSafe = do - x <- run_ $ (enumList 1 ["f", "oo\n\n", "bar\n\nbaz\n"]) $$ do + x <- run_ $ (enumList 1 ["f", "oo\n\n", "bar\n\r\nbaz\n"]) $$ do a <- takeLineMax 0 id b <- takeLineMax 0 id c <- takeLineMax 0 id @@ -33,7 +33,7 @@ caseTakeLineMaxSafe = do x @?= ("foo", "", "bar", "", "baz") caseTakeUntilBlankSafe = do - x <- run_ $ (enumList 1 ["f", "oo\n", "bar\nbaz\n\n"]) $$ takeUntilBlank 0 id + x <- run_ $ (enumList 1 ["f", "oo\n", "bar\nbaz\n\r\n"]) $$ takeHeaders x @?= ["foo", "bar", "baz"] caseTakeLineMaxUnsafe = do @@ -63,9 +63,9 @@ caseTakeLineMaxIncomplete = do assertException IncompleteHeaders x caseTakeUntilBlankTooMany = do - x <- run $ (enumList 1 $ repeat "f\n") $$ takeUntilBlank 0 id + x <- run $ (enumList 1 $ repeat "f\n") $$ takeHeaders assertException TooManyHeaders x caseTakeUntilBlankTooLarge = do - x <- run $ (enumList 1 $ repeat "f") $$ takeUntilBlank 0 id + x <- run $ (enumList 1 $ repeat "f") $$ takeHeaders assertException OverLargeHeader x From 1354eb4d01ec318ee29ee64b89f2967d8849ea00 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 14 Jan 2011 12:56:37 +0200 Subject: [PATCH 42/90] Fixed rpath and chunked enum --- Network/Wai/Handler/Warp.hs | 10 +++--- pong.hs | 62 +++++++++++++++++++++++++++++++++++-- 2 files changed, 63 insertions(+), 9 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index a4ef9071c..8e2434e7b 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -59,7 +59,7 @@ import Blaze.ByteString.Builder.HTTP import Blaze.ByteString.Builder (copyByteString, Builder, toLazyByteString, toByteStringIO) import Blaze.ByteString.Builder.Char8 (fromChar, fromShow) -import Data.Monoid (mappend) +import Data.Monoid (mappend, mconcat) import Network.Socket.SendFile (sendFile) import Network.Socket.Enumerator (iterSocket) @@ -133,9 +133,7 @@ parseRequest' port (firstLine:otherLines) remoteHost' = do let rpath = if S.null rpath' then "/" - else if '/' == B.head rpath' - then "/" - else B.cons '/' rpath' + else rpath' let heads = map parseHeaderNoAttr otherLines let host = fromMaybe "" $ lookup "host" heads let len = @@ -276,8 +274,8 @@ sendResponse req hv socket (ResponseEnumerator res) = chunk = E.checkDone $ E.continue . step step k E.EOF = k (E.Chunks [chunkedTransferTerminator]) >>== return step k (E.Chunks []) = E.continue $ step k - -- FIXME ensure that the mconcat on the concents here is unnecessary - step k x = k x >>== chunk + step k (E.Chunks [x]) = k (E.Chunks [chunkedTransferEncoding x]) >>== chunk + step k (E.Chunks xs) = k (E.Chunks [chunkedTransferEncoding $ mconcat xs]) >>== chunk parseHeaderNoAttr :: ByteString -> (CIByteString, ByteString) parseHeaderNoAttr s = diff --git a/pong.hs b/pong.hs index 47c594df7..0d3de0f0d 100644 --- a/pong.hs +++ b/pong.hs @@ -1,11 +1,67 @@ {-# LANGUAGE OverloadedStrings #-} import Network.Wai import Network.Wai.Handler.Warp -import Blaze.ByteString.Builder (fromByteString) +import Blaze.ByteString.Builder (copyByteString) +import Data.Monoid +import Data.Enumerator (run_, enumList, ($$)) -main = run 3000 $ const $ return $ ResponseBuilder +main = run 3000 app + +app req = return $ + case pathInfo req of + "/builder/withlen" -> builderWithLen + "/builder/nolen" -> builderNoLen + "/enum/withlen" -> enumWithLen + "/enum/nolen" -> enumNoLen + "/file/withlen" -> fileWithLen + "/file/nolen" -> fileNoLen + _ -> index $ pathInfo req + +builderWithLen = ResponseBuilder status200 [ ("Content-Type", "text/plain") , ("Content-Length", "4") ] - $ fromByteString "PONG" + $ copyByteString "PONG" + +builderNoLen = ResponseBuilder + status200 + [ ("Content-Type", "text/plain") + ] + $ copyByteString "PONG" + +fileWithLen = ResponseFile + status200 + [ ("Content-Type", "text/plain") + , ("Content-Length", "4") + ] + "pong.txt" + +fileNoLen = ResponseFile + status200 + [ ("Content-Type", "text/plain") + ] + "pong.txt" + +enumWithLen = ResponseEnumerator $ \f -> + run_ $ (enumList 1 $ map copyByteString ["P", "O", "NG"]) $$ f + status200 + [ ("Content-Type", "text/plain") + , ("Content-Length", "4") + ] + +enumNoLen = ResponseEnumerator $ \f -> + run_ $ (enumList 1 $ map copyByteString ["P", "O", "NG"]) $$ f + status200 + [ ("Content-Type", "text/plain") + ] + +index p = ResponseBuilder status200 [("Content-Type", "text/html")] $ mconcat $ map copyByteString + [ "

builder withlen

\n" + , "

builder nolen

\n" + , "

enum withlen

\n" + , "

enum nolen

\n" + , "

file withlen

\n" + , "

file nolen

\n" + , p + ] From 7067beea39414e78a44a724b40f38e1492a035ce Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Sat, 15 Jan 2011 18:16:53 +0200 Subject: [PATCH 43/90] Add Matt to author list --- warp.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/warp.cabal b/warp.cabal index 7266cab7f..ab14f8a66 100644 --- a/warp.cabal +++ b/warp.cabal @@ -3,7 +3,7 @@ Version: 0.3.0 Synopsis: A fast, light-weight web server for WAI applications. License: BSD3 License-file: LICENSE -Author: Michael Snoyman +Author: Michael Snoyman, Matt Brown Maintainer: michael@snoyman.com Homepage: http://github.com/snoyberg/warp Category: Web, Yesod From 89a22d88d81a3a5b527216956b6b2b79dadd8b9b Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Mon, 17 Jan 2011 23:21:30 +0200 Subject: [PATCH 44/90] Added some FIXMEs for improvements --- Network/Wai/Handler/Warp.hs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 8e2434e7b..f82dbbd37 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -251,6 +251,7 @@ sendResponse req hv socket (ResponseBuilder s hs b) sendResponse req hv socket (ResponseEnumerator res) = res go where + -- FIXME perhaps alloca a buffer per thread and reuse that in all functiosn below. Should lessen greatly the GC burden (I hope) go s hs | not (hasBody s req) = do liftIO $ Sock.sendMany socket @@ -260,7 +261,7 @@ sendResponse req hv socket (ResponseEnumerator res) = go s hs = chunk' $ E.enumList 1 [headers hv s hs isChunked'] - $$ E.joinI $ builderToByteString + $$ E.joinI $ builderToByteString -- FIXME unsafeBuilderToByteString $$ (iterSocket socket >> return isKeepAlive) where hasLength = lookup "content-length" hs /= Nothing From 164fed3239533de25b184a7d4eb04fadd4ce001f Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Mon, 24 Jan 2011 05:43:33 +0200 Subject: [PATCH 45/90] Exporting Port --- Network/Wai/Handler/Warp.hs | 1 + warp.cabal | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index f82dbbd37..39f25734d 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -18,6 +18,7 @@ --------------------------------------------------------- module Network.Wai.Handler.Warp ( run + , Port , sendResponse , parseRequest #if TEST diff --git a/warp.cabal b/warp.cabal index ab14f8a66..c22e15744 100644 --- a/warp.cabal +++ b/warp.cabal @@ -1,5 +1,5 @@ Name: warp -Version: 0.3.0 +Version: 0.3.0.1 Synopsis: A fast, light-weight web server for WAI applications. License: BSD3 License-file: LICENSE From f0f3409c4bd7b7bc6452eb6241c70387780f67d9 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Tue, 25 Jan 2011 21:00:50 +0200 Subject: [PATCH 46/90] runOnException, serveConnections --- Network/Wai/Handler/Warp.hs | 51 +++++++++++++++++++++++++++---------- warp.cabal | 2 +- 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 39f25734d..f670cfd93 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -4,7 +4,7 @@ {-# LANGUAGE CPP #-} {-# LANGUAGE BangPatterns #-} --------------------------------------------------------- --- | +-- -- Module : Network.Wai.Handler.Warp -- Copyright : Michael Snoyman -- License : BSD3 @@ -16,15 +16,26 @@ -- A fast, light-weight HTTP server handler for WAI. -- --------------------------------------------------------- + +-- | A fast, light-weight HTTP server handler for WAI. Some random notes (a FAQ, if you will): +-- +-- * When a 'ResponseFile' indicates a file which does not exist, an exception +-- is thrown. This will close the connection to the client as well. You should +-- handle file existance checks at the application level. module Network.Wai.Handler.Warp - ( run + ( -- * Run a Warp server + run + , runOnException + , serveConnections + -- * Datatypes , Port + , InvalidRequest (..) + -- * Utility functions for other packages , sendResponse , parseRequest #if TEST , takeLineMax , takeHeaders - , InvalidRequest (..) #endif ) where @@ -69,30 +80,42 @@ import System.Timeout (timeout) import Data.Word (Word8) import Data.List (foldl') +-- | Run an 'Application' on the given port, ignoring all exceptions. run :: Port -> Application -> IO () -run port = withSocketsDo . -- FIXME should this be called by client user instead? +run = runOnException print + +-- | Run an 'Application' on the given port, with the given exception handler. +-- Please note that you will also receive 'InvalidRequest' exceptions. +runOnException :: (SomeException -> IO ()) -> Port -> Application -> IO () +runOnException onE port = withSocketsDo . -- FIXME should this be called by client user instead? bracket (listenOn $ PortNumber $ fromIntegral port) sClose . - serveConnections port + serveConnections onE port + type Port = Int -serveConnections :: Port -> Application -> Socket -> IO () -serveConnections port app socket = do +-- | Runs a server, listening on the given socket. The user is responsible for +-- closing the socket after 'runWithSocket' completes. You must also supply a +-- 'Port' argument for use in the 'serverPort' record; however, this field is +-- only used for informational purposes. If you are in fact listening on a +-- non-TCP socket, this can be a ficticious value. +serveConnections :: (SomeException -> IO ()) + -> Port -> Application -> Socket -> IO () +serveConnections onE port app socket = do (conn, sa) <- accept socket - _ <- forkIO $ serveConnection port app conn sa - serveConnections port app socket + _ <- forkIO $ serveConnection onE port app conn sa + serveConnections onE port app socket -serveConnection :: Port -> Application -> Socket -> SockAddr -> IO () -serveConnection port app conn remoteHost' = do +serveConnection :: (SomeException -> IO ()) + -> Port -> Application -> Socket -> SockAddr -> IO () +serveConnection onException port app conn remoteHost' = do catch (finally (E.run_ $ fromClient $$ serveConnection') (sClose conn)) - ignoreAll + onException where - ignoreAll :: SomeException -> IO () - ignoreAll _ = return () fromClient = enumSocket bytesPerRead conn serveConnection' = do (enumeratee, env) <- parseRequest port remoteHost' diff --git a/warp.cabal b/warp.cabal index c22e15744..d6701eaf8 100644 --- a/warp.cabal +++ b/warp.cabal @@ -1,5 +1,5 @@ Name: warp -Version: 0.3.0.1 +Version: 0.3.1 Synopsis: A fast, light-weight web server for WAI applications. License: BSD3 License-file: LICENSE From 63dddc59301ffa1808845851603c6ee5aeacc785 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Tue, 25 Jan 2011 22:40:25 +0200 Subject: [PATCH 47/90] runInteractive --- Network/Wai/Handler/Warp.hs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index f670cfd93..b03ba19f5 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -26,6 +26,7 @@ module Network.Wai.Handler.Warp ( -- * Run a Warp server run , runOnException + , runInteractive , serveConnections -- * Datatypes , Port @@ -56,7 +57,7 @@ import Network.Socket ) import qualified Network.Socket.ByteString as Sock import Control.Exception (bracket, finally, Exception, SomeException, catch) -import Control.Concurrent (forkIO) +import Control.Concurrent (forkIO, threadDelay) import Data.Maybe (fromMaybe) import Data.Typeable (Typeable) @@ -79,10 +80,12 @@ import Control.Monad.IO.Class (liftIO) import System.Timeout (timeout) import Data.Word (Word8) import Data.List (foldl') +import qualified Control.Concurrent.MVar as MV +import Control.Monad (forever) -- | Run an 'Application' on the given port, ignoring all exceptions. run :: Port -> Application -> IO () -run = runOnException print +run = runOnException (const $ return ()) -- | Run an 'Application' on the given port, with the given exception handler. -- Please note that you will also receive 'InvalidRequest' exceptions. @@ -93,6 +96,22 @@ runOnException onE port = withSocketsDo . -- FIXME should this be called by clie sClose . serveConnections onE port +-- | When using the standard 'run' function on Windows in GHCi, ctrl-C becomes +-- unresponsive. This function should be a drop-in replacement allowing normal +-- operations. This does have a minor performance hit, and therefore this +-- function is recommended only for development, not production. +runInteractive :: Port -> Application -> IO () +runInteractive port app = withSocketsDo $ do + var <- MV.newMVar Nothing + let clean = MV.modifyMVar_ var $ \s -> maybe (return ()) sClose s >> return Nothing + _ <- forkIO $ bracket + (listenOn $ PortNumber $ fromIntegral port) + (const clean) + (\s -> do + MV.modifyMVar_ var (\_ -> return $ Just s) + serveConnections (const $ return ()) port app s) + forever (threadDelay maxBound) `finally` clean + type Port = Int -- | Runs a server, listening on the given socket. The user is responsible for From 43c5821992f344919558326a470e885adce04c2a Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Tue, 25 Jan 2011 22:54:54 +0200 Subject: [PATCH 48/90] runOnException -> runEx --- Network/Wai/Handler/Warp.hs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index b03ba19f5..a62e125d8 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -25,7 +25,7 @@ module Network.Wai.Handler.Warp ( -- * Run a Warp server run - , runOnException + , runEx , runInteractive , serveConnections -- * Datatypes @@ -85,12 +85,12 @@ import Control.Monad (forever) -- | Run an 'Application' on the given port, ignoring all exceptions. run :: Port -> Application -> IO () -run = runOnException (const $ return ()) +run = runEx (const $ return ()) -- | Run an 'Application' on the given port, with the given exception handler. -- Please note that you will also receive 'InvalidRequest' exceptions. -runOnException :: (SomeException -> IO ()) -> Port -> Application -> IO () -runOnException onE port = withSocketsDo . -- FIXME should this be called by client user instead? +runEx :: (SomeException -> IO ()) -> Port -> Application -> IO () +runEx onE port = withSocketsDo . -- FIXME should this be called by client user instead? bracket (listenOn $ PortNumber $ fromIntegral port) sClose . From 8e4a1cbbe7bf4f32c8184ef034e63f1169c55ac8 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 27 Jan 2011 17:13:03 +0200 Subject: [PATCH 49/90] runInteractive is now runEx, for Windows only --- Network/Wai/Handler/Warp.hs | 30 +++++++++++++++--------------- warp.cabal | 2 ++ 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index a62e125d8..9a0682e17 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -26,7 +26,6 @@ module Network.Wai.Handler.Warp ( -- * Run a Warp server run , runEx - , runInteractive , serveConnections -- * Datatypes , Port @@ -57,7 +56,7 @@ import Network.Socket ) import qualified Network.Socket.ByteString as Sock import Control.Exception (bracket, finally, Exception, SomeException, catch) -import Control.Concurrent (forkIO, threadDelay) +import Control.Concurrent (forkIO) import Data.Maybe (fromMaybe) import Data.Typeable (Typeable) @@ -80,8 +79,12 @@ import Control.Monad.IO.Class (liftIO) import System.Timeout (timeout) import Data.Word (Word8) import Data.List (foldl') + +#if WINDOWS +import Control.Concurrent (threadDelay) import qualified Control.Concurrent.MVar as MV import Control.Monad (forever) +#endif -- | Run an 'Application' on the given port, ignoring all exceptions. run :: Port -> Application -> IO () @@ -90,18 +93,8 @@ run = runEx (const $ return ()) -- | Run an 'Application' on the given port, with the given exception handler. -- Please note that you will also receive 'InvalidRequest' exceptions. runEx :: (SomeException -> IO ()) -> Port -> Application -> IO () -runEx onE port = withSocketsDo . -- FIXME should this be called by client user instead? - bracket - (listenOn $ PortNumber $ fromIntegral port) - sClose . - serveConnections onE port - --- | When using the standard 'run' function on Windows in GHCi, ctrl-C becomes --- unresponsive. This function should be a drop-in replacement allowing normal --- operations. This does have a minor performance hit, and therefore this --- function is recommended only for development, not production. -runInteractive :: Port -> Application -> IO () -runInteractive port app = withSocketsDo $ do +#if WINDOWS +runEx onE port app = withSocketsDo $ do var <- MV.newMVar Nothing let clean = MV.modifyMVar_ var $ \s -> maybe (return ()) sClose s >> return Nothing _ <- forkIO $ bracket @@ -109,8 +102,15 @@ runInteractive port app = withSocketsDo $ do (const clean) (\s -> do MV.modifyMVar_ var (\_ -> return $ Just s) - serveConnections (const $ return ()) port app s) + serveConnections onE port app s) forever (threadDelay maxBound) `finally` clean +#else +runEx onE port = withSocketsDo . -- FIXME should this be called by client user instead? + bracket + (listenOn $ PortNumber $ fromIntegral port) + sClose . + serveConnections onE port +#endif type Port = Int diff --git a/warp.cabal b/warp.cabal index d6701eaf8..9cb6e548b 100644 --- a/warp.cabal +++ b/warp.cabal @@ -35,6 +35,8 @@ Library ghc-options: -Wall if !flag(timeout-protection) Cpp-options: -DNO_TIMEOUT_PROTECTION + if os(windows) + Cpp-options: -DWINDOWS source-repository head type: git From c09d83377b68e8ae2afa368ce6c5d77f57b53214 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 3 Feb 2011 13:28:01 +0200 Subject: [PATCH 50/90] throwIO --- Network/Wai/Handler/Warp.hs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 9a0682e17..d42295465 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -55,7 +55,7 @@ import Network.Socket ( accept, SockAddr ) import qualified Network.Socket.ByteString as Sock -import Control.Exception (bracket, finally, Exception, SomeException, catch) +import Control.Exception (bracket, finally, Exception, SomeException, catch, throwIO) import Control.Concurrent (forkIO) import Data.Maybe (fromMaybe) @@ -170,7 +170,7 @@ parseRequest' :: Port -> [ByteString] -> SockAddr -> E.Iteratee S.ByteString IO (E.Enumeratee S.ByteString S.ByteString IO a, Request) -parseRequest' _ [] _ = E.throwError $ NotEnoughLines [] +parseRequest' _ [] _ = throwError $ NotEnoughLines [] parseRequest' port (firstLine:otherLines) remoteHost' = do (method, rpath', gets, httpversion) <- parseFirst firstLine let rpath = @@ -217,13 +217,13 @@ parseFirst s = do (method, query, http') <- case pieces of [x, y, z] -> return (x, y, z) - _ -> E.throwError $ BadFirstLine $ B.unpack s + _ -> throwError $ BadFirstLine $ B.unpack s let (hfirst, hsecond) = B.splitAt 5 http' if (hfirst == "HTTP/") then let (rpath, qstring) = B.break (== '?') query in return (method, rpath, qstring, hsecond) - else E.throwError NonHttp + else throwError NonHttp {-# INLINE parseFirst #-} -- FIXME is this inline necessary? the function is only called from one place and not exported httpBuilder, spaceBuilder, newlineBuilder, transferEncodingBuilder @@ -341,7 +341,7 @@ enumSocket len socket (E.Continue k) = do #else mbs <- liftIO $ timeout readTimeout $ Sock.recv socket len case mbs of - Nothing -> E.throwError SocketTimeout + Nothing -> throwError SocketTimeout Just bs -> go bs #endif where @@ -360,7 +360,7 @@ takeUntilBlank :: Int -> ([ByteString] -> [ByteString]) -> E.Iteratee S.ByteString IO [ByteString] takeUntilBlank count _ - | count > maxHeaders = E.throwError TooManyHeaders + | count > maxHeaders = throwError TooManyHeaders takeUntilBlank count front = do l <- takeLineMax 0 id if B.null l @@ -373,7 +373,7 @@ takeLineMax :: Int takeLineMax len front = do mbs <- EL.head case mbs of - Nothing -> E.throwError IncompleteHeaders + Nothing -> throwError IncompleteHeaders Just bs -> do let (x, y) = S.breakByte 10 bs x' = if S.length x > 0 && S.last x == 13 @@ -382,8 +382,10 @@ takeLineMax len front = do let len' = len + B.length x case () of () - | len' > maxHeaderLength -> E.throwError OverLargeHeader + | len' > maxHeaderLength -> throwError OverLargeHeader | B.null y -> takeLineMax len' $ front . (:) x | otherwise -> do E.yield () $ E.Chunks [B.drop 1 y] return $ B.concat $ front [x'] + +throwError = liftIO . throwIO From 2339bb5d5b2e5fd57b4d39b999da1ef494671e7e Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 3 Feb 2011 18:07:09 +0200 Subject: [PATCH 51/90] List-based timeouts --- Network/Wai/Handler/Warp.hs | 108 ++++++++++++++++++++---------------- Timeout.hs | 43 ++++++++++++++ warp.cabal | 3 +- 3 files changed, 106 insertions(+), 48 deletions(-) create mode 100644 Timeout.hs diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index d42295465..219ff53ad 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -48,14 +48,12 @@ import qualified Data.ByteString as S import qualified Data.ByteString.Unsafe as SU import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy as L -import Network - ( listenOn, sClose, PortID(PortNumber), Socket - , withSocketsDo) +import Network (listenOn, sClose, PortID(PortNumber), Socket) import Network.Socket ( accept, SockAddr ) import qualified Network.Socket.ByteString as Sock -import Control.Exception (bracket, finally, Exception, SomeException, catch, throwIO) +import Control.Exception (bracket, finally, Exception, SomeException, catch) import Control.Concurrent (forkIO) import Data.Maybe (fromMaybe) @@ -73,17 +71,17 @@ import Blaze.ByteString.Builder import Blaze.ByteString.Builder.Char8 (fromChar, fromShow) import Data.Monoid (mappend, mconcat) import Network.Socket.SendFile (sendFile) -import Network.Socket.Enumerator (iterSocket) import Control.Monad.IO.Class (liftIO) -import System.Timeout (timeout) +import qualified Timeout as T import Data.Word (Word8) import Data.List (foldl') +import Control.Monad (forever) #if WINDOWS import Control.Concurrent (threadDelay) import qualified Control.Concurrent.MVar as MV -import Control.Monad (forever) +import Network.Socket (withSocketsDo) #endif -- | Run an 'Application' on the given port, ignoring all exceptions. @@ -105,7 +103,7 @@ runEx onE port app = withSocketsDo $ do serveConnections onE port app s) forever (threadDelay maxBound) `finally` clean #else -runEx onE port = withSocketsDo . -- FIXME should this be called by client user instead? +runEx onE port = bracket (listenOn $ PortNumber $ fromIntegral port) sClose . @@ -122,24 +120,32 @@ type Port = Int serveConnections :: (SomeException -> IO ()) -> Port -> Application -> Socket -> IO () serveConnections onE port app socket = do - (conn, sa) <- accept socket - _ <- forkIO $ serveConnection onE port app conn sa - serveConnections onE port app socket - -serveConnection :: (SomeException -> IO ()) + tm <- T.initialize $ timeout * 1000000 + forever $ do + (conn, sa) <- accept socket + _ <- forkIO $ do + th <- T.register tm $ sClose conn + serveConnection th onE port app conn sa + return () + +serveConnection :: T.Handle + -> (SomeException -> IO ()) -> Port -> Application -> Socket -> SockAddr -> IO () -serveConnection onException port app conn remoteHost' = do +serveConnection th onException port app conn remoteHost' = do catch (finally (E.run_ $ fromClient $$ serveConnection') (sClose conn)) onException where - fromClient = enumSocket bytesPerRead conn + fromClient = enumSocket th bytesPerRead conn serveConnection' = do (enumeratee, env) <- parseRequest port remoteHost' + -- Let the application run for as long as it wants + --FIXME liftIO $ T.pause th res <- E.joinI $ enumeratee $$ app env - keepAlive <- liftIO $ sendResponse env (httpVersion env) conn res + --liftIO $ T.resume th + keepAlive <- liftIO $ sendResponse th env (httpVersion env) conn res if keepAlive then serveConnection' else return () parseRequest :: Port -> SockAddr -> E.Iteratee S.ByteString IO (E.Enumeratee ByteString ByteString IO a, Request) @@ -148,11 +154,11 @@ parseRequest port remoteHost' = do parseRequest' port headers' remoteHost' -- FIXME come up with good values here -maxHeaders, maxHeaderLength, bytesPerRead, readTimeout :: Int +maxHeaders, maxHeaderLength, bytesPerRead, timeout :: Int maxHeaders = 30 maxHeaderLength = 1024 bytesPerRead = 4096 -readTimeout = 3000000 +timeout = 1 -- seconds data InvalidRequest = NotEnoughLines [String] @@ -170,7 +176,7 @@ parseRequest' :: Port -> [ByteString] -> SockAddr -> E.Iteratee S.ByteString IO (E.Enumeratee S.ByteString S.ByteString IO a, Request) -parseRequest' _ [] _ = throwError $ NotEnoughLines [] +parseRequest' _ [] _ = E.throwError $ NotEnoughLines [] parseRequest' port (firstLine:otherLines) remoteHost' = do (method, rpath', gets, httpversion) <- parseFirst firstLine let rpath = @@ -217,13 +223,13 @@ parseFirst s = do (method, query, http') <- case pieces of [x, y, z] -> return (x, y, z) - _ -> throwError $ BadFirstLine $ B.unpack s + _ -> E.throwError $ BadFirstLine $ B.unpack s let (hfirst, hsecond) = B.splitAt 5 http' if (hfirst == "HTTP/") then let (rpath, qstring) = B.break (== '?') query in return (method, rpath, qstring, hsecond) - else throwError NonHttp + else E.throwError NonHttp {-# INLINE parseFirst #-} -- FIXME is this inline necessary? the function is only called from one place and not exported httpBuilder, spaceBuilder, newlineBuilder, transferEncodingBuilder @@ -262,15 +268,16 @@ isChunked = (==) http11 hasBody :: Status -> Request -> Bool hasBody s req = s /= (Status 204 "") && requestMethod req /= "HEAD" -sendResponse :: Request -> HttpVersion -> Socket -> Response -> IO Bool -sendResponse req hv socket (ResponseFile s hs fp) = do +sendResponse :: T.Handle + -> Request -> HttpVersion -> Socket -> Response -> IO Bool +sendResponse _FIXMEth req hv socket (ResponseFile s hs fp) = do Sock.sendMany socket $ L.toChunks $ toLazyByteString $ headers hv s hs False if hasBody s req then do - sendFile socket fp + sendFile socket fp -- FIXME tickle the timeout here return $ lookup "content-length" hs /= Nothing else return True -sendResponse req hv socket (ResponseBuilder s hs b) +sendResponse _FIXMEth req hv socket (ResponseBuilder s hs b) | hasBody s req = do toByteStringIO (Sock.sendAll socket) b' return isKeepAlive @@ -291,7 +298,7 @@ sendResponse req hv socket (ResponseBuilder s hs b) hasLength = lookup "content-length" hs /= Nothing isChunked' = isChunked hv && not hasLength isKeepAlive = isChunked' || hasLength -sendResponse req hv socket (ResponseEnumerator res) = +sendResponse th req hv socket (ResponseEnumerator res) = res go where -- FIXME perhaps alloca a buffer per thread and reuse that in all functiosn below. Should lessen greatly the GC burden (I hope) @@ -305,7 +312,7 @@ sendResponse req hv socket (ResponseEnumerator res) = chunk' $ E.enumList 1 [headers hv s hs isChunked'] $$ E.joinI $ builderToByteString -- FIXME unsafeBuilderToByteString - $$ (iterSocket socket >> return isKeepAlive) + $$ (iterSocket th socket >> return isKeepAlive) where hasLength = lookup "content-length" hs /= Nothing isChunked' = isChunked hv && not hasLength @@ -331,24 +338,20 @@ parseHeaderNoAttr s = else rest in (mkCIByteString k, rest') --- FIXME when we switch to Jeremy's timeout code, we can probably start using --- network-enumerator's enumSocket -enumSocket :: Int -> Socket -> E.Enumerator ByteString IO a -enumSocket len socket (E.Continue k) = do -#if NO_TIMEOUT_PROTECTION - bs <- liftIO $ Sock.recv socket len - go bs -#else - mbs <- liftIO $ timeout readTimeout $ Sock.recv socket len - case mbs of - Nothing -> throwError SocketTimeout - Just bs -> go bs -#endif +enumSocket :: T.Handle -> Int -> Socket -> E.Enumerator ByteString IO a +enumSocket th len socket = + inner where - go bs + inner (E.Continue k) = do + bs <- liftIO $ Sock.recv socket len + liftIO $ T.tickle th + if S.null bs + then E.throwError SocketTimeout + else go k bs + inner step = E.returnI step + go k bs | S.length bs == 0 = E.continue k - | otherwise = k (E.Chunks [bs]) >>== enumSocket len socket -enumSocket _ _ step = E.returnI step + | otherwise = k (E.Chunks [bs]) >>== enumSocket th len socket ------ The functions below are not warp-specific and could be split out into a --separate package. @@ -360,7 +363,7 @@ takeUntilBlank :: Int -> ([ByteString] -> [ByteString]) -> E.Iteratee S.ByteString IO [ByteString] takeUntilBlank count _ - | count > maxHeaders = throwError TooManyHeaders + | count > maxHeaders = E.throwError TooManyHeaders takeUntilBlank count front = do l <- takeLineMax 0 id if B.null l @@ -373,7 +376,7 @@ takeLineMax :: Int takeLineMax len front = do mbs <- EL.head case mbs of - Nothing -> throwError IncompleteHeaders + Nothing -> E.throwError IncompleteHeaders Just bs -> do let (x, y) = S.breakByte 10 bs x' = if S.length x > 0 && S.last x == 13 @@ -382,10 +385,21 @@ takeLineMax len front = do let len' = len + B.length x case () of () - | len' > maxHeaderLength -> throwError OverLargeHeader + | len' > maxHeaderLength -> E.throwError OverLargeHeader | B.null y -> takeLineMax len' $ front . (:) x | otherwise -> do E.yield () $ E.Chunks [B.drop 1 y] return $ B.concat $ front [x'] -throwError = liftIO . throwIO +iterSocket :: T.Handle + -> Socket + -> E.Iteratee B.ByteString IO () +iterSocket th sock = + E.continue step + where + step E.EOF = E.yield () E.EOF + step (E.Chunks []) = E.continue step + step (E.Chunks xs) = do + liftIO $ Sock.sendMany sock xs + liftIO $ T.tickle th + E.continue step diff --git a/Timeout.hs b/Timeout.hs new file mode 100644 index 000000000..253ecab29 --- /dev/null +++ b/Timeout.hs @@ -0,0 +1,43 @@ +module Timeout + ( Manager + , Handle + , initialize + , register + , tickle + ) where + +import qualified Data.IORef as I +import Control.Concurrent (forkIO, threadDelay) +import Control.Monad (forever) + +newtype Manager = Manager (I.IORef [Handle]) +data Handle = Handle (IO ()) (I.IORef Bool) + +initialize :: Int -> IO Manager +initialize timeout = do + ref <- I.newIORef [] + _ <- forkIO $ forever $ do + threadDelay timeout + ms <- I.atomicModifyIORef ref (\x -> ([], x)) + ms' <- go ms id + I.atomicModifyIORef ref (\x -> (ms' x, ())) + return $ Manager ref + where + go [] front = return front + go (m@(Handle onTimeout iactive):rest) front = do + active <- I.atomicModifyIORef iactive (\x -> (False, x)) + if active + then go rest (front . (:) m) + else do + onTimeout + go rest front + +register :: Manager -> IO () -> IO Handle +register (Manager ref) onTimeout = do + iactive <- I.newIORef True + let h = Handle onTimeout iactive + I.atomicModifyIORef ref (\x -> (h : x, ())) + return h + +tickle :: Handle -> IO () +tickle (Handle _ iactive) = I.writeIORef iactive True diff --git a/warp.cabal b/warp.cabal index 9cb6e548b..f356a4495 100644 --- a/warp.cabal +++ b/warp.cabal @@ -1,5 +1,5 @@ Name: warp -Version: 0.3.1 +Version: 0.3.2 Synopsis: A fast, light-weight web server for WAI applications. License: BSD3 License-file: LICENSE @@ -32,6 +32,7 @@ Library else build-depends: network >= 2.3 && < 2.4 Exposed-modules: Network.Wai.Handler.Warp + Other-modules: Timeout ghc-options: -Wall if !flag(timeout-protection) Cpp-options: -DNO_TIMEOUT_PROTECTION From c5b29e63379fba8e5351cf526620bf6c0538c5a6 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 4 Feb 2011 14:30:08 +0200 Subject: [PATCH 52/90] pause/resume timeouts --- Network/Wai/Handler/Warp.hs | 4 ++-- Timeout.hs | 29 ++++++++++++++++++++--------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 219ff53ad..eaeb81fa4 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -142,9 +142,9 @@ serveConnection th onException port app conn remoteHost' = do serveConnection' = do (enumeratee, env) <- parseRequest port remoteHost' -- Let the application run for as long as it wants - --FIXME liftIO $ T.pause th + liftIO $ T.pause th res <- E.joinI $ enumeratee $$ app env - --liftIO $ T.resume th + liftIO $ T.resume th keepAlive <- liftIO $ sendResponse th env (httpVersion env) conn res if keepAlive then serveConnection' else return () diff --git a/Timeout.hs b/Timeout.hs index 253ecab29..4a515e269 100644 --- a/Timeout.hs +++ b/Timeout.hs @@ -4,14 +4,18 @@ module Timeout , initialize , register , tickle + , pause + , resume ) where import qualified Data.IORef as I import Control.Concurrent (forkIO, threadDelay) import Control.Monad (forever) +import qualified Control.Exception as E newtype Manager = Manager (I.IORef [Handle]) -data Handle = Handle (IO ()) (I.IORef Bool) +data Handle = Handle (IO ()) (I.IORef State) +data State = Active | Inactive | Paused initialize :: Int -> IO Manager initialize timeout = do @@ -25,19 +29,26 @@ initialize timeout = do where go [] front = return front go (m@(Handle onTimeout iactive):rest) front = do - active <- I.atomicModifyIORef iactive (\x -> (False, x)) - if active - then go rest (front . (:) m) - else do - onTimeout + state <- I.atomicModifyIORef iactive (\x -> (go' x, x)) + case state of + Inactive -> do + onTimeout `E.catch` ignoreAll go rest front + _ -> go rest (front . (:) m) + go' Active = Inactive + go' x = x + +ignoreAll :: E.SomeException -> IO () +ignoreAll _ = return () register :: Manager -> IO () -> IO Handle register (Manager ref) onTimeout = do - iactive <- I.newIORef True + iactive <- I.newIORef Active let h = Handle onTimeout iactive I.atomicModifyIORef ref (\x -> (h : x, ())) return h -tickle :: Handle -> IO () -tickle (Handle _ iactive) = I.writeIORef iactive True +tickle, pause, resume :: Handle -> IO () +tickle (Handle _ iactive) = I.writeIORef iactive Active +pause (Handle _ iactive) = I.writeIORef iactive Paused +resume = tickle From 04d6c1d44addc1e05f526d98c80b6a66edbecf2c Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Sun, 6 Feb 2011 17:13:07 +0200 Subject: [PATCH 53/90] runSettings --- Network/Wai/Handler/Warp.hs | 66 ++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 11 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index eaeb81fa4..d427da8cb 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -27,6 +27,13 @@ module Network.Wai.Handler.Warp run , runEx , serveConnections + -- * Run a Warp server with full settings control + , runSettings + , Settings + , defaultSettings + , settingsPort + , settingsOnException + , settingsTimeout -- * Datatypes , Port , InvalidRequest (..) @@ -53,7 +60,10 @@ import Network.Socket ( accept, SockAddr ) import qualified Network.Socket.ByteString as Sock -import Control.Exception (bracket, finally, Exception, SomeException, catch) +import Control.Exception + ( bracket, finally, Exception, SomeException, catch + , fromException + ) import Control.Concurrent (forkIO) import Data.Maybe (fromMaybe) @@ -91,23 +101,30 @@ run = runEx (const $ return ()) -- | Run an 'Application' on the given port, with the given exception handler. -- Please note that you will also receive 'InvalidRequest' exceptions. runEx :: (SomeException -> IO ()) -> Port -> Application -> IO () +runEx onE port = runSettings Settings + { settingsPort = port + , settingsOnException = onE + , settingsTimeout = 30 + } + +runSettings :: Settings -> Application -> IO () #if WINDOWS -runEx onE port app = withSocketsDo $ do +runSettings set app = withSocketsDo $ do var <- MV.newMVar Nothing let clean = MV.modifyMVar_ var $ \s -> maybe (return ()) sClose s >> return Nothing _ <- forkIO $ bracket - (listenOn $ PortNumber $ fromIntegral port) + (listenOn $ PortNumber $ fromIntegral $ settingsPort set) (const clean) (\s -> do MV.modifyMVar_ var (\_ -> return $ Just s) - serveConnections onE port app s) + serveConnections' set app s) forever (threadDelay maxBound) `finally` clean #else -runEx onE port = +runSettings set = bracket - (listenOn $ PortNumber $ fromIntegral port) + (listenOn $ PortNumber $ fromIntegral $ settingsPort set) sClose . - serveConnections onE port + serveConnections' set #endif type Port = Int @@ -119,8 +136,17 @@ type Port = Int -- non-TCP socket, this can be a ficticious value. serveConnections :: (SomeException -> IO ()) -> Port -> Application -> Socket -> IO () -serveConnections onE port app socket = do - tm <- T.initialize $ timeout * 1000000 +serveConnections onE port = serveConnections' defaultSettings + { settingsOnException = onE + , settingsPort = port + } + +serveConnections' :: Settings + -> Application -> Socket -> IO () +serveConnections' set app socket = do + let onE = settingsOnException set + port = settingsPort set + tm <- T.initialize $ settingsTimeout set * 1000000 forever $ do (conn, sa) <- accept socket _ <- forkIO $ do @@ -154,11 +180,10 @@ parseRequest port remoteHost' = do parseRequest' port headers' remoteHost' -- FIXME come up with good values here -maxHeaders, maxHeaderLength, bytesPerRead, timeout :: Int +maxHeaders, maxHeaderLength, bytesPerRead :: Int maxHeaders = 30 maxHeaderLength = 1024 bytesPerRead = 4096 -timeout = 1 -- seconds data InvalidRequest = NotEnoughLines [String] @@ -403,3 +428,22 @@ iterSocket th sock = liftIO $ Sock.sendMany sock xs liftIO $ T.tickle th E.continue step + +data Settings = Settings + { settingsPort :: Int + , settingsOnException :: SomeException -> IO () + , settingsTimeout :: Int -- ^ seconds + } + +defaultSettings :: Settings +defaultSettings = Settings + { settingsPort = 3000 + , settingsOnException = \e -> + case fromException e of + Just x -> go x + Nothing -> print e + , settingsTimeout = 30 + } + where + go :: InvalidRequest -> IO () + go _ = return () From dbfb7505258ff7ef079f93bc91d81aee4fc51a90 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Sun, 6 Feb 2011 17:18:18 +0200 Subject: [PATCH 54/90] Added cancel to Timeout --- Network/Wai/Handler/Warp.hs | 1 + Timeout.hs | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index d427da8cb..14f12fd35 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -152,6 +152,7 @@ serveConnections' set app socket = do _ <- forkIO $ do th <- T.register tm $ sClose conn serveConnection th onE port app conn sa + T.cancel th return () serveConnection :: T.Handle diff --git a/Timeout.hs b/Timeout.hs index 4a515e269..ad5e22ce5 100644 --- a/Timeout.hs +++ b/Timeout.hs @@ -6,6 +6,7 @@ module Timeout , tickle , pause , resume + , cancel ) where import qualified Data.IORef as I @@ -13,9 +14,11 @@ import Control.Concurrent (forkIO, threadDelay) import Control.Monad (forever) import qualified Control.Exception as E +-- FIXME implement stopManager + newtype Manager = Manager (I.IORef [Handle]) data Handle = Handle (IO ()) (I.IORef State) -data State = Active | Inactive | Paused +data State = Active | Inactive | Paused | Canceled initialize :: Int -> IO Manager initialize timeout = do @@ -34,6 +37,7 @@ initialize timeout = do Inactive -> do onTimeout `E.catch` ignoreAll go rest front + Canceled -> go rest front _ -> go rest (front . (:) m) go' Active = Inactive go' x = x @@ -48,7 +52,8 @@ register (Manager ref) onTimeout = do I.atomicModifyIORef ref (\x -> (h : x, ())) return h -tickle, pause, resume :: Handle -> IO () +tickle, pause, resume, cancel :: Handle -> IO () tickle (Handle _ iactive) = I.writeIORef iactive Active pause (Handle _ iactive) = I.writeIORef iactive Paused resume = tickle +cancel (Handle _ iactive) = I.writeIORef iactive Canceled From 80e4c143cafab753af8381f5b195f739867a8372 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Sun, 6 Feb 2011 17:24:50 +0200 Subject: [PATCH 55/90] Tickling sendFile responses --- Network/Wai/Handler/Warp.hs | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 14f12fd35..cfbadab97 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -64,7 +64,7 @@ import Control.Exception ( bracket, finally, Exception, SomeException, catch , fromException ) -import Control.Concurrent (forkIO) +import Control.Concurrent (forkIO, threadWaitWrite) import Data.Maybe (fromMaybe) import Data.Typeable (Typeable) @@ -80,7 +80,7 @@ import Blaze.ByteString.Builder (copyByteString, Builder, toLazyByteString, toByteStringIO) import Blaze.ByteString.Builder.Char8 (fromChar, fromShow) import Data.Monoid (mappend, mconcat) -import Network.Socket.SendFile (sendFile) +import Network.Socket.SendFile (sendFileIterWith, Iter (..)) import Control.Monad.IO.Class (liftIO) import qualified Timeout as T @@ -186,6 +186,9 @@ maxHeaders = 30 maxHeaderLength = 1024 bytesPerRead = 4096 +sendFileCount :: Integer +sendFileCount = 65536 + data InvalidRequest = NotEnoughLines [String] | BadFirstLine String @@ -296,13 +299,23 @@ hasBody s req = s /= (Status 204 "") && requestMethod req /= "HEAD" sendResponse :: T.Handle -> Request -> HttpVersion -> Socket -> Response -> IO Bool -sendResponse _FIXMEth req hv socket (ResponseFile s hs fp) = do +sendResponse th req hv socket (ResponseFile s hs fp) = do Sock.sendMany socket $ L.toChunks $ toLazyByteString $ headers hv s hs False if hasBody s req then do - sendFile socket fp -- FIXME tickle the timeout here + sendFileIterWith tickler socket fp sendFileCount return $ lookup "content-length" hs /= Nothing else return True + where + tickler iter = do + r <- iter + case r of + Done _ -> return () + Sent _ cont -> T.tickle th >> tickler cont + WouldBlock _ fd cont -> do + -- FIXME do we want to tickle here? + threadWaitWrite fd + tickler cont sendResponse _FIXMEth req hv socket (ResponseBuilder s hs b) | hasBody s req = do toByteStringIO (Sock.sendAll socket) b' From 59d9cc529013e54ecf57a125d731126b2ba02284 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Sun, 6 Feb 2011 17:25:54 +0200 Subject: [PATCH 56/90] Tickling ResponseBuilder --- Network/Wai/Handler/Warp.hs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index cfbadab97..892480b5c 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -316,15 +316,18 @@ sendResponse th req hv socket (ResponseFile s hs fp) = do -- FIXME do we want to tickle here? threadWaitWrite fd tickler cont -sendResponse _FIXMEth req hv socket (ResponseBuilder s hs b) +sendResponse th req hv socket (ResponseBuilder s hs b) | hasBody s req = do - toByteStringIO (Sock.sendAll socket) b' + toByteStringIO (\bs -> do + Sock.sendAll socket bs + T.tickle th) b' return isKeepAlive | otherwise = do Sock.sendMany socket $ L.toChunks $ toLazyByteString $ headers hv s hs False + T.tickle th return True where headers' = headers hv s hs isChunked' From 199f8f5e98a3f5a79a7f253400cc06d520ec5b02 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 11 Feb 2011 10:01:28 +0200 Subject: [PATCH 57/90] Timeout manager kills threads, does not close sockets --- Network/Wai/Handler/Warp.hs | 2 +- Timeout.hs | 8 +++++++- warp.cabal | 2 +- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 892480b5c..c3e90c88b 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -150,7 +150,7 @@ serveConnections' set app socket = do forever $ do (conn, sa) <- accept socket _ <- forkIO $ do - th <- T.register tm $ sClose conn + th <- T.registerKillThread tm serveConnection th onE port app conn sa T.cancel th return () diff --git a/Timeout.hs b/Timeout.hs index ad5e22ce5..7775537d5 100644 --- a/Timeout.hs +++ b/Timeout.hs @@ -3,6 +3,7 @@ module Timeout , Handle , initialize , register + , registerKillThread , tickle , pause , resume @@ -10,7 +11,7 @@ module Timeout ) where import qualified Data.IORef as I -import Control.Concurrent (forkIO, threadDelay) +import Control.Concurrent (forkIO, threadDelay, myThreadId, killThread) import Control.Monad (forever) import qualified Control.Exception as E @@ -52,6 +53,11 @@ register (Manager ref) onTimeout = do I.atomicModifyIORef ref (\x -> (h : x, ())) return h +registerKillThread :: Manager -> IO Handle +registerKillThread m = do + tid <- myThreadId + register m $ killThread tid + tickle, pause, resume, cancel :: Handle -> IO () tickle (Handle _ iactive) = I.writeIORef iactive Active pause (Handle _ iactive) = I.writeIORef iactive Paused diff --git a/warp.cabal b/warp.cabal index f356a4495..b68114177 100644 --- a/warp.cabal +++ b/warp.cabal @@ -1,5 +1,5 @@ Name: warp -Version: 0.3.2 +Version: 0.3.2.1 Synopsis: A fast, light-weight web server for WAI applications. License: BSD3 License-file: LICENSE From aca7bfba18332bb70cc7b3e82af2c790a4339629 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 11 Feb 2011 10:06:14 +0200 Subject: [PATCH 58/90] Using Matt's new takeHeaders code --- Network/Wai/Handler/Warp.hs | 72 ++++++++++++++++++++++++++++++++++--- runtests.hs | 2 +- 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index c3e90c88b..1d07061fd 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -181,10 +181,11 @@ parseRequest port remoteHost' = do parseRequest' port headers' remoteHost' -- FIXME come up with good values here -maxHeaders, maxHeaderLength, bytesPerRead :: Int +maxHeaders, maxHeaderLength, bytesPerRead, maxTotalHeaderLength :: Int maxHeaders = 30 maxHeaderLength = 1024 bytesPerRead = 4096 +maxTotalHeaderLength = 50 * 1024 sendFileCount :: Integer sendFileCount = 65536 @@ -398,9 +399,6 @@ enumSocket th len socket = ------ The functions below are not warp-specific and could be split out into a --separate package. -takeHeaders :: E.Iteratee ByteString IO [ByteString] -takeHeaders = takeUntilBlank 0 id - takeUntilBlank :: Int -> ([ByteString] -> [ByteString]) -> E.Iteratee S.ByteString IO [ByteString] @@ -464,3 +462,69 @@ defaultSettings = Settings where go :: InvalidRequest -> IO () go _ = return () + +takeHeaders = do + !x <- forceHead + takeHeaders' 0 id id x + +{-# INLINE takeHeaders #-} + +takeHeaders' :: Int + -> ([ByteString] -> [ByteString]) + -> ([ByteString] -> [ByteString]) + -> ByteString + -> E.Iteratee S.ByteString IO [ByteString] +takeHeaders' !len _ _ _ | len > maxTotalHeaderLength = E.throwError OverLargeHeader +takeHeaders' !len !lines !prepend !bs = do + let !bsLen = {-# SCC "takeHeaders'.bsLen" #-} S.length bs + !mnl = {-# SCC "takeHeaders'.mnl" #-} S.elemIndex 10 bs + case mnl of + -- no newline. prepend entire bs to next line + !Nothing -> {-# SCC "takeHeaders'.noNewline" #-} do + let !len' = len + bsLen + !more <- forceHead + takeHeaders' len' lines (prepend . (:) bs) more + Just !nl -> {-# SCC "takeHeaders'.newline" #-} do + let !end = nl + !start = nl + 1 + !line = {-# SCC "takeHeaders'.line" #-} + if end > 0 + -- line data included in this chunk + then S.concat $! prepend [SU.unsafeTake (checkCR bs end) bs] + --then S.concat $! prepend [SU.unsafeTake (end-1) bs] + -- no line data in this chunk (all in prepend, or empty line) + else S.concat $! prepend [] + if S.null line + -- no more headers + then {-# SCC "takeHeaders'.noMoreHeaders" #-} do + let !lines' = {-# SCC "takeHeaders'.noMoreHeaders.lines'" #-} lines [] + if start < bsLen + then {-# SCC "takeHeaders'.noMoreHeaders.yield" #-} do + let !rest = {-# SCC "takeHeaders'.noMoreHeaders.yield.rest" #-} SU.unsafeDrop start bs + E.yield lines' $! E.Chunks [rest] + else return lines' + + -- more headers + else {-# SCC "takeHeaders'.moreHeaders" #-} do + let !len' = len + start + !lines' = {-# SCC "takeHeaders.lines'" #-} lines . (:) line + !more <- {-# SCC "takeHeaders'.more" #-} + if start < bsLen + then return $! SU.unsafeDrop start bs + else forceHead + {-# SCC "takeHeaders'.takeMore" #-} takeHeaders' len' lines' id more +{-# INLINE takeHeaders' #-} + +forceHead = do + !mx <- E.head + case mx of + !Nothing -> E.throwError IncompleteHeaders + Just !x -> return x +{-# INLINE forceHead #-} + +checkCR bs pos = + let !p = pos - 1 + in if '\r' == B.index bs p + then p + else pos +{-# INLINE checkCR #-} diff --git a/runtests.hs b/runtests.hs index 0d627d68e..9f6fba9d2 100644 --- a/runtests.hs +++ b/runtests.hs @@ -64,7 +64,7 @@ caseTakeLineMaxIncomplete = do caseTakeUntilBlankTooMany = do x <- run $ (enumList 1 $ repeat "f\n") $$ takeHeaders - assertException TooManyHeaders x + assertException OverLargeHeader x caseTakeUntilBlankTooLarge = do x <- run $ (enumList 1 $ repeat "f") $$ takeHeaders From 130be2b31891bcfc0ee9349ee306173856abab5a Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 11 Feb 2011 10:11:58 +0200 Subject: [PATCH 59/90] Code cleanup --- Network/Wai/Handler/Warp.hs | 44 +++++-------------------------------- runtests.hs | 37 ++----------------------------- 2 files changed, 8 insertions(+), 73 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 1d07061fd..fd760cc29 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -41,12 +41,11 @@ module Network.Wai.Handler.Warp , sendResponse , parseRequest #if TEST - , takeLineMax , takeHeaders #endif ) where -import Prelude hiding (catch) +import Prelude hiding (catch, lines) import Network.Wai import qualified System.IO @@ -181,9 +180,7 @@ parseRequest port remoteHost' = do parseRequest' port headers' remoteHost' -- FIXME come up with good values here -maxHeaders, maxHeaderLength, bytesPerRead, maxTotalHeaderLength :: Int -maxHeaders = 30 -maxHeaderLength = 1024 +bytesPerRead, maxTotalHeaderLength :: Int bytesPerRead = 4096 maxTotalHeaderLength = 50 * 1024 @@ -399,38 +396,6 @@ enumSocket th len socket = ------ The functions below are not warp-specific and could be split out into a --separate package. -takeUntilBlank :: Int - -> ([ByteString] -> [ByteString]) - -> E.Iteratee S.ByteString IO [ByteString] -takeUntilBlank count _ - | count > maxHeaders = E.throwError TooManyHeaders -takeUntilBlank count front = do - l <- takeLineMax 0 id - if B.null l - then return $ front [] - else takeUntilBlank (count + 1) $ front . (:) l - -takeLineMax :: Int - -> ([ByteString] -> [ByteString]) - -> E.Iteratee ByteString IO ByteString -takeLineMax len front = do - mbs <- EL.head - case mbs of - Nothing -> E.throwError IncompleteHeaders - Just bs -> do - let (x, y) = S.breakByte 10 bs - x' = if S.length x > 0 && S.last x == 13 - then S.init x - else x - let len' = len + B.length x - case () of - () - | len' > maxHeaderLength -> E.throwError OverLargeHeader - | B.null y -> takeLineMax len' $ front . (:) x - | otherwise -> do - E.yield () $ E.Chunks [B.drop 1 y] - return $ B.concat $ front [x'] - iterSocket :: T.Handle -> Socket -> E.Iteratee B.ByteString IO () @@ -463,6 +428,7 @@ defaultSettings = Settings go :: InvalidRequest -> IO () go _ = return () +takeHeaders :: E.Iteratee ByteString IO [ByteString] takeHeaders = do !x <- forceHead takeHeaders' 0 id id x @@ -515,13 +481,15 @@ takeHeaders' !len !lines !prepend !bs = do {-# SCC "takeHeaders'.takeMore" #-} takeHeaders' len' lines' id more {-# INLINE takeHeaders' #-} +forceHead :: E.Iteratee ByteString IO ByteString forceHead = do - !mx <- E.head + !mx <- EL.head case mx of !Nothing -> E.throwError IncompleteHeaders Just !x -> return x {-# INLINE forceHead #-} +checkCR :: ByteString -> Int -> Int checkCR bs pos = let !p = pos - 1 in if '\r' == B.index bs p diff --git a/runtests.hs b/runtests.hs index 9f6fba9d2..14e266352 100644 --- a/runtests.hs +++ b/runtests.hs @@ -5,7 +5,7 @@ import Test.Framework (defaultMain, testGroup, Test) import Test.Framework.Providers.HUnit import Test.HUnit hiding (Test) -import Network.Wai.Handler.Warp (takeLineMax, takeHeaders, InvalidRequest (..)) +import Network.Wai.Handler.Warp (takeHeaders, InvalidRequest (..)) import Data.Enumerator (run_, ($$), enumList, run) import Control.Exception (fromException) @@ -14,54 +14,21 @@ main = defaultMain [testSuite] testSuite :: Test testSuite = testGroup "Text.Hamlet" - [ testCase "takeLineMax safe" caseTakeLineMaxSafe - , testCase "takeUntilBlank safe" caseTakeUntilBlankSafe - , testCase "takeLineMax unsafe" caseTakeLineMaxUnsafe - , testCase "takeLineMax incomplete" caseTakeLineMaxIncomplete + [ testCase "takeUntilBlank safe" caseTakeUntilBlankSafe , testCase "takeUntilBlank too many lines" caseTakeUntilBlankTooMany , testCase "takeUntilBlank too large" caseTakeUntilBlankTooLarge ] -caseTakeLineMaxSafe = do - x <- run_ $ (enumList 1 ["f", "oo\n\n", "bar\n\r\nbaz\n"]) $$ do - a <- takeLineMax 0 id - b <- takeLineMax 0 id - c <- takeLineMax 0 id - d <- takeLineMax 0 id - e <- takeLineMax 0 id - return (a, b, c, d, e) - x @?= ("foo", "", "bar", "", "baz") - caseTakeUntilBlankSafe = do x <- run_ $ (enumList 1 ["f", "oo\n", "bar\nbaz\n\r\n"]) $$ takeHeaders x @?= ["foo", "bar", "baz"] -caseTakeLineMaxUnsafe = do - x <- run $ (enumList 1 $ repeat "abc") $$ do - a <- takeLineMax 0 id - b <- takeLineMax 0 id - c <- takeLineMax 0 id - d <- takeLineMax 0 id - e <- takeLineMax 0 id - return (a, b, c, d, e) - assertException OverLargeHeader x - assertException x (Left se) = case fromException se of Just e -> e @?= x Nothing -> assertFailure "Not an exception" assertException _ _ = assertFailure "Not an exception" -caseTakeLineMaxIncomplete = do - x <- run $ (enumList 1 ["f", "oo\n\n", "bar\n\nbaz"]) $$ do - a <- takeLineMax 0 id - b <- takeLineMax 0 id - c <- takeLineMax 0 id - d <- takeLineMax 0 id - e <- takeLineMax 0 id - return (a, b, c, d, e) - assertException IncompleteHeaders x - caseTakeUntilBlankTooMany = do x <- run $ (enumList 1 $ repeat "f\n") $$ takeHeaders assertException OverLargeHeader x From 5ed535db69605ec788ecf58d8668a11d78169482 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Sat, 12 Feb 2011 22:30:18 +0200 Subject: [PATCH 60/90] Theoretical Comet support --- Network/Wai/Handler/Warp.hs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index fd760cc29..fb62a004d 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -402,11 +402,16 @@ iterSocket :: T.Handle iterSocket th sock = E.continue step where - step E.EOF = E.yield () E.EOF + -- We pause timeouts before passing control back to user code. This ensures + -- that a timeout will only ever be executed when Warp is in control. We + -- also make sure to resume the timeout after the completion of user code + -- so that we can kill idle connections. + step E.EOF = liftIO (T.resume th) >> E.yield () E.EOF step (E.Chunks []) = E.continue step step (E.Chunks xs) = do + liftIO $ T.resume th liftIO $ Sock.sendMany sock xs - liftIO $ T.tickle th + liftIO $ T.pause th E.continue step data Settings = Settings From 8a94b3c9699a2c3673a3c358c8bf64ab6d02856e Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 17 Feb 2011 07:10:46 +0200 Subject: [PATCH 61/90] Removed unneeded network-enumerator dep --- warp.cabal | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/warp.cabal b/warp.cabal index b68114177..c9b963a89 100644 --- a/warp.cabal +++ b/warp.cabal @@ -1,5 +1,5 @@ Name: warp -Version: 0.3.2.1 +Version: 0.3.2.2 Synopsis: A fast, light-weight web server for WAI applications. License: BSD3 License-file: LICENSE @@ -25,7 +25,6 @@ Library , enumerator >= 0.4.5 && < 0.5 , blaze-builder >= 0.2.1.4 && < 0.3 , sendfile >= 0.7.2 && < 0.8 - , network-enumerator >= 0.1.1 && < 0.2 if flag(network-bytestring) build-depends: network >= 2.2.1 && < 2.2.3 , network-bytestring >= 0.1.3 && < 0.1.4 From fd50079c8201900b0f5fadb8f220917154f19960 Mon Sep 17 00:00:00 2001 From: Matt Brown Date: Sat, 26 Feb 2011 14:27:33 -0800 Subject: [PATCH 62/90] more efficient content-length parse --- Network/Wai/Handler/Warp.hs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index fb62a004d..c2cbc3bfc 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -64,6 +64,7 @@ import Control.Exception , fromException ) import Control.Concurrent (forkIO, threadWaitWrite) +import qualified Data.Char as C import Data.Maybe (fromMaybe) import Data.Typeable (Typeable) @@ -215,10 +216,7 @@ parseRequest' port (firstLine:otherLines) remoteHost' = do let len = case lookup "content-length" heads of Nothing -> 0 - Just bs -> - case reads $ B.unpack bs of -- FIXME could probably be optimized - (x, _):_ -> x - [] -> 0 + Just bs -> fromIntegral $ B.foldl' (\i c -> i * 10 + C.digitToInt c) 0 $ B.takeWhile C.isDigit bs let serverName' = takeUntil 58 host -- ':' -- FIXME isolate takes an Integer instead of Int or Int64. If this is a -- performance penalty, we may need our own version. From 89fdbea8bc59e671f799cf1e4eb263296ce17f59 Mon Sep 17 00:00:00 2001 From: Matt Brown Date: Sat, 26 Feb 2011 14:33:19 -0800 Subject: [PATCH 63/90] use breakByte instead of break --- Network/Wai/Handler/Warp.hs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index c2cbc3bfc..172d85663 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -252,7 +252,7 @@ parseFirst s = do let (hfirst, hsecond) = B.splitAt 5 http' if (hfirst == "HTTP/") then - let (rpath, qstring) = B.break (== '?') query + let (rpath, qstring) = S.breakByte 63 query -- '?' in return (method, rpath, qstring, hsecond) else E.throwError NonHttp {-# INLINE parseFirst #-} -- FIXME is this inline necessary? the function is only called from one place and not exported From 8dd8eca68b90eadb948a826f757f7f12498bfd1a Mon Sep 17 00:00:00 2001 From: Matt Brown Date: Sat, 26 Feb 2011 14:54:11 -0800 Subject: [PATCH 64/90] cleanup parseFirst --- Network/Wai/Handler/Warp.hs | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 172d85663..7b847c0d6 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -243,18 +243,15 @@ takeUntil c bs = parseFirst :: ByteString -> E.Iteratee S.ByteString IO (ByteString, ByteString, ByteString, HttpVersion) -parseFirst s = do - let pieces = S.split 32 s -- ' ' - (method, query, http') <- - case pieces of - [x, y, z] -> return (x, y, z) - _ -> E.throwError $ BadFirstLine $ B.unpack s - let (hfirst, hsecond) = B.splitAt 5 http' - if (hfirst == "HTTP/") - then - let (rpath, qstring) = S.breakByte 63 query -- '?' - in return (method, rpath, qstring, hsecond) - else E.throwError NonHttp +parseFirst s = + case S.split 32 s of -- ' ' + [method, query, http'] -> do + let (hfirst, hsecond) = B.splitAt 5 http' + if hfirst == "HTTP/" + then let (rpath, qstring) = S.breakByte 63 query -- '?' + in return (method, rpath, qstring, hsecond) + else E.throwError NonHttp + _ -> E.throwError $ BadFirstLine $ B.unpack s {-# INLINE parseFirst #-} -- FIXME is this inline necessary? the function is only called from one place and not exported httpBuilder, spaceBuilder, newlineBuilder, transferEncodingBuilder From 824691a6b170373ab0cf872da47a2c4ba3416f54 Mon Sep 17 00:00:00 2001 From: Matt Brown Date: Sat, 26 Feb 2011 15:00:40 -0800 Subject: [PATCH 65/90] cleanup whitespace --- Network/Wai/Handler/Warp.hs | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 7b847c0d6..ee5ec6b51 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -324,8 +324,7 @@ sendResponse th req hv socket (ResponseBuilder s hs b) return True where headers' = headers hv s hs isChunked' - b' = - if isChunked' + b' = if isChunked' then headers' `mappend` chunkedTransferEncoding b `mappend` chunkedTransferTerminator @@ -337,14 +336,12 @@ sendResponse th req hv socket (ResponseEnumerator res) = res go where -- FIXME perhaps alloca a buffer per thread and reuse that in all functiosn below. Should lessen greatly the GC burden (I hope) - go s hs - | not (hasBody s req) = do + go s hs | not (hasBody s req) = do liftIO $ Sock.sendMany socket $ L.toChunks $ toLazyByteString $ headers hv s hs False return True - go s hs = - chunk' + go s hs = chunk' $ E.enumList 1 [headers hv s hs isChunked'] $$ E.joinI $ builderToByteString -- FIXME unsafeBuilderToByteString $$ (iterSocket th socket >> return isKeepAlive) @@ -352,10 +349,9 @@ sendResponse th req hv socket (ResponseEnumerator res) = hasLength = lookup "content-length" hs /= Nothing isChunked' = isChunked hv && not hasLength isKeepAlive = isChunked' || hasLength - chunk' i = - if isChunked' - then E.joinI $ chunk $$ i - else i + chunk' i = if isChunked' + then E.joinI $ chunk $$ i + else i chunk :: E.Enumeratee Builder Builder IO Bool chunk = E.checkDone $ E.continue . step step k E.EOF = k (E.Chunks [chunkedTransferTerminator]) >>== return @@ -369,8 +365,8 @@ parseHeaderNoAttr s = restLen = S.length rest -- FIXME check for colon without following space? rest' = if restLen > 1 && SU.unsafeTake 2 rest == ": " - then SU.unsafeDrop 2 rest - else rest + then SU.unsafeDrop 2 rest + else rest in (mkCIByteString k, rest') enumSocket :: T.Handle -> Int -> Socket -> E.Enumerator ByteString IO a From 2f45a71da787c4afb1e6bee489fac235778190d7 Mon Sep 17 00:00:00 2001 From: Matt Brown Date: Mon, 28 Feb 2011 12:23:18 -0800 Subject: [PATCH 66/90] cleanup enumSocket --- Network/Wai/Handler/Warp.hs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index ee5ec6b51..7e082e33b 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -377,13 +377,9 @@ enumSocket th len socket = bs <- liftIO $ Sock.recv socket len liftIO $ T.tickle th if S.null bs - then E.throwError SocketTimeout - else go k bs + then E.continue k + else k (E.Chunks [bs]) >>== inner inner step = E.returnI step - go k bs - | S.length bs == 0 = E.continue k - | otherwise = k (E.Chunks [bs]) >>== enumSocket th len socket - ------ The functions below are not warp-specific and could be split out into a --separate package. From 6f8fa02e6261b586c15f4e450132925486a8c2b2 Mon Sep 17 00:00:00 2001 From: Kazu Yamamoto Date: Tue, 1 Mar 2011 16:30:28 +0900 Subject: [PATCH 67/90] Set serverName and port even if HTTP/1.0 is used. Noe that HTTP/1.0 does not define Host: header. --- Network/Wai/Handler/Warp.hs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 7e082e33b..7d8be72b4 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -207,12 +207,14 @@ parseRequest' :: Port parseRequest' _ [] _ = E.throwError $ NotEnoughLines [] parseRequest' port (firstLine:otherLines) remoteHost' = do (method, rpath', gets, httpversion) <- parseFirst firstLine - let rpath = + let (host',rpath) = if S.null rpath' - then "/" - else rpath' + then ("","/") + else if "http://" `S.isPrefixOf` rpath' + then S.break (47==) $ S.drop 7 rpath' -- '/' + else ("", rpath') let heads = map parseHeaderNoAttr otherLines - let host = fromMaybe "" $ lookup "host" heads + let host = fromMaybe host' $ lookup "host" heads let len = case lookup "content-length" heads of Nothing -> 0 From 20b92cab3704a12947d2e07252dc8c802120cf40 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 2 Mar 2011 10:32:15 +0200 Subject: [PATCH 68/90] Version bump --- Network/Wai/Handler/Warp.hs | 2 +- warp.cabal | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 7d8be72b4..e8d79802e 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -211,7 +211,7 @@ parseRequest' port (firstLine:otherLines) remoteHost' = do if S.null rpath' then ("","/") else if "http://" `S.isPrefixOf` rpath' - then S.break (47==) $ S.drop 7 rpath' -- '/' + then S.breakByte 47 $ S.drop 7 rpath' -- '/' else ("", rpath') let heads = map parseHeaderNoAttr otherLines let host = fromMaybe host' $ lookup "host" heads diff --git a/warp.cabal b/warp.cabal index c9b963a89..862a96b2a 100644 --- a/warp.cabal +++ b/warp.cabal @@ -1,5 +1,5 @@ Name: warp -Version: 0.3.2.2 +Version: 0.3.2.3 Synopsis: A fast, light-weight web server for WAI applications. License: BSD3 License-file: LICENSE From 44b5d84d492cd4997b72d5d7d51d88c3213b4152 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 10 Mar 2011 07:47:41 +0200 Subject: [PATCH 69/90] WAI 0.4 --- Network/Wai/Handler/Warp.hs | 55 +++++++++++++++++++++++-------------- warp.cabal | 6 ++-- 2 files changed, 38 insertions(+), 23 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index e8d79802e..8b6a369bb 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -87,6 +87,8 @@ import qualified Timeout as T import Data.Word (Word8) import Data.List (foldl') import Control.Monad (forever) +import qualified Network.HTTP.Types as H +import qualified Data.Ascii as A #if WINDOWS import Control.Concurrent (threadDelay) @@ -149,6 +151,7 @@ serveConnections' set app socket = do tm <- T.initialize $ settingsTimeout set * 1000000 forever $ do (conn, sa) <- accept socket + putStrLn "Accepted a connection" _ <- forkIO $ do th <- T.registerKillThread tm serveConnection th onE port app conn sa @@ -214,19 +217,22 @@ parseRequest' port (firstLine:otherLines) remoteHost' = do then S.breakByte 47 $ S.drop 7 rpath' -- '/' else ("", rpath') let heads = map parseHeaderNoAttr otherLines - let host = fromMaybe host' $ lookup "host" heads + let host = A.toByteString $ fromMaybe (A.unsafeFromByteString host') + $ lookup "host" heads let len = case lookup "content-length" heads of Nothing -> 0 - Just bs -> fromIntegral $ B.foldl' (\i c -> i * 10 + C.digitToInt c) 0 $ B.takeWhile C.isDigit bs + Just bs -> fromIntegral $ B.foldl' (\i c -> i * 10 + C.digitToInt c) 0 $ B.takeWhile C.isDigit $ A.toByteString bs let serverName' = takeUntil 58 host -- ':' -- FIXME isolate takes an Integer instead of Int or Int64. If this is a -- performance penalty, we may need our own version. return (EB.isolate len, Request - { requestMethod = method + { requestMethod = A.unsafeFromByteString method , httpVersion = httpversion - , pathInfo = rpath - , queryString = gets + , pathInfo = H.decodePathSegments rpath + , rawPathInfo = rpath + , rawQueryString = gets + , queryString = H.parseQuery gets , serverName = serverName' , serverPort = port , requestHeaders = heads @@ -244,14 +250,18 @@ takeUntil c bs = {-# INLINE takeUntil #-} parseFirst :: ByteString - -> E.Iteratee S.ByteString IO (ByteString, ByteString, ByteString, HttpVersion) + -> E.Iteratee S.ByteString IO (ByteString, ByteString, ByteString, H.HttpVersion) parseFirst s = case S.split 32 s of -- ' ' [method, query, http'] -> do let (hfirst, hsecond) = B.splitAt 5 http' if hfirst == "HTTP/" then let (rpath, qstring) = S.breakByte 63 query -- '?' - in return (method, rpath, qstring, hsecond) + hv = + case hsecond of + "1.1" -> H.http11 + _ -> H.http10 + in return (method, rpath, qstring, hv) else E.throwError NonHttp _ -> E.throwError $ BadFirstLine $ B.unpack s {-# INLINE parseFirst #-} -- FIXME is this inline necessary? the function is only called from one place and not exported @@ -264,14 +274,17 @@ newlineBuilder = copyByteString "\r\n" transferEncodingBuilder = copyByteString "Transfer-Encoding: chunked\r\n\r\n" colonSpaceBuilder = copyByteString ": " -headers :: HttpVersion -> Status -> ResponseHeaders -> Bool -> Builder +headers :: H.HttpVersion -> H.Status -> H.ResponseHeaders -> Bool -> Builder headers !httpversion !status !responseHeaders !isChunked' = {-# SCC "headers" #-} let !start = httpBuilder - `mappend` copyByteString httpversion + `mappend` (copyByteString $ + case httpversion of + H.HttpVersion 1 1 -> "1.1" + _ -> "1.0") `mappend` spaceBuilder - `mappend` fromShow (statusCode status) + `mappend` fromShow (H.statusCode status) `mappend` spaceBuilder - `mappend` copyByteString (statusMessage status) + `mappend` copyByteString (A.toByteString $ H.statusMessage status) `mappend` newlineBuilder !start' = foldl' responseHeaderToBuilder start responseHeaders !end = if isChunked' @@ -279,21 +292,21 @@ headers !httpversion !status !responseHeaders !isChunked' = {-# SCC "headers" #- else newlineBuilder in start' `mappend` end -responseHeaderToBuilder :: Builder -> (CIByteString, ByteString) -> Builder +responseHeaderToBuilder :: Builder -> (A.CIAscii, A.Ascii) -> Builder responseHeaderToBuilder b (x, y) = b - `mappend` (copyByteString $ ciOriginal x) + `mappend` (copyByteString $ A.ciToByteString x) `mappend` colonSpaceBuilder - `mappend` copyByteString y + `mappend` copyByteString (A.toByteString y) `mappend` newlineBuilder -isChunked :: HttpVersion -> Bool -isChunked = (==) http11 +isChunked :: H.HttpVersion -> Bool +isChunked = (==) H.http11 -hasBody :: Status -> Request -> Bool -hasBody s req = s /= (Status 204 "") && requestMethod req /= "HEAD" +hasBody :: H.Status -> Request -> Bool +hasBody s req = s /= (H.Status 204 "") && requestMethod req /= "HEAD" sendResponse :: T.Handle - -> Request -> HttpVersion -> Socket -> Response -> IO Bool + -> Request -> H.HttpVersion -> Socket -> Response -> IO Bool sendResponse th req hv socket (ResponseFile s hs fp) = do Sock.sendMany socket $ L.toChunks $ toLazyByteString $ headers hv s hs False if hasBody s req @@ -361,7 +374,7 @@ sendResponse th req hv socket (ResponseEnumerator res) = step k (E.Chunks [x]) = k (E.Chunks [chunkedTransferEncoding x]) >>== chunk step k (E.Chunks xs) = k (E.Chunks [chunkedTransferEncoding $ mconcat xs]) >>== chunk -parseHeaderNoAttr :: ByteString -> (CIByteString, ByteString) +parseHeaderNoAttr :: ByteString -> H.Header parseHeaderNoAttr s = let (k, rest) = S.breakByte 58 s -- ':' restLen = S.length rest @@ -369,7 +382,7 @@ parseHeaderNoAttr s = rest' = if restLen > 1 && SU.unsafeTake 2 rest == ": " then SU.unsafeDrop 2 rest else rest - in (mkCIByteString k, rest') + in (A.toCIAscii $ A.unsafeFromByteString k, A.unsafeFromByteString rest') enumSocket :: T.Handle -> Int -> Socket -> E.Enumerator ByteString IO a enumSocket th len socket = diff --git a/warp.cabal b/warp.cabal index 862a96b2a..8ef619555 100644 --- a/warp.cabal +++ b/warp.cabal @@ -1,5 +1,5 @@ Name: warp -Version: 0.3.2.3 +Version: 0.4.0 Synopsis: A fast, light-weight web server for WAI applications. License: BSD3 License-file: LICENSE @@ -19,12 +19,14 @@ flag network-bytestring Library Build-Depends: base >= 3 && < 5 , bytestring >= 0.9 && < 0.10 - , wai >= 0.3.0 && < 0.4 + , wai >= 0.4 && < 0.5 , blaze-builder-enumerator >= 0.2 && < 0.3 , transformers >= 0.2 && < 0.3 , enumerator >= 0.4.5 && < 0.5 , blaze-builder >= 0.2.1.4 && < 0.3 , sendfile >= 0.7.2 && < 0.8 + , http-types >= 0.5 && < 0.6 + , ascii >= 0.0.2 && < 0.1 if flag(network-bytestring) build-depends: network >= 2.2.1 && < 2.2.3 , network-bytestring >= 0.1.3 && < 0.1.4 From be4b74d5c258c4476c1b7a0e38925724f0defea7 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 24 Mar 2011 13:41:40 +0200 Subject: [PATCH 70/90] Newer WAI --- Network/Wai/Handler/Warp.hs | 31 +++++++++++++++++-------------- warp.cabal | 12 ++++-------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 8b6a369bb..2f44fec4a 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -80,7 +80,7 @@ import Blaze.ByteString.Builder (copyByteString, Builder, toLazyByteString, toByteStringIO) import Blaze.ByteString.Builder.Char8 (fromChar, fromShow) import Data.Monoid (mappend, mconcat) -import Network.Socket.SendFile (sendFileIterWith, Iter (..)) +import Network.Socket.SendFile (sendFileIterWith,sendFileIterWith', Iter (..)) import Control.Monad.IO.Class (liftIO) import qualified Timeout as T @@ -88,7 +88,7 @@ import Data.Word (Word8) import Data.List (foldl') import Control.Monad (forever) import qualified Network.HTTP.Types as H -import qualified Data.Ascii as A +import qualified Data.CaseInsensitive as CI #if WINDOWS import Control.Concurrent (threadDelay) @@ -151,7 +151,6 @@ serveConnections' set app socket = do tm <- T.initialize $ settingsTimeout set * 1000000 forever $ do (conn, sa) <- accept socket - putStrLn "Accepted a connection" _ <- forkIO $ do th <- T.registerKillThread tm serveConnection th onE port app conn sa @@ -217,17 +216,16 @@ parseRequest' port (firstLine:otherLines) remoteHost' = do then S.breakByte 47 $ S.drop 7 rpath' -- '/' else ("", rpath') let heads = map parseHeaderNoAttr otherLines - let host = A.toByteString $ fromMaybe (A.unsafeFromByteString host') - $ lookup "host" heads + let host = fromMaybe host' $ lookup "host" heads let len = case lookup "content-length" heads of Nothing -> 0 - Just bs -> fromIntegral $ B.foldl' (\i c -> i * 10 + C.digitToInt c) 0 $ B.takeWhile C.isDigit $ A.toByteString bs + Just bs -> fromIntegral $ B.foldl' (\i c -> i * 10 + C.digitToInt c) 0 $ B.takeWhile C.isDigit bs let serverName' = takeUntil 58 host -- ':' -- FIXME isolate takes an Integer instead of Int or Int64. If this is a -- performance penalty, we may need our own version. return (EB.isolate len, Request - { requestMethod = A.unsafeFromByteString method + { requestMethod = method , httpVersion = httpversion , pathInfo = H.decodePathSegments rpath , rawPathInfo = rpath @@ -284,7 +282,7 @@ headers !httpversion !status !responseHeaders !isChunked' = {-# SCC "headers" #- `mappend` spaceBuilder `mappend` fromShow (H.statusCode status) `mappend` spaceBuilder - `mappend` copyByteString (A.toByteString $ H.statusMessage status) + `mappend` copyByteString (H.statusMessage status) `mappend` newlineBuilder !start' = foldl' responseHeaderToBuilder start responseHeaders !end = if isChunked' @@ -292,11 +290,11 @@ headers !httpversion !status !responseHeaders !isChunked' = {-# SCC "headers" #- else newlineBuilder in start' `mappend` end -responseHeaderToBuilder :: Builder -> (A.CIAscii, A.Ascii) -> Builder +responseHeaderToBuilder :: Builder -> H.Header -> Builder responseHeaderToBuilder b (x, y) = b - `mappend` (copyByteString $ A.ciToByteString x) + `mappend` copyByteString (CI.original x) `mappend` colonSpaceBuilder - `mappend` copyByteString (A.toByteString y) + `mappend` copyByteString y `mappend` newlineBuilder isChunked :: H.HttpVersion -> Bool @@ -307,11 +305,16 @@ hasBody s req = s /= (H.Status 204 "") && requestMethod req /= "HEAD" sendResponse :: T.Handle -> Request -> H.HttpVersion -> Socket -> Response -> IO Bool -sendResponse th req hv socket (ResponseFile s hs fp) = do +sendResponse th req hv socket (ResponseFile s hs fp mpart) = do Sock.sendMany socket $ L.toChunks $ toLazyByteString $ headers hv s hs False if hasBody s req then do - sendFileIterWith tickler socket fp sendFileCount + case mpart of + Nothing -> sendFileIterWith tickler socket fp sendFileCount + Just part -> + sendFileIterWith' tickler socket fp sendFileCount + (filePartOffset part) + (filePartByteCount part) return $ lookup "content-length" hs /= Nothing else return True where @@ -382,7 +385,7 @@ parseHeaderNoAttr s = rest' = if restLen > 1 && SU.unsafeTake 2 rest == ": " then SU.unsafeDrop 2 rest else rest - in (A.toCIAscii $ A.unsafeFromByteString k, A.unsafeFromByteString rest') + in (CI.mk k, rest') enumSocket :: T.Handle -> Int -> Socket -> E.Enumerator ByteString IO a enumSocket th len socket = diff --git a/warp.cabal b/warp.cabal index 8ef619555..911f4ffa3 100644 --- a/warp.cabal +++ b/warp.cabal @@ -23,15 +23,11 @@ Library , blaze-builder-enumerator >= 0.2 && < 0.3 , transformers >= 0.2 && < 0.3 , enumerator >= 0.4.5 && < 0.5 - , blaze-builder >= 0.2.1.4 && < 0.3 + , blaze-builder >= 0.2.1.4 && < 0.4 , sendfile >= 0.7.2 && < 0.8 - , http-types >= 0.5 && < 0.6 - , ascii >= 0.0.2 && < 0.1 - if flag(network-bytestring) - build-depends: network >= 2.2.1 && < 2.2.3 - , network-bytestring >= 0.1.3 && < 0.1.4 - else - build-depends: network >= 2.3 && < 2.4 + , http-types >= 0.6 && < 0.7 + , case-insensitive >= 0.2 && < 0.3 + , network >= 2.3 && < 2.4 Exposed-modules: Network.Wai.Handler.Warp Other-modules: Timeout ghc-options: -Wall From 4841a7fb0288b13df4d8bc40e7d1b275a1bfcbec Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 24 Mar 2011 15:42:56 +0200 Subject: [PATCH 71/90] Allow network 2.2.* --- warp.cabal | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/warp.cabal b/warp.cabal index 911f4ffa3..883cdb31e 100644 --- a/warp.cabal +++ b/warp.cabal @@ -27,7 +27,11 @@ Library , sendfile >= 0.7.2 && < 0.8 , http-types >= 0.6 && < 0.7 , case-insensitive >= 0.2 && < 0.3 - , network >= 2.3 && < 2.4 + if flag(network-bytestring) + build-depends: network >= 2.2.1 && < 2.2.3 + , network-bytestring >= 0.1.3 && < 0.1.4 + else + build-depends: network >= 2.3 && < 2.4 Exposed-modules: Network.Wai.Handler.Warp Other-modules: Timeout ghc-options: -Wall From 5df6a8b895843781a7765acf2682356148104f9f Mon Sep 17 00:00:00 2001 From: Bas van Dijk Date: Wed, 30 Mar 2011 18:23:07 +0200 Subject: [PATCH 72/90] Return the content-length from parseRequest instead as the Enumeratee --- Network/Wai/Handler/Warp.hs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 8b6a369bb..c46058f27 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -170,15 +170,15 @@ serveConnection th onException port app conn remoteHost' = do where fromClient = enumSocket th bytesPerRead conn serveConnection' = do - (enumeratee, env) <- parseRequest port remoteHost' + (len, env) <- parseRequest port remoteHost' -- Let the application run for as long as it wants liftIO $ T.pause th - res <- E.joinI $ enumeratee $$ app env + res <- E.joinI $ EB.isolate len $$ app env liftIO $ T.resume th keepAlive <- liftIO $ sendResponse th env (httpVersion env) conn res if keepAlive then serveConnection' else return () -parseRequest :: Port -> SockAddr -> E.Iteratee S.ByteString IO (E.Enumeratee ByteString ByteString IO a, Request) +parseRequest :: Port -> SockAddr -> E.Iteratee S.ByteString IO (Integer, Request) parseRequest port remoteHost' = do headers' <- takeHeaders parseRequest' port headers' remoteHost' @@ -206,7 +206,7 @@ instance Exception InvalidRequest parseRequest' :: Port -> [ByteString] -> SockAddr - -> E.Iteratee S.ByteString IO (E.Enumeratee S.ByteString S.ByteString IO a, Request) + -> E.Iteratee S.ByteString IO (Integer, Request) parseRequest' _ [] _ = E.throwError $ NotEnoughLines [] parseRequest' port (firstLine:otherLines) remoteHost' = do (method, rpath', gets, httpversion) <- parseFirst firstLine @@ -226,7 +226,7 @@ parseRequest' port (firstLine:otherLines) remoteHost' = do let serverName' = takeUntil 58 host -- ':' -- FIXME isolate takes an Integer instead of Int or Int64. If this is a -- performance penalty, we may need our own version. - return (EB.isolate len, Request + return (len, Request { requestMethod = A.unsafeFromByteString method , httpVersion = httpversion , pathInfo = H.decodePathSegments rpath From f7eb0f0f581e243f45518b696731a0097b865f35 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 1 Apr 2011 10:30:15 +0300 Subject: [PATCH 73/90] API cleanup --- Network/Wai/Handler/Warp.hs | 68 ++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 39 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index d50d5e1d6..3a837f850 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -25,10 +25,9 @@ module Network.Wai.Handler.Warp ( -- * Run a Warp server run - , runEx - , serveConnections - -- * Run a Warp server with full settings control , runSettings + , runSettingsSocket + -- * Settings , Settings , defaultSettings , settingsPort @@ -47,7 +46,6 @@ module Network.Wai.Handler.Warp import Prelude hiding (catch, lines) import Network.Wai -import qualified System.IO import Data.ByteString (ByteString) import qualified Data.ByteString as S @@ -89,6 +87,7 @@ import Data.List (foldl') import Control.Monad (forever) import qualified Network.HTTP.Types as H import qualified Data.CaseInsensitive as CI +import System.IO (hPutStrLn, stderr) #if WINDOWS import Control.Concurrent (threadDelay) @@ -96,19 +95,12 @@ import qualified Control.Concurrent.MVar as MV import Network.Socket (withSocketsDo) #endif --- | Run an 'Application' on the given port, ignoring all exceptions. +-- | Run an 'Application' on the given port. This calls 'runSettings' with +-- 'defaultSettings'. run :: Port -> Application -> IO () -run = runEx (const $ return ()) - --- | Run an 'Application' on the given port, with the given exception handler. --- Please note that you will also receive 'InvalidRequest' exceptions. -runEx :: (SomeException -> IO ()) -> Port -> Application -> IO () -runEx onE port = runSettings Settings - { settingsPort = port - , settingsOnException = onE - , settingsTimeout = 30 - } +run p = runSettings defaultSettings { settingsPort = p } +-- | Run a Warp server with the given settings. runSettings :: Settings -> Application -> IO () #if WINDOWS runSettings set app = withSocketsDo $ do @@ -119,33 +111,26 @@ runSettings set app = withSocketsDo $ do (const clean) (\s -> do MV.modifyMVar_ var (\_ -> return $ Just s) - serveConnections' set app s) + runSettingsSocket set s app) forever (threadDelay maxBound) `finally` clean #else runSettings set = bracket (listenOn $ PortNumber $ fromIntegral $ settingsPort set) sClose . - serveConnections' set + (flip (runSettingsSocket set)) #endif type Port = Int --- | Runs a server, listening on the given socket. The user is responsible for --- closing the socket after 'runWithSocket' completes. You must also supply a --- 'Port' argument for use in the 'serverPort' record; however, this field is --- only used for informational purposes. If you are in fact listening on a --- non-TCP socket, this can be a ficticious value. -serveConnections :: (SomeException -> IO ()) - -> Port -> Application -> Socket -> IO () -serveConnections onE port = serveConnections' defaultSettings - { settingsOnException = onE - , settingsPort = port - } - -serveConnections' :: Settings - -> Application -> Socket -> IO () -serveConnections' set app socket = do +-- | Same as 'runSettings', but uses a user-supplied socket instead of opening +-- one. This allows the user to provide, for example, Unix named socket, which +-- can be used when reverse HTTP proxying into your application. +-- +-- Note that the 'settingsPort' will still be passed to 'Application's via the +-- 'serverPort' record. +runSettingsSocket :: Settings -> Socket -> Application -> IO () +runSettingsSocket set socket app = do let onE = settingsOnException set port = settingsPort set tm <- T.initialize $ settingsTimeout set * 1000000 @@ -194,10 +179,8 @@ data InvalidRequest = NotEnoughLines [String] | BadFirstLine String | NonHttp - | TooManyHeaders | IncompleteHeaders | OverLargeHeader - | SocketTimeout deriving (Show, Typeable, Eq) instance Exception InvalidRequest @@ -235,7 +218,6 @@ parseRequest' port (firstLine:otherLines) remoteHost' = do , serverPort = port , requestHeaders = heads , isSecure = False - , errorHandler = System.IO.hPutStr System.IO.stderr , remoteHost = remoteHost' }) @@ -419,19 +401,27 @@ iterSocket th sock = liftIO $ T.pause th E.continue step +-- | Various Warp server settings. This is purposely kept as an abstract data +-- type so that new settings can be added without breaking backwards +-- compatibility. In order to create a 'Settings' value, use 'defaultSettings' +-- and record syntax to modify individual records. For example: +-- +-- > defaultSettings { settingsTimeout = 20 } data Settings = Settings - { settingsPort :: Int - , settingsOnException :: SomeException -> IO () - , settingsTimeout :: Int -- ^ seconds + { settingsPort :: Int -- ^ Port to listen on. Default value: 3000 + , settingsOnException :: SomeException -> IO () -- ^ What to do with exceptions thrown by either the application or server. Default: ignore server-generated exceptions (see 'InvalidRequest') and print application-generated applications to stderr. + , settingsTimeout :: Int -- ^ Timeout value in seconds. Default value: 30 } +-- | The default settings for the Warp server. See the individual settings for +-- the default value. defaultSettings :: Settings defaultSettings = Settings { settingsPort = 3000 , settingsOnException = \e -> case fromException e of Just x -> go x - Nothing -> print e + Nothing -> hPutStrLn stderr $ show e , settingsTimeout = 30 } where From 7f04e06665fbae5b102b0c8d88ccf3277a0dee7f Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 1 Apr 2011 10:33:35 +0300 Subject: [PATCH 74/90] Un-exposing some functions --- Network/Wai/Handler/Warp.hs | 3 --- 1 file changed, 3 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 3a837f850..8805587f4 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -36,9 +36,6 @@ module Network.Wai.Handler.Warp -- * Datatypes , Port , InvalidRequest (..) - -- * Utility functions for other packages - , sendResponse - , parseRequest #if TEST , takeHeaders #endif From 2e1aae0bc6b8a1b5c6d1bcb157271c062bb89dd8 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 3 Apr 2011 10:50:55 +0300 Subject: [PATCH 75/90] settingsHost --- Network/Wai/Handler/Warp.hs | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 8805587f4..6b53f3622 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -31,6 +31,7 @@ module Network.Wai.Handler.Warp , Settings , defaultSettings , settingsPort + , settingsHost , settingsOnException , settingsTimeout -- * Datatypes @@ -49,10 +50,13 @@ import qualified Data.ByteString as S import qualified Data.ByteString.Unsafe as SU import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy as L -import Network (listenOn, sClose, PortID(PortNumber), Socket) +import Network (sClose, Socket) import Network.Socket - ( accept, SockAddr + ( accept, SockAddr (SockAddrInet), Family (AF_INET) + , SocketType (Stream), listen, bindSocket, setSocketOption + , SocketOption (ReuseAddr), iNADDR_ANY, inet_addr, SockAddr (SockAddrInet) ) +import qualified Network.Socket import qualified Network.Socket.ByteString as Sock import Control.Exception ( bracket, finally, Exception, SomeException, catch @@ -92,6 +96,16 @@ import qualified Control.Concurrent.MVar as MV import Network.Socket (withSocketsDo) #endif +bindPort :: Int -> String -> IO Socket +bindPort p s = do + sock <- Network.Socket.socket AF_INET Stream 0 + h <- if s == "*" then return iNADDR_ANY else inet_addr s + let addr = SockAddrInet (fromIntegral p) h + setSocketOption sock ReuseAddr 1 + bindSocket sock addr + listen sock 150 + return sock + -- | Run an 'Application' on the given port. This calls 'runSettings' with -- 'defaultSettings'. run :: Port -> Application -> IO () @@ -104,7 +118,7 @@ runSettings set app = withSocketsDo $ do var <- MV.newMVar Nothing let clean = MV.modifyMVar_ var $ \s -> maybe (return ()) sClose s >> return Nothing _ <- forkIO $ bracket - (listenOn $ PortNumber $ fromIntegral $ settingsPort set) + (bindPort (settingsPort set) (settingsHost set)) (const clean) (\s -> do MV.modifyMVar_ var (\_ -> return $ Just s) @@ -113,7 +127,7 @@ runSettings set app = withSocketsDo $ do #else runSettings set = bracket - (listenOn $ PortNumber $ fromIntegral $ settingsPort set) + (bindPort (settingsPort set) (settingsHost set)) sClose . (flip (runSettingsSocket set)) #endif @@ -406,6 +420,7 @@ iterSocket th sock = -- > defaultSettings { settingsTimeout = 20 } data Settings = Settings { settingsPort :: Int -- ^ Port to listen on. Default value: 3000 + , settingsHost :: String -- ^ Host to bind to, or * for all. Default value: * , settingsOnException :: SomeException -> IO () -- ^ What to do with exceptions thrown by either the application or server. Default: ignore server-generated exceptions (see 'InvalidRequest') and print application-generated applications to stderr. , settingsTimeout :: Int -- ^ Timeout value in seconds. Default value: 30 } @@ -415,6 +430,7 @@ data Settings = Settings defaultSettings :: Settings defaultSettings = Settings { settingsPort = 3000 + , settingsHost = "*" , settingsOnException = \e -> case fromException e of Just x -> go x From 6af611134e106a51af590982fbb45f65bdf63119 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Sat, 9 Apr 2011 21:48:26 +0300 Subject: [PATCH 76/90] Build flags --- warp.cabal | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/warp.cabal b/warp.cabal index 883cdb31e..fa5df868c 100644 --- a/warp.cabal +++ b/warp.cabal @@ -1,5 +1,5 @@ Name: warp -Version: 0.4.0 +Version: 0.4.0.1 Synopsis: A fast, light-weight web server for WAI applications. License: BSD3 License-file: LICENSE @@ -11,10 +11,8 @@ Build-Type: Simple Cabal-Version: >=1.6 Stability: Stable -flag timeout-protection - Description: Use timeouts (very performance-costly) to protect against DOS attacks. - Default: True flag network-bytestring + Default: False Library Build-Depends: base >= 3 && < 5 @@ -35,8 +33,6 @@ Library Exposed-modules: Network.Wai.Handler.Warp Other-modules: Timeout ghc-options: -Wall - if !flag(timeout-protection) - Cpp-options: -DNO_TIMEOUT_PROTECTION if os(windows) Cpp-options: -DWINDOWS From fd73ffb098475255756f7706f8328513643ed20c Mon Sep 17 00:00:00 2001 From: Kazu Yamamoto Date: Wed, 15 Jun 2011 10:38:30 +0900 Subject: [PATCH 77/90] using simple-sendfile. --- Network/Wai/Handler/Warp.hs | 29 ++++++++--------------------- warp.cabal | 2 +- 2 files changed, 9 insertions(+), 22 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 6b53f3622..6820414e4 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -62,7 +62,7 @@ import Control.Exception ( bracket, finally, Exception, SomeException, catch , fromException ) -import Control.Concurrent (forkIO, threadWaitWrite) +import Control.Concurrent (forkIO) import qualified Data.Char as C import Data.Maybe (fromMaybe) @@ -79,7 +79,7 @@ import Blaze.ByteString.Builder (copyByteString, Builder, toLazyByteString, toByteStringIO) import Blaze.ByteString.Builder.Char8 (fromChar, fromShow) import Data.Monoid (mappend, mconcat) -import Network.Socket.SendFile (sendFileIterWith,sendFileIterWith', Iter (..)) +import Network.Sendfile import Control.Monad.IO.Class (liftIO) import qualified Timeout as T @@ -183,9 +183,6 @@ bytesPerRead, maxTotalHeaderLength :: Int bytesPerRead = 4096 maxTotalHeaderLength = 50 * 1024 -sendFileCount :: Integer -sendFileCount = 65536 - data InvalidRequest = NotEnoughLines [String] | BadFirstLine String @@ -298,28 +295,18 @@ hasBody s req = s /= (H.Status 204 "") && requestMethod req /= "HEAD" sendResponse :: T.Handle -> Request -> H.HttpVersion -> Socket -> Response -> IO Bool -sendResponse th req hv socket (ResponseFile s hs fp mpart) = do +sendResponse _ req hv socket (ResponseFile s hs fp mpart) = do Sock.sendMany socket $ L.toChunks $ toLazyByteString $ headers hv s hs False if hasBody s req then do case mpart of - Nothing -> sendFileIterWith tickler socket fp sendFileCount - Just part -> - sendFileIterWith' tickler socket fp sendFileCount - (filePartOffset part) - (filePartByteCount part) + Nothing -> sendfile socket fp EntireFile + Just part -> sendfile socket fp PartOfFile { + rangeOffset = filePartOffset part + , rangeLength = filePartByteCount part + } return $ lookup "content-length" hs /= Nothing else return True - where - tickler iter = do - r <- iter - case r of - Done _ -> return () - Sent _ cont -> T.tickle th >> tickler cont - WouldBlock _ fd cont -> do - -- FIXME do we want to tickle here? - threadWaitWrite fd - tickler cont sendResponse th req hv socket (ResponseBuilder s hs b) | hasBody s req = do toByteStringIO (\bs -> do diff --git a/warp.cabal b/warp.cabal index fa5df868c..7cd0900f1 100644 --- a/warp.cabal +++ b/warp.cabal @@ -22,7 +22,7 @@ Library , transformers >= 0.2 && < 0.3 , enumerator >= 0.4.5 && < 0.5 , blaze-builder >= 0.2.1.4 && < 0.4 - , sendfile >= 0.7.2 && < 0.8 + , simple-sendfile , http-types >= 0.6 && < 0.7 , case-insensitive >= 0.2 && < 0.3 if flag(network-bytestring) From 3b1063d5a723a7f5e7d4fc1cee99351e363366cc Mon Sep 17 00:00:00 2001 From: Kazu Yamamoto Date: Wed, 22 Jun 2011 15:50:04 +0900 Subject: [PATCH 78/90] passing T.tickle to sendfile. --- Network/Wai/Handler/Warp.hs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 6820414e4..73966d18a 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -295,16 +295,17 @@ hasBody s req = s /= (H.Status 204 "") && requestMethod req /= "HEAD" sendResponse :: T.Handle -> Request -> H.HttpVersion -> Socket -> Response -> IO Bool -sendResponse _ req hv socket (ResponseFile s hs fp mpart) = do +sendResponse th req hv socket (ResponseFile s hs fp mpart) = do Sock.sendMany socket $ L.toChunks $ toLazyByteString $ headers hv s hs False if hasBody s req then do case mpart of - Nothing -> sendfile socket fp EntireFile + Nothing -> sendfile socket fp EntireFile (T.tickle th) Just part -> sendfile socket fp PartOfFile { rangeOffset = filePartOffset part , rangeLength = filePartByteCount part - } + } (T.tickle th) + T.tickle th return $ lookup "content-length" hs /= Nothing else return True sendResponse th req hv socket (ResponseBuilder s hs b) From 921f7e32a11257330133068c5018b309d2895d10 Mon Sep 17 00:00:00 2001 From: Kazu Yamamoto Date: Wed, 22 Jun 2011 16:26:17 +0900 Subject: [PATCH 79/90] ver check for simple-sendfile. --- warp.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/warp.cabal b/warp.cabal index 7cd0900f1..fa47c44d9 100644 --- a/warp.cabal +++ b/warp.cabal @@ -22,7 +22,7 @@ Library , transformers >= 0.2 && < 0.3 , enumerator >= 0.4.5 && < 0.5 , blaze-builder >= 0.2.1.4 && < 0.4 - , simple-sendfile + , simple-sendfile >= 0.1 && < 0.2 , http-types >= 0.6 && < 0.7 , case-insensitive >= 0.2 && < 0.3 if flag(network-bytestring) From a7e4a7d73dc584523d7ad43391bdf5e835d63807 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Wed, 22 Jun 2011 10:52:32 +0300 Subject: [PATCH 80/90] Get file length when not provided --- Network/Wai/Handler/Warp.hs | 28 +++++++++++++++++++++++++--- file-nolen.hs | 6 ++++++ file.hs | 3 ++- warp.cabal | 1 + 4 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 file-nolen.hs diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 73966d18a..9c335c5da 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -81,6 +81,8 @@ import Blaze.ByteString.Builder.Char8 (fromChar, fromShow) import Data.Monoid (mappend, mconcat) import Network.Sendfile +import qualified System.PosixCompat.Files as P + import Control.Monad.IO.Class (liftIO) import qualified Timeout as T import Data.Word (Word8) @@ -296,18 +298,38 @@ hasBody s req = s /= (H.Status 204 "") && requestMethod req /= "HEAD" sendResponse :: T.Handle -> Request -> H.HttpVersion -> Socket -> Response -> IO Bool sendResponse th req hv socket (ResponseFile s hs fp mpart) = do - Sock.sendMany socket $ L.toChunks $ toLazyByteString $ headers hv s hs False + (hs', cl) <- + case (mcl, mpart) of + (Just cl, _) -> return (hs, cl) + (Nothing, Nothing) -> do + cl <- P.fileSize `fmap` P.getFileStatus fp + return (("Content-Length", B.pack $ show cl):hs, fromIntegral cl) + (Nothing, Just part) -> do + let cl = filePartByteCount part + return (("Content-Length", B.pack $ show cl):hs, fromIntegral cl) + Sock.sendMany socket $ L.toChunks $ toLazyByteString $ headers hv s hs' False if hasBody s req then do case mpart of - Nothing -> sendfile socket fp EntireFile (T.tickle th) + Nothing -> sendfile socket fp PartOfFile { + rangeOffset = 0 + , rangeLength = cl + } (T.tickle th) Just part -> sendfile socket fp PartOfFile { rangeOffset = filePartOffset part , rangeLength = filePartByteCount part } (T.tickle th) T.tickle th - return $ lookup "content-length" hs /= Nothing + return True else return True + where + clS = lookup "content-length" hs + mcl = clS >>= readInt + -- FIXME make this more efficient + readInt bs = + case reads $ B.unpack bs of + (i, _):_ -> Just i + [] -> Nothing sendResponse th req hv socket (ResponseBuilder s hs b) | hasBody s req = do toByteStringIO (\bs -> do diff --git a/file-nolen.hs b/file-nolen.hs new file mode 100644 index 000000000..9b3c081d6 --- /dev/null +++ b/file-nolen.hs @@ -0,0 +1,6 @@ +{-# LANGUAGE OverloadedStrings #-} +import Network.Wai +import Network.Wai.Handler.Warp +import Network.HTTP.Types + +main = run 3000 $ const $ return $ ResponseFile status200 [("Content-Type", "text/plain")] "test.txt" Nothing diff --git a/file.hs b/file.hs index d1391f0ff..57734f10a 100644 --- a/file.hs +++ b/file.hs @@ -1,5 +1,6 @@ {-# LANGUAGE OverloadedStrings #-} import Network.Wai import Network.Wai.Handler.Warp +import Network.HTTP.Types -main = run 3000 $ const $ return $ ResponseFile status200 [("Content-Type", "text/plain"), ("Content-Length", "16")] "test.txt" +main = run 3000 $ const $ return $ ResponseFile status200 [("Content-Type", "text/plain"), ("Content-Length", "16")] "test.txt" Nothing diff --git a/warp.cabal b/warp.cabal index fa47c44d9..0d2f88eb7 100644 --- a/warp.cabal +++ b/warp.cabal @@ -25,6 +25,7 @@ Library , simple-sendfile >= 0.1 && < 0.2 , http-types >= 0.6 && < 0.7 , case-insensitive >= 0.2 && < 0.3 + , unix-compat >= 0.2 && < 0.3 if flag(network-bytestring) build-depends: network >= 2.2.1 && < 2.2.3 , network-bytestring >= 0.1.3 && < 0.1.4 From f6134fa15519272e6f3f424f36464ed754e407a5 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Wed, 22 Jun 2011 11:00:57 +0300 Subject: [PATCH 81/90] More efficient takeInt --- Network/Wai/Handler/Warp.hs | 16 +++++++--------- runtests.hs | 7 ++++++- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 9c335c5da..144112d03 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -39,6 +39,7 @@ module Network.Wai.Handler.Warp , InvalidRequest (..) #if TEST , takeHeaders + , readInt #endif ) where @@ -299,7 +300,7 @@ sendResponse :: T.Handle -> Request -> H.HttpVersion -> Socket -> Response -> IO Bool sendResponse th req hv socket (ResponseFile s hs fp mpart) = do (hs', cl) <- - case (mcl, mpart) of + case (readInt `fmap` lookup "content-length" hs, mpart) of (Just cl, _) -> return (hs, cl) (Nothing, Nothing) -> do cl <- P.fileSize `fmap` P.getFileStatus fp @@ -322,14 +323,6 @@ sendResponse th req hv socket (ResponseFile s hs fp mpart) = do T.tickle th return True else return True - where - clS = lookup "content-length" hs - mcl = clS >>= readInt - -- FIXME make this more efficient - readInt bs = - case reads $ B.unpack bs of - (i, _):_ -> Just i - [] -> Nothing sendResponse th req hv socket (ResponseBuilder s hs b) | hasBody s req = do toByteStringIO (\bs -> do @@ -519,3 +512,8 @@ checkCR bs pos = then p else pos {-# INLINE checkCR #-} + +-- Note: This function produces garbage on invalid input. But serving an +-- invalid content-length is a bad idea, mkay? +readInt :: S.ByteString -> Integer +readInt = S.foldl' (\x w -> x * 10 + fromIntegral w - 48) 0 diff --git a/runtests.hs b/runtests.hs index 14e266352..6dcf15d3a 100644 --- a/runtests.hs +++ b/runtests.hs @@ -3,11 +3,13 @@ {-# LANGUAGE OverloadedStrings #-} import Test.Framework (defaultMain, testGroup, Test) import Test.Framework.Providers.HUnit +import Test.Framework.Providers.QuickCheck2 import Test.HUnit hiding (Test) -import Network.Wai.Handler.Warp (takeHeaders, InvalidRequest (..)) +import Network.Wai.Handler.Warp (takeHeaders, InvalidRequest (..), readInt) import Data.Enumerator (run_, ($$), enumList, run) import Control.Exception (fromException) +import qualified Data.ByteString.Char8 as S8 main :: IO () main = defaultMain [testSuite] @@ -17,6 +19,9 @@ testSuite = testGroup "Text.Hamlet" [ testCase "takeUntilBlank safe" caseTakeUntilBlankSafe , testCase "takeUntilBlank too many lines" caseTakeUntilBlankTooMany , testCase "takeUntilBlank too large" caseTakeUntilBlankTooLarge + , testProperty "takeInt" $ \i' -> + let i = abs i' + in i == readInt (S8.pack $ show i) ] caseTakeUntilBlankSafe = do From 56f61dffc6f9c25dc04294fe7ddbac0b12edda79 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 23 Jun 2011 17:01:06 +0300 Subject: [PATCH 82/90] Version bump --- warp.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/warp.cabal b/warp.cabal index 0d2f88eb7..fb95e222c 100644 --- a/warp.cabal +++ b/warp.cabal @@ -1,5 +1,5 @@ Name: warp -Version: 0.4.0.1 +Version: 0.4.1 Synopsis: A fast, light-weight web server for WAI applications. License: BSD3 License-file: LICENSE From 451beeeeb76665ea4c05ba97ff7acf295e9b42e8 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 30 Jun 2011 15:57:26 +0300 Subject: [PATCH 83/90] Fixed pong.hs --- pong.hs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pong.hs b/pong.hs index 0d3de0f0d..b1a6aa1ac 100644 --- a/pong.hs +++ b/pong.hs @@ -1,6 +1,7 @@ {-# LANGUAGE OverloadedStrings #-} import Network.Wai import Network.Wai.Handler.Warp +import Network.HTTP.Types (status200) import Blaze.ByteString.Builder (copyByteString) import Data.Monoid import Data.Enumerator (run_, enumList, ($$)) @@ -8,14 +9,14 @@ import Data.Enumerator (run_, enumList, ($$)) main = run 3000 app app req = return $ - case pathInfo req of + case rawPathInfo req of "/builder/withlen" -> builderWithLen "/builder/nolen" -> builderNoLen "/enum/withlen" -> enumWithLen "/enum/nolen" -> enumNoLen "/file/withlen" -> fileWithLen "/file/nolen" -> fileNoLen - _ -> index $ pathInfo req + x -> index x builderWithLen = ResponseBuilder status200 @@ -36,12 +37,14 @@ fileWithLen = ResponseFile , ("Content-Length", "4") ] "pong.txt" + Nothing fileNoLen = ResponseFile status200 [ ("Content-Type", "text/plain") ] "pong.txt" + Nothing enumWithLen = ResponseEnumerator $ \f -> run_ $ (enumList 1 $ map copyByteString ["P", "O", "NG"]) $$ f From 1febae94d54bed479faa2a1268b9a7911238912f Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 30 Jun 2011 15:57:38 +0300 Subject: [PATCH 84/90] Hiding ThreadKilled messages --- Network/Wai/Handler/Warp.hs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 144112d03..2b616ede8 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -61,7 +61,7 @@ import qualified Network.Socket import qualified Network.Socket.ByteString as Sock import Control.Exception ( bracket, finally, Exception, SomeException, catch - , fromException + , fromException, AsyncException (ThreadKilled) ) import Control.Concurrent (forkIO) import qualified Data.Char as C @@ -437,12 +437,17 @@ defaultSettings = Settings , settingsOnException = \e -> case fromException e of Just x -> go x - Nothing -> hPutStrLn stderr $ show e + Nothing -> + if go' $ fromException e + then hPutStrLn stderr $ show e + else return () , settingsTimeout = 30 } where go :: InvalidRequest -> IO () go _ = return () + go' (Just ThreadKilled) = False + go' _ = True takeHeaders :: E.Iteratee ByteString IO [ByteString] takeHeaders = do From 98a2de810e3a4ff85546b30bc9245937ce7d793e Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Thu, 30 Jun 2011 15:58:03 +0300 Subject: [PATCH 85/90] Version bump --- warp.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/warp.cabal b/warp.cabal index fb95e222c..4355873e4 100644 --- a/warp.cabal +++ b/warp.cabal @@ -1,5 +1,5 @@ Name: warp -Version: 0.4.1 +Version: 0.4.1.1 Synopsis: A fast, light-weight web server for WAI applications. License: BSD3 License-file: LICENSE From 0ad661ea1b4fec6346b3fd83c091bab87759cf75 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 15 Jul 2011 08:37:14 +0300 Subject: [PATCH 86/90] case-insensitive bump --- warp.cabal | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/warp.cabal b/warp.cabal index 4355873e4..5847297ca 100644 --- a/warp.cabal +++ b/warp.cabal @@ -1,5 +1,5 @@ Name: warp -Version: 0.4.1.1 +Version: 0.4.1.2 Synopsis: A fast, light-weight web server for WAI applications. License: BSD3 License-file: LICENSE @@ -24,7 +24,7 @@ Library , blaze-builder >= 0.2.1.4 && < 0.4 , simple-sendfile >= 0.1 && < 0.2 , http-types >= 0.6 && < 0.7 - , case-insensitive >= 0.2 && < 0.3 + , case-insensitive >= 0.2 && < 0.4 , unix-compat >= 0.2 && < 0.3 if flag(network-bytestring) build-depends: network >= 2.2.1 && < 2.2.3 From a680cca0be5954eb443a6f3d04c3bebbde892609 Mon Sep 17 00:00:00 2001 From: Michael Snoyman Date: Fri, 15 Jul 2011 10:17:02 +0300 Subject: [PATCH 87/90] Expose internals --- Network/Wai/Handler/Warp.hs | 46 ++++++++++++++++++++++++++----------- Timeout.hs | 1 + server-no-keepalive.hs | 34 +++++++++++++++++++++++++++ warp.cabal | 2 +- 4 files changed, 69 insertions(+), 14 deletions(-) create mode 100644 server-no-keepalive.hs diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 2b616ede8..31c3b8c31 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -37,6 +37,16 @@ module Network.Wai.Handler.Warp -- * Datatypes , Port , InvalidRequest (..) + -- * Internal + , Manager + , withManager + , parseRequest + , sendResponse + , registerKillThread + , bindPort + , enumSocket + , pause + , resume #if TEST , takeHeaders , readInt @@ -86,6 +96,7 @@ import qualified System.PosixCompat.Files as P import Control.Monad.IO.Class (liftIO) import qualified Timeout as T +import Timeout (Manager, registerKillThread, pause, resume) import Data.Word (Word8) import Data.List (foldl') import Control.Monad (forever) @@ -173,7 +184,7 @@ serveConnection th onException port app conn remoteHost' = do liftIO $ T.pause th res <- E.joinI $ EB.isolate len $$ app env liftIO $ T.resume th - keepAlive <- liftIO $ sendResponse th env (httpVersion env) conn res + keepAlive <- liftIO $ sendResponse th env conn res if keepAlive then serveConnection' else return () parseRequest :: Port -> SockAddr -> E.Iteratee S.ByteString IO (Integer, Request) @@ -297,8 +308,8 @@ hasBody :: H.Status -> Request -> Bool hasBody s req = s /= (H.Status 204 "") && requestMethod req /= "HEAD" sendResponse :: T.Handle - -> Request -> H.HttpVersion -> Socket -> Response -> IO Bool -sendResponse th req hv socket (ResponseFile s hs fp mpart) = do + -> Request -> Socket -> Response -> IO Bool +sendResponse th req socket (ResponseFile s hs fp mpart) = do (hs', cl) <- case (readInt `fmap` lookup "content-length" hs, mpart) of (Just cl, _) -> return (hs, cl) @@ -308,7 +319,7 @@ sendResponse th req hv socket (ResponseFile s hs fp mpart) = do (Nothing, Just part) -> do let cl = filePartByteCount part return (("Content-Length", B.pack $ show cl):hs, fromIntegral cl) - Sock.sendMany socket $ L.toChunks $ toLazyByteString $ headers hv s hs' False + Sock.sendMany socket $ L.toChunks $ toLazyByteString $ headers (httpVersion req) s hs' False if hasBody s req then do case mpart of @@ -323,7 +334,7 @@ sendResponse th req hv socket (ResponseFile s hs fp mpart) = do T.tickle th return True else return True -sendResponse th req hv socket (ResponseBuilder s hs b) +sendResponse th req socket (ResponseBuilder s hs b) | hasBody s req = do toByteStringIO (\bs -> do Sock.sendAll socket bs @@ -333,35 +344,35 @@ sendResponse th req hv socket (ResponseBuilder s hs b) Sock.sendMany socket $ L.toChunks $ toLazyByteString - $ headers hv s hs False + $ headers (httpVersion req) s hs False T.tickle th return True where - headers' = headers hv s hs isChunked' + headers' = headers (httpVersion req) s hs isChunked' b' = if isChunked' then headers' `mappend` chunkedTransferEncoding b `mappend` chunkedTransferTerminator - else headers hv s hs False `mappend` b + else headers (httpVersion req) s hs False `mappend` b hasLength = lookup "content-length" hs /= Nothing - isChunked' = isChunked hv && not hasLength + isChunked' = isChunked (httpVersion req) && not hasLength isKeepAlive = isChunked' || hasLength -sendResponse th req hv socket (ResponseEnumerator res) = +sendResponse th req socket (ResponseEnumerator res) = res go where -- FIXME perhaps alloca a buffer per thread and reuse that in all functiosn below. Should lessen greatly the GC burden (I hope) go s hs | not (hasBody s req) = do liftIO $ Sock.sendMany socket $ L.toChunks $ toLazyByteString - $ headers hv s hs False + $ headers (httpVersion req) s hs False return True go s hs = chunk' - $ E.enumList 1 [headers hv s hs isChunked'] + $ E.enumList 1 [headers (httpVersion req) s hs isChunked'] $$ E.joinI $ builderToByteString -- FIXME unsafeBuilderToByteString $$ (iterSocket th socket >> return isKeepAlive) where hasLength = lookup "content-length" hs /= Nothing - isChunked' = isChunked hv && not hasLength + isChunked' = isChunked (httpVersion req) && not hasLength isKeepAlive = isChunked' || hasLength chunk' i = if isChunked' then E.joinI $ chunk $$ i @@ -522,3 +533,12 @@ checkCR bs pos = -- invalid content-length is a bad idea, mkay? readInt :: S.ByteString -> Integer readInt = S.foldl' (\x w -> x * 10 + fromIntegral w - 48) 0 + +-- | Call the inner function with a timeout manager. +withManager :: Int -- ^ timeout in microseconds + -> (Manager -> IO a) + -> IO a +withManager timeout f = do + -- FIXME when stopManager is available, use it + man <- T.initialize timeout + f man diff --git a/Timeout.hs b/Timeout.hs index 7775537d5..27427ae75 100644 --- a/Timeout.hs +++ b/Timeout.hs @@ -17,6 +17,7 @@ import qualified Control.Exception as E -- FIXME implement stopManager +-- | A timeout manager newtype Manager = Manager (I.IORef [Handle]) data Handle = Handle (IO ()) (I.IORef State) data State = Active | Inactive | Paused | Canceled diff --git a/server-no-keepalive.hs b/server-no-keepalive.hs new file mode 100644 index 000000000..254dc55ae --- /dev/null +++ b/server-no-keepalive.hs @@ -0,0 +1,34 @@ +{-# LANGUAGE OverloadedStrings #-} +import Network.Wai.Handler.Warp +import Control.Exception (bracket) +import Control.Monad (forever) +import Network (sClose) +import Network.Socket (accept) +import Control.Monad.IO.Class (liftIO) +import qualified Data.Enumerator as E +import qualified Data.Enumerator.Binary as EB +import Control.Concurrent (forkIO) +import Network.Wai (responseLBS) +import Network.HTTP.Types (status200) +import Data.Enumerator (($$), run_) + +app = const $ return $ responseLBS status200 [("Content-type", "text/plain")] "This is not kept alive under any circumtances" + +main = withManager 30000000 $ \man -> bracket + (bindPort (settingsPort set) (settingsHost set)) + sClose + (\socket -> forever $ do + (conn, sa) <- accept socket + th <- liftIO $ registerKillThread man + _ <- forkIO $ do + run_ $ enumSocket th 4096 conn $$ do + liftIO $ pause th + (len, env) <- parseRequest (settingsPort set) sa + liftIO $ resume th + res <- E.joinI $ EB.isolate len $$ app env + _ <- liftIO $ sendResponse th env conn res + liftIO $ sClose conn + return () + ) + where + set = defaultSettings diff --git a/warp.cabal b/warp.cabal index 5847297ca..0b726ba20 100644 --- a/warp.cabal +++ b/warp.cabal @@ -1,5 +1,5 @@ Name: warp -Version: 0.4.1.2 +Version: 0.4.2 Synopsis: A fast, light-weight web server for WAI applications. License: BSD3 License-file: LICENSE From 02c1396c86e3fceb48cbe7df58cb631c804e24d4 Mon Sep 17 00:00:00 2001 From: Kazu Yamamoto Date: Wed, 20 Jul 2011 13:32:35 +0900 Subject: [PATCH 88/90] Using getAddrinfo to support both IPv4 and IPv6 on a socket. --- Network/Wai/Handler/Warp.hs | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 31c3b8c31..288a7119d 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -63,15 +63,17 @@ import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy as L import Network (sClose, Socket) import Network.Socket - ( accept, SockAddr (SockAddrInet), Family (AF_INET) - , SocketType (Stream), listen, bindSocket, setSocketOption - , SocketOption (ReuseAddr), iNADDR_ANY, inet_addr, SockAddr (SockAddrInet) + ( accept, Family (..) + , SocketType (Stream), listen, bindSocket, setSocketOption, maxListenQueue + , SockAddr, SocketOption (ReuseAddr) + , AddrInfo(..), AddrInfoFlag(..), defaultHints, getAddrInfo ) import qualified Network.Socket import qualified Network.Socket.ByteString as Sock import Control.Exception ( bracket, finally, Exception, SomeException, catch , fromException, AsyncException (ThreadKilled) + , bracketOnError ) import Control.Concurrent (forkIO) import qualified Data.Char as C @@ -112,13 +114,26 @@ import Network.Socket (withSocketsDo) bindPort :: Int -> String -> IO Socket bindPort p s = do - sock <- Network.Socket.socket AF_INET Stream 0 - h <- if s == "*" then return iNADDR_ANY else inet_addr s - let addr = SockAddrInet (fromIntegral p) h - setSocketOption sock ReuseAddr 1 - bindSocket sock addr - listen sock 150 - return sock + let hints = defaultHints { addrFlags = [AI_PASSIVE + , AI_NUMERICSERV + , AI_NUMERICHOST] + , addrSocketType = Stream } + host = if s == "*" then Nothing else Just s + port = Just . show $ p + addrs <- getAddrInfo (Just hints) host port + -- Choose an IPv6 socket if exists. This ensures the socket can + -- handle both IPv4 and IPv6 if v6only is false. + let addrs' = filter (\x -> addrFamily x == AF_INET6) addrs + addr = if null addrs' then head addrs else head addrs' + bracketOnError + (Network.Socket.socket (addrFamily addr) (addrSocketType addr) (addrProtocol addr)) + (sClose) + (\sock -> do + setSocketOption sock ReuseAddr 1 + bindSocket sock (addrAddress addr) + listen sock maxListenQueue + return sock + ) -- | Run an 'Application' on the given port. This calls 'runSettings' with -- 'defaultSettings'. From 8a886b9cfeffe3404f94019346aaf0f01a2df807 Mon Sep 17 00:00:00 2001 From: Kazu Yamamoto Date: Wed, 20 Jul 2011 15:46:07 +0900 Subject: [PATCH 89/90] Handling the Connection: field. --- Network/Wai/Handler/Warp.hs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/Network/Wai/Handler/Warp.hs b/Network/Wai/Handler/Warp.hs index 31c3b8c31..e0eb4f6b9 100755 --- a/Network/Wai/Handler/Warp.hs +++ b/Network/Wai/Handler/Warp.hs @@ -301,6 +301,20 @@ responseHeaderToBuilder b (x, y) = b `mappend` copyByteString y `mappend` newlineBuilder +checkPersist :: Request -> Bool +checkPersist req + | ver == H.http11 = checkPersist11 conn + | otherwise = checkPersist10 conn + where + ver = httpVersion req + conn = lookup "connection" $ requestHeaders req + checkPersist11 (Just x) + | CI.foldCase x == "close" = False + checkPersist11 _ = True + checkPersist10 (Just x) + | CI.foldCase x == "keep-alive" = True + checkPersist10 _ = False + isChunked :: H.HttpVersion -> Bool isChunked = (==) H.http11 @@ -320,6 +334,7 @@ sendResponse th req socket (ResponseFile s hs fp mpart) = do let cl = filePartByteCount part return (("Content-Length", B.pack $ show cl):hs, fromIntegral cl) Sock.sendMany socket $ L.toChunks $ toLazyByteString $ headers (httpVersion req) s hs' False + let isPersist= checkPersist req if hasBody s req then do case mpart of @@ -332,8 +347,8 @@ sendResponse th req socket (ResponseFile s hs fp mpart) = do , rangeLength = filePartByteCount part } (T.tickle th) T.tickle th - return True - else return True + return isPersist + else return isPersist sendResponse th req socket (ResponseBuilder s hs b) | hasBody s req = do toByteStringIO (\bs -> do @@ -356,7 +371,8 @@ sendResponse th req socket (ResponseBuilder s hs b) else headers (httpVersion req) s hs False `mappend` b hasLength = lookup "content-length" hs /= Nothing isChunked' = isChunked (httpVersion req) && not hasLength - isKeepAlive = isChunked' || hasLength + isPersist = checkPersist req + isKeepAlive = isPersist && (isChunked' || hasLength) sendResponse th req socket (ResponseEnumerator res) = res go where @@ -373,7 +389,8 @@ sendResponse th req socket (ResponseEnumerator res) = where hasLength = lookup "content-length" hs /= Nothing isChunked' = isChunked (httpVersion req) && not hasLength - isKeepAlive = isChunked' || hasLength + isPersist = checkPersist req + isKeepAlive = isPersist && (isChunked' || hasLength) chunk' i = if isChunked' then E.joinI $ chunk $$ i else i From 5e5372f01bc90380049573f6c2d35151220cf6b8 Mon Sep 17 00:00:00 2001 From: Michael Date: Wed, 20 Jul 2011 20:17:53 +0300 Subject: [PATCH 90/90] Version bump --- warp.cabal | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/warp.cabal b/warp.cabal index 0b726ba20..0132bdcbd 100644 --- a/warp.cabal +++ b/warp.cabal @@ -1,5 +1,5 @@ Name: warp -Version: 0.4.2 +Version: 0.4.3 Synopsis: A fast, light-weight web server for WAI applications. License: BSD3 License-file: LICENSE