packages feed

wai 2.1.0.3 → 3.0.0

raw patch · 5 files changed

+179/−68 lines, 5 filesdep +hspecdep +waidep −conduitdep −conduit-extradep −transformersdep ~basedep ~blaze-builderdep ~bytestringPVP ok

version bump matches the API change (PVP)

Dependencies added: hspec, wai

Dependencies removed: conduit, conduit-extra, transformers

Dependency ranges changed: base, blaze-builder, bytestring

API changes (from Hackage documentation)

- Network.Wai: responseSource :: Status -> ResponseHeaders -> Source IO (Flush Builder) -> Response
- Network.Wai: responseSourceBracket :: IO a -> (a -> IO ()) -> (a -> IO (Status, ResponseHeaders, Source IO (Flush Builder))) -> IO Response
- Network.Wai: responseToSource :: Response -> (Status, ResponseHeaders, WithSource IO (Flush Builder) b)
- Network.Wai: type WithSource m a b = (Source m a -> m b) -> m b
- Network.Wai.Internal: ResponseSource :: Status -> ResponseHeaders -> (forall b. WithSource IO (Flush Builder) b) -> Response
- Network.Wai.Internal: type RawApp = Source IO ByteString -> Sink ByteString IO () -> IO ()
- Network.Wai.Internal: type WithRawApp b = (RawApp -> IO b) -> IO b
- Network.Wai.Internal: type WithSource m a b = (Source m a -> m b) -> m b
+ Network.Wai: data ResponseReceived
+ Network.Wai: responseStream :: Status -> ResponseHeaders -> StreamingBody -> Response
+ Network.Wai: responseToStream :: Response -> (Status, ResponseHeaders, (StreamingBody -> IO a) -> IO a)
+ Network.Wai: type StreamingBody = (Builder -> IO ()) -> IO () -> IO ()
+ Network.Wai.Internal: ResponseReceived :: ResponseReceived
+ Network.Wai.Internal: ResponseStream :: Status -> ResponseHeaders -> StreamingBody -> Response
+ Network.Wai.Internal: data ResponseReceived
+ Network.Wai.Internal: instance Typeable ResponseReceived
+ Network.Wai.Internal: type StreamingBody = (Builder -> IO ()) -> IO () -> IO ()
- Network.Wai: requestBody :: Request -> Source IO ByteString
+ Network.Wai: requestBody :: Request -> IO ByteString
- Network.Wai: responseRaw :: (Source IO ByteString -> Sink ByteString IO () -> IO ()) -> Response -> Response
+ Network.Wai: responseRaw :: (IO ByteString -> (ByteString -> IO ()) -> IO ()) -> Response -> Response
- Network.Wai: type Application = Request -> IO Response
+ Network.Wai: type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived
- Network.Wai.Internal: Request :: Method -> HttpVersion -> ByteString -> ByteString -> RequestHeaders -> Bool -> SockAddr -> [Text] -> Query -> Source IO ByteString -> Vault -> RequestBodyLength -> Maybe ByteString -> Maybe ByteString -> Request
+ Network.Wai.Internal: Request :: Method -> HttpVersion -> ByteString -> ByteString -> RequestHeaders -> Bool -> SockAddr -> [Text] -> Query -> IO ByteString -> Vault -> RequestBodyLength -> Maybe ByteString -> Maybe ByteString -> Request
- Network.Wai.Internal: ResponseRaw :: (forall b. WithRawApp b) -> Response -> Response
+ Network.Wai.Internal: ResponseRaw :: (IO ByteString -> (ByteString -> IO ()) -> IO ()) -> Response -> Response
- Network.Wai.Internal: requestBody :: Request -> Source IO ByteString
+ Network.Wai.Internal: requestBody :: Request -> IO ByteString

Files

Network/Wai.hs view
@@ -39,6 +39,7 @@       -- * Types       Application     , Middleware+    , ResponseReceived       -- * Request     , Request     , defaultRequest@@ -61,36 +62,35 @@     , lazyRequestBody       -- * Response     , Response+    , StreamingBody     , FilePart (..)-    , WithSource       -- ** Response composers     , responseFile     , responseBuilder     , responseLBS-    , responseSource-    , responseSourceBracket+    , responseStream     , responseRaw       -- * Response accessors     , responseStatus     , responseHeaders-    , responseToSource+    , responseToStream     ) where  import           Blaze.ByteString.Builder     (Builder, fromLazyByteString) import           Blaze.ByteString.Builder     (fromByteString)-import           Control.Exception            (bracket, bracketOnError)+import           Control.Monad                (unless) import qualified Data.ByteString              as B import qualified Data.ByteString.Lazy         as L+import qualified Data.ByteString.Lazy.Internal as LI+import           Data.ByteString.Lazy.Internal (defaultChunkSize) import           Data.ByteString.Lazy.Char8   ()-import qualified Data.Conduit                 as C-import qualified Data.Conduit.Binary          as CB-import           Data.Conduit.Lazy            (lazyConsume)-import qualified Data.Conduit.List            as CL+import           Data.Function                (fix) import           Data.Monoid                  (mempty) import qualified Network.HTTP.Types           as H import           Network.Socket               (SockAddr (SockAddrInet)) import           Network.Wai.Internal import qualified System.IO                    as IO+import           System.IO.Unsafe             (unsafeInterleaveIO)  ---------------------------------------------------------------- @@ -130,29 +130,11 @@ responseLBS s h = ResponseBuilder s h . fromLazyByteString  -- | Creating 'Response' from 'C.Source'.-responseSource :: H.Status -> H.ResponseHeaders -> C.Source IO (C.Flush Builder) -> Response-responseSource st hs src = ResponseSource st hs ($ src)---- | Creating 'Response' with allocated resource safely released.------   * The first argument is an action to allocate resource.------   * The second argument is a function to release the resource.------   * The third argument is a function to create---     ('H.Status','H.ResponseHeaders','C.Source' 'IO' ('C.Flush' 'Builder'))---     from the resource.-responseSourceBracket :: IO a-                      -> (a -> IO ())-                      -> (a -> IO (H.Status-                                  ,H.ResponseHeaders-                                  ,C.Source IO (C.Flush Builder)))-                      -> IO Response-responseSourceBracket setup teardown action =-    bracketOnError setup teardown $ \resource -> do-        (st,hdr,src) <- action resource-        return $ ResponseSource st hdr $ \f ->-            bracket (return resource) teardown (\_ -> f src)+responseStream :: H.Status+               -> H.ResponseHeaders+               -> StreamingBody+               -> Response+responseStream = ResponseStream  -- | Create a response for a raw application. This is useful for \"upgrade\" -- situations such as WebSockets, where an application requests for the server@@ -165,10 +147,10 @@ -- @responseRaw@, behavior is undefined. -- -- Since 2.1.0-responseRaw :: (C.Source IO B.ByteString -> C.Sink B.ByteString IO () -> IO ())+responseRaw :: (IO B.ByteString -> (B.ByteString -> IO ()) -> IO ())             -> Response             -> Response-responseRaw rawApp fallback = ResponseRaw ($ rawApp) fallback+responseRaw = ResponseRaw  ---------------------------------------------------------------- @@ -176,36 +158,55 @@ responseStatus :: Response -> H.Status responseStatus (ResponseFile    s _ _ _) = s responseStatus (ResponseBuilder s _ _  ) = s-responseStatus (ResponseSource  s _ _  ) = s+responseStatus (ResponseStream  s _ _  ) = s responseStatus (ResponseRaw _ res      ) = responseStatus res  -- | Accessing 'H.ResponseHeaders' in 'Response'. responseHeaders :: Response -> H.ResponseHeaders responseHeaders (ResponseFile    _ hs _ _) = hs responseHeaders (ResponseBuilder _ hs _  ) = hs-responseHeaders (ResponseSource  _ hs _  ) = hs+responseHeaders (ResponseStream  _ hs _  ) = hs responseHeaders (ResponseRaw _ res)        = responseHeaders res  -- | Converting the body information in 'Response' to 'Source'.-responseToSource :: Response-                 -> (H.Status, H.ResponseHeaders, WithSource IO (C.Flush Builder) b)-responseToSource (ResponseSource s h b) = (s, h, b)-responseToSource (ResponseFile s h fp (Just part)) =-    (s, h, \f -> IO.withFile fp IO.ReadMode $ \handle -> f $ sourceFilePart handle part C.$= CL.map (C.Chunk . fromByteString))-responseToSource (ResponseFile s h fp Nothing) =-    (s, h, \f -> IO.withFile fp IO.ReadMode $ \handle -> f $ CB.sourceHandle handle C.$= CL.map (C.Chunk . fromByteString))-responseToSource (ResponseBuilder s h b) =-    (s, h, ($ CL.sourceList [C.Chunk b]))-responseToSource (ResponseRaw _ res) = responseToSource res--sourceFilePart :: IO.Handle -> FilePart -> C.Source IO B.ByteString-sourceFilePart handle (FilePart offset count _) =-    CB.sourceHandleRange handle (Just offset) (Just count)+responseToStream :: Response+                 -> ( H.Status+                    , H.ResponseHeaders+                    , (StreamingBody -> IO a) -> IO a+                    )+responseToStream (ResponseStream s h b) = (s, h, ($ b))+responseToStream (ResponseFile s h fp (Just part)) =+    ( s+    , h+    , \withBody -> IO.withBinaryFile fp IO.ReadMode $ \handle -> withBody $ \sendChunk _flush -> do+        IO.hSeek handle IO.AbsoluteSeek $ filePartOffset part+        let loop remaining | remaining <= 0 = return ()+            loop remaining = do+                bs <- B.hGetSome handle defaultChunkSize+                unless (B.null bs) $ do+                    let x = B.take remaining bs+                    sendChunk $ fromByteString x+                    loop $ remaining - B.length x+        loop $ fromIntegral $ filePartByteCount part+    )+responseToStream (ResponseFile s h fp Nothing) =+    ( s+    , h+    , \withBody -> IO.withBinaryFile fp IO.ReadMode $ \handle ->+       withBody $ \sendChunk _flush -> fix $ \loop -> do+            bs <- B.hGetSome handle defaultChunkSize+            unless (B.null bs) $ do+                sendChunk $ fromByteString bs+                loop+    )+responseToStream (ResponseBuilder s h b) =+    (s, h, \withBody -> withBody $ \sendChunk _flush -> sendChunk b)+responseToStream (ResponseRaw _ res) = responseToStream res  ----------------------------------------------------------------  -- | The WAI application.-type Application = Request -> IO Response+type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived  -- | Middleware is a component that sits between the server and application. It -- can do such tasks as GZIP encoding or response caching. What follows is the@@ -237,7 +238,7 @@     , remoteHost = SockAddrInet 0 0     , pathInfo = []     , queryString = []-    , requestBody = return ()+    , requestBody = return B.empty     , vault = mempty     , requestBodyLength = KnownLength 0     , requestHeaderHost = Nothing@@ -249,4 +250,13 @@ -- -- Since 1.4.1 lazyRequestBody :: Request -> IO L.ByteString-lazyRequestBody = fmap L.fromChunks . lazyConsume . requestBody+lazyRequestBody req =+    loop+  where+    loop = unsafeInterleaveIO $ do+        bs <- requestBody req+        if B.null bs+            then return LI.Empty+            else do+                bss <- loop+                return $ LI.Chunk bs bss
Network/Wai/Internal.hs view
@@ -8,7 +8,6 @@  import           Blaze.ByteString.Builder     (Builder) import qualified Data.ByteString              as B-import qualified Data.Conduit                 as C import           Data.Text                    (Text) import           Data.Typeable                (Typeable) import Data.Vault.Lazy (Vault)@@ -51,8 +50,9 @@   ,  pathInfo             :: [Text]   -- | Parsed query string information   ,  queryString          :: H.Query-  -- | A request body provided as 'Source'.-  ,  requestBody          :: C.Source IO B.ByteString+  -- | Get the next chunk of the body. Returns an empty bytestring when the+  -- body is fully consumed.+  ,  requestBody          :: IO B.ByteString   -- | A location for arbitrary data to be shared by applications and middleware.   ,  vault                 :: Vault   -- | The size of the request body. In the case of a chunked request body,@@ -84,15 +84,17 @@ data Response     = ResponseFile H.Status H.ResponseHeaders FilePath (Maybe FilePart)     | ResponseBuilder H.Status H.ResponseHeaders Builder-    | ResponseSource H.Status H.ResponseHeaders (forall b. WithSource IO (C.Flush Builder) b)-    | ResponseRaw (forall b. WithRawApp b) Response+    | ResponseStream H.Status H.ResponseHeaders StreamingBody+    | ResponseRaw (IO B.ByteString -> (B.ByteString -> IO ()) -> IO ()) Response   deriving Typeable -type RawApp = C.Source IO B.ByteString -> C.Sink B.ByteString IO () -> IO ()-type WithRawApp b = (RawApp -> IO b) -> IO b---- | Auxiliary type for 'ResponseSource'.-type WithSource m a b = (C.Source m a -> m b) -> m b+-- | Represents a streaming HTTP response body. It's a function of two+-- parameters; the first parameter provides a means of sending another chunk of+-- data, and the second parameter provides a means of flushing the data to the+-- client.+--+-- Since 3.0.0+type StreamingBody = (Builder -> IO ()) -> IO () -> IO ()  -- | The size of the request body. In the case of chunked bodies, the size will -- not be known.@@ -108,3 +110,14 @@     , filePartByteCount :: Integer     , filePartFileSize  :: Integer     } deriving Show++-- | A special datatype to indicate that the WAI handler has received the+-- response. This is to avoid the need for Rank2Types in the definition of+-- Application.+--+-- It is /highly/ advised that only WAI handlers import and use the data+-- constructor for this data type.+--+-- Since 3.0.0+data ResponseReceived = ResponseReceived+    deriving Typeable
+ test/Network/WaiSpec.hs view
@@ -0,0 +1,73 @@+module Network.WaiSpec (spec) where++import Test.Hspec+import Test.Hspec.QuickCheck (prop)+import Network.Wai+import Network.Wai.Internal (Request (Request))+import Data.IORef+import Data.Monoid+import qualified Data.ByteString as S+import qualified Data.ByteString.Lazy as L+import Blaze.ByteString.Builder (toByteString, Builder, fromWord8)+import Control.Monad (forM_)++spec :: Spec+spec = do+    describe "responseToStream" $ do+        let getBody res = do+                let (_, _, f) = responseToStream res+                f $ \streamingBody -> do+                    builderRef <- newIORef mempty+                    let add :: Builder -> IO ()+                        add b = atomicModifyIORef builderRef $ \builder ->+                            (builder `mappend` b, ())+                        flush :: IO ()+                        flush = return ()+                    streamingBody add flush+                    fmap toByteString $ readIORef builderRef+        prop "responseLBS" $ \bytes -> do+            body <- getBody $ responseLBS undefined undefined $ L.pack bytes+            body `shouldBe` S.pack bytes+        prop "responseBuilder" $ \bytes -> do+            body <- getBody $ responseBuilder undefined undefined+                            $ mconcat $ map fromWord8 bytes+            body `shouldBe` S.pack bytes+        prop "responseStream" $ \chunks -> do+            body <- getBody $ responseStream undefined undefined $ \sendChunk _ ->+                forM_ chunks $ \chunk -> sendChunk $ mconcat $ map fromWord8 chunk+            body `shouldBe` S.concat (map S.pack chunks)+        it "responseFile total" $ do+            let fp = "wai.cabal"+            body <- getBody $ responseFile undefined undefined fp Nothing+            expected <- S.readFile fp+            body `shouldBe` expected+        prop "responseFile partial" $ \offset' count' -> do+            let fp = "wai.cabal"+            totalBS <- S.readFile fp+            let total = S.length totalBS+                offset = abs offset' `mod` total+                count = abs count' `mod` (total - offset)+            body <- getBody $ responseFile undefined undefined fp $ Just FilePart+                { filePartOffset = fromIntegral offset+                , filePartByteCount = fromIntegral count+                , filePartFileSize = fromIntegral total+                }+            let expected = S.take count $ S.drop offset totalBS+            body `shouldBe` expected+    describe "lazyRequestBody" $ do+        prop "works" $ \chunks -> do+            ref <- newIORef $ map S.pack $ filter (not . null) chunks+            let req = Request+                        { requestBody = atomicModifyIORef ref $ \bss ->+                            case bss of+                                [] -> ([], S.empty)+                                x:y -> (y, x)+                        }+            body <- lazyRequestBody req+            body `shouldBe` L.fromChunks (map S.pack chunks)+        it "is lazy" $ do+            let req = Request+                        { requestBody = error "requestBody"+                        }+            _ <- lazyRequestBody req+            return ()
+ test/Spec.hs view
@@ -0,0 +1,1 @@+{-# OPTIONS_GHC -F -pgmF hspec-discover #-}
wai.cabal view
@@ -1,5 +1,5 @@ Name:                wai-Version:             2.1.0.3+Version:             3.0.0 Synopsis:            Web Application Interface. Description:         Provides a common protocol for communication between web applications and web servers. License:             MIT@@ -9,7 +9,7 @@ Homepage:            https://github.com/yesodweb/wai Category:            Web Build-Type:          Simple-Cabal-Version:       >=1.6+Cabal-Version:       >=1.8 Stability:           Stable  Source-repository head@@ -20,13 +20,27 @@   Build-Depends:     base                      >= 4        && < 5                    , bytestring                >= 0.9.1.4                    , blaze-builder             >= 0.2.1.4  && < 0.4-                   , conduit                   >= 1.0.8    && < 1.2-                   , conduit-extra             >= 1.0                    , network                   >= 2.2.1.5                    , http-types                >= 0.7                    , text                      >= 0.7-                   , transformers              >= 0.2.2                    , vault                     >= 0.3      && < 0.4   Exposed-modules:   Network.Wai                      Network.Wai.Internal   ghc-options:       -Wall++test-suite test+    hs-source-dirs: test+    main-is:        Spec.hs+    type:           exitcode-stdio-1.0+    ghc-options:    -threaded+    cpp-options:    -DTEST+    build-depends:  base+                  , wai+                  , hspec+                  , blaze-builder+                  , bytestring+    other-modules:  Network.WaiSpec++source-repository head+  type:     git+  location: git://github.com/yesodweb/wai.git