@@ -26,25 +26,26 @@
26#include "overlapping-compat.h"
26#include "overlapping-compat.h"
27module Servant.StreamSpec (spec) where
27module Servant.StreamSpec (spec) where
29−import Prelude ()
29+import Control.Monad (replicateM_, void)
30−import Prelude.Compat
30+import qualified Data.ByteString as BS
31import Data.Proxy
31import Data.Proxy
32+import GHC.Stats (currentBytesUsed, getGCStats)
32−import qualified Network.HTTP.Client as C
33+import qualified Network.HTTP.Client as C
36+import System.IO (IOMode (ReadMode), withFile)
33−import System.IO.Unsafe (unsafePerformIO)
37+import System.IO.Unsafe (unsafePerformIO)
34import Test.Hspec
38import Test.Hspec
39+import Test.QuickCheck
36−import Servant.API ((:<|>) ((:<|>)),
41+import Servant.API ((:<|>) ((:<|>)), (:>), JSON,
40− NewlineFraming,
42+ NetstringFraming, NewlineFraming,
42− ResultStream(..),
43+ OctetStream, ResultStream (..),
43− StreamGenerator(..))
44+ StreamGenerator (..), StreamGet)
44import Servant.Client
45import Servant.Client
46+import Servant.ClientSpec (Person (..))
47+import qualified Servant.ClientSpec as CS
45import Servant.Server
48import Servant.Server
46−import qualified Servant.ClientSpec as CS
47−import Servant.ClientSpec (Person(..))
50spec :: Spec
51spec :: Spec
@@ -54,7 +55,7 @@ spec = describe "Servant.Stream" $ do
54type StreamApi f =
55type StreamApi f =
55 "streamGetNewline" :> StreamGet NewlineFraming JSON (f Person)
56 "streamGetNewline" :> StreamGet NewlineFraming JSON (f Person)
56 :<|> "streamGetNetstring" :> StreamGet NetstringFraming JSON (f Person)
57 :<|> "streamGetNetstring" :> StreamGet NetstringFraming JSON (f Person)
57− :<|> EmptyAPI
58+ :<|> "streamALot" :> StreamGet NewlineFraming OctetStream (f BS.ByteString)
60capi :: Proxy (StreamApi ResultStream)
61capi :: Proxy (StreamApi ResultStream)
@@ -63,12 +64,9 @@ capi = Proxy
63sapi :: Proxy (StreamApi StreamGenerator)
64sapi :: Proxy (StreamApi StreamGenerator)
64sapi = Proxy
65sapi = Proxy
66−
67+getGetNL, getGetNS :: ClientM (ResultStream Person)
68+getGetALot :: ClientM (ResultStream BS.ByteString)
67−getGetNL :<|> getGetNS :<|> EmptyClient = client capi
69+getGetNL :<|> getGetNS :<|> getGetALot = client capi
70−getGetNL :: ClientM (ResultStream Person)
71−getGetNS :: ClientM (ResultStream Person)
73alice :: Person
71alice :: Person
74alice = Person "Alice" 42
72alice = Person "Alice" 42
@@ -77,14 +75,24 @@ bob :: Person
77bob = Person "Bob" 25
75bob = Person "Bob" 25
79server :: Application
77server :: Application
80−server = serve sapi (
78+server = serve sapi
81− (return (StreamGenerator (\f r -> f alice >> r bob >> r alice))
79+ $ return (StreamGenerator (\f r -> f alice >> r bob >> r alice))
82− :: Handler (StreamGenerator Person))
84− (return (StreamGenerator (\f r -> f alice >> r bob >> r alice))
80+ :<|> return (StreamGenerator (\f r -> f alice >> r bob >> r alice))
85− :: Handler (StreamGenerator Person))
81+ :<|> return (StreamGenerator lotsGenerator)
87− emptyServer)
83+ lotsGenerator f r = do
85+ withFile "/dev/urandom" ReadMode $
86+ \handle -> streamFiveMBNTimes handle 1000 r
89+ streamFiveMBNTimes handle left sink
90+ | left <= 0 = return ""
92+ msg <- BS.hGet handle (megabytes 5)
94+ streamFiveMBNTimes handle (left - 1) sink
90{-# NOINLINE manager' #-}
98{-# NOINLINE manager' #-}
@@ -94,20 +102,35 @@ manager' = unsafePerformIO $ C.newManager C.defaultManagerSettings
94runClient :: ClientM a -> BaseUrl -> IO (Either ServantError a)
102runClient :: ClientM a -> BaseUrl -> IO (Either ServantError a)
95runClient x baseUrl' = runClientM x (mkClientEnv manager' baseUrl')
103runClient x baseUrl' = runClientM x (mkClientEnv manager' baseUrl')
97−runResultStream :: ResultStream a -> IO (Maybe (Either String a), Maybe (Either String a), Maybe (Either String a), Maybe (Either String a))
105+runResultStream :: ResultStream a
106+ -> IO ( Maybe (Either String a)
107+ , Maybe (Either String a)
108+ , Maybe (Either String a)
109+ , Maybe (Either String a))
98−runResultStream (ResultStream k) = k $ \act -> (,,,) <$> act <*> act <*> act <*> act
110+runResultStream (ResultStream k)
111+ = k $ \act -> (,,,) <$> act <*> act <*> act <*> act
100streamSpec :: Spec
113streamSpec :: Spec
101streamSpec = beforeAll (CS.startWaiApp server) $ afterAll CS.endWaiApp $ do
114streamSpec = beforeAll (CS.startWaiApp server) $ afterAll CS.endWaiApp $ do
103− it "Servant.API.StreamGet.Newline" $ \(_, baseUrl) -> do
116+ it "works with Servant.API.StreamGet.Newline" $ \(_, baseUrl) -> do
104 Right res <- runClient getGetNL baseUrl
117 Right res <- runClient getGetNL baseUrl
105 let jra = Just (Right alice)
118 let jra = Just (Right alice)
106 jrb = Just (Right bob)
119 jrb = Just (Right bob)
107 runResultStream res `shouldReturn` (jra, jrb, jra, Nothing)
120 runResultStream res `shouldReturn` (jra, jrb, jra, Nothing)
109− it "Servant.API.StreamGet.Netstring" $ \(_, baseUrl) -> do
122+ it "works with Servant.API.StreamGet.Netstring" $ \(_, baseUrl) -> do
110 Right res <- runClient getGetNS baseUrl
123 Right res <- runClient getGetNS baseUrl
111 let jra = Just (Right alice)
124 let jra = Just (Right alice)
112 jrb = Just (Right bob)
125 jrb = Just (Right bob)
113 runResultStream res `shouldReturn` (jra, jrb, jra, Nothing)
126 runResultStream res `shouldReturn` (jra, jrb, jra, Nothing)
128+ it "streams in constant memory" $ \(_, baseUrl) -> do
129+ Right (ResultStream res) <- runClient getGetALot baseUrl
130+ let consumeNChunks n = replicateM_ n (res void)
132+ memUsed <- currentBytesUsed <$> getGCStats
133+ memUsed `shouldSatisfy` (< (megabytes 20))
135+megabytes :: Num a => a -> a
136+megabytes n = n * (1000 ^ 2)