on master help sign in
Merge branch 'read-send-file' into kazu-master
commit2f8a8e1b771829f4a8a77c0111352ce45a14c30f authorKazu Yamamoto <kazu@iij.ad.jp> dateMon 15 Jun 2015, 14:39 (11 years ago) parentc754e6c06687ad
11 files changed+422−80
warp-tls/Network/Wai/Handler/WarpTLS.hs+5−19 warp/Network/Wai/Handler/Warp.hs+12−9 warp/Network/Wai/Handler/Warp/FdCache.hs+1−1 warp/Network/Wai/Handler/Warp/Response.hs+26−11 warp/Network/Wai/Handler/Warp/Run.hs+4−5 warp/Network/Wai/Handler/Warp/SendFile.hs+132−18 warp/Network/Wai/Handler/Warp/Types.hs+22−16 warp/test/FdCacheSpec.hs+1−1 warp/test/SendFileSpec.hs+116 warp/test/inputFile+100 warp/warp.cabal+3
warp-tls/Network/Wai/Handler/WarpTLS.hs+5−19
@@ -35,12 +35,11 @@ module Network.Wai.Handler.WarpTLS (
3535import Control.Applicative ((<$>))
3636#endif
3737import Control.Exception (Exception, throwIO, bracket, finally, handle, fromException, try, IOException, onException)
38−import Control.Monad (unless, void)
38+import Control.Monad (void)
3939import qualified Crypto.Random.AESCtr
4040import qualified Data.ByteString as B
4141import qualified Data.ByteString as S
4242import qualified Data.ByteString.Lazy as L
43−import Data.ByteString.Lazy.Internal (defaultChunkSize)
4443import Data.Default.Class (def)
4544import qualified Data.IORef as I
4645import Data.Streaming.Network (bindPortTCP, safeRecv)
@@ -52,7 +51,6 @@ import qualified Network.TLS.Extra as TLSExtra
5251import Network.Wai (Application)
5352import Network.Wai.Handler.Warp
5453import Network.Wai.Handler.Warp.Buffer (allocateBuffer, bufferSize, freeBuffer)
55−import qualified System.IO as IO
5654import System.IO.Error (isEOFError)
5755
5856----------------------------------------------------------------
@@ -285,30 +283,18 @@ httpOverTls TLSSettings{..} s cachedRef params = do
285283 }
286284 conn ctx pool writeBuf = Connection {
287285 connSendMany = TLS.sendData ctx . L.fromChunks
288− , connSendAll = TLS.sendData ctx . L.fromChunks . return
286+ , connSendAll = sendall
289287 , connSendFile = sendfile
290288 , connClose = close
291289 , connRecv = recv
292− , connSendFileOverride = NotOverride
293290 , connBufferPool = pool
294291 , connWriteBuffer = writeBuf
295292 , connBufferSize = bufferSize
296293 }
297294 where
298− sendfile fp offset len tickle' headers = do
299− TLS.sendData ctx $ L.fromChunks headers
300− IO.withBinaryFile fp IO.ReadMode $ \h -> do
301− IO.hSeek h IO.AbsoluteSeek offset
302− loop h $ fromIntegral len
303− where
304− loop _ remaining | remaining <= 0 = return ()
305− loop h remaining = do
306− bs <- B.hGetSome h defaultChunkSize
307− unless (B.null bs) $ do
308− let x = B.take remaining bs
309− TLS.sendData ctx $ L.fromChunks [x]
310− void $ tickle' -- This tickles the Timer. It MUST be called per chunk.
311− loop h $ remaining - B.length x
295+ sendall = TLS.sendData ctx . L.fromChunks . return
296+ sendfile fid offset len hook headers =
297+ readSendFile writeBuf bufferSize sendall fid offset len hook headers
312298
313299 close = freeBuffer writeBuf `finally`
314300 void (tryIO $ TLS.bye ctx) `finally`
warp/Network/Wai/Handler/Warp.hs+12−9
@@ -88,14 +88,16 @@ module Network.Wai.Handler.Warp (
8888 , HostPreference (..)
8989 , Port
9090 , InvalidRequest (..)
91− , ConnSendFileOverride (..)
9291 -- * Per-request utilities
9392 , pauseTimeout
9493 -- * Connection
9594 , Connection (..)
95+ , SendFile
96+ , FileId (..)
9697 , socketConnection
9798 , newBufferPool
98− -- * Internal
99+ , readSendFile
100+ -- * Internal
99101 -- ** Version
100102 , warpVersion
101103 -- ** Data types
@@ -114,6 +116,13 @@ module Network.Wai.Handler.Warp (
114116 , sendResponse
115117 ) where
116118
119+import Control.Exception (SomeException)
120+import Data.ByteString (ByteString)
121+import Data.Maybe (fromMaybe)
122+import Data.Streaming.Network (HostPreference)
123+import qualified Data.Vault.Lazy as Vault
124+import Network.Socket (SockAddr)
125+import Network.Wai (Request, Response, vault)
117126import Network.Wai.Handler.Warp.Buffer
118127import Network.Wai.Handler.Warp.Date
119128import Network.Wai.Handler.Warp.FdCache
@@ -121,16 +130,10 @@ import Network.Wai.Handler.Warp.Header
121130import Network.Wai.Handler.Warp.Request
122131import Network.Wai.Handler.Warp.Response
123132import Network.Wai.Handler.Warp.Run
133+import Network.Wai.Handler.Warp.SendFile
124134import Network.Wai.Handler.Warp.Settings
125135import Network.Wai.Handler.Warp.Timeout
126136import Network.Wai.Handler.Warp.Types
127−import Control.Exception (SomeException)
128−import Network.Wai (Request, Response, vault)
129−import Network.Socket (SockAddr)
130−import Data.Streaming.Network (HostPreference)
131−import Data.ByteString (ByteString)
132−import qualified Data.Vault.Lazy as Vault
133−import Data.Maybe (fromMaybe)
134137
135138-- | Port to listen on. Default value: 3000
136139--
warp/Network/Wai/Handler/Warp/FdCache.hs+1−1
@@ -2,7 +2,7 @@
22
33-- | File descriptor cache to avoid locks in kernel.
44
5−#ifndef SENDFILEFD
5+#ifdef WINDOWS
66module Network.Wai.Handler.Warp.FdCache (
77 withFdCache
88 , MutableFdCache
warp/Network/Wai/Handler/Warp/Response.hs+26−11
@@ -42,12 +42,13 @@ import Data.Monoid (mappend, mempty)
4242import Data.Version (showVersion)
4343import qualified Network.HTTP.Types as H
4444import Network.Wai
45−import qualified Network.Wai.Handler.Warp.Date as D
4645import Network.Wai.Handler.Warp.Buffer (toBlazeBuffer)
46+import qualified Network.Wai.Handler.Warp.Date as D
47+import qualified Network.Wai.Handler.Warp.FdCache as F
4748import Network.Wai.Handler.Warp.Header
4849import Network.Wai.Handler.Warp.IO (toBufIOWith)
49−import Network.Wai.Handler.Warp.ResponseHeader
5050import Network.Wai.Handler.Warp.RequestHeader (parseByteRanges)
51+import Network.Wai.Handler.Warp.ResponseHeader
5152import qualified Network.Wai.Handler.Warp.Timeout as T
5253import Network.Wai.Handler.Warp.Types
5354import Network.Wai.Internal
@@ -181,7 +182,7 @@ sendResponse defServer conn ii req reqidxhdr src response = do
181182 hs <- addServerAndDate hs0
182183 if hasBody s then do
183184 -- HEAD comes here even if it does not have body.
184− sendRsp conn ver s hs rsp
185+ sendRsp conn mfdc ver s hs rsp
185186 T.tickle th
186187 return ret
187188 else do
@@ -195,6 +196,7 @@ sendResponse defServer conn ii req reqidxhdr src response = do
195196 rspidxhdr = indexResponseHeader hs0
196197 th = threadHandle ii
197198 dc = dateCacher ii
199+ mfdc = fdCacher ii
198200 addServerAndDate = addDate dc rspidxhdr . addServer defServer rspidxhdr
199201 mRange = reqidxhdr ! idxRange
200202 (isPersist,isChunked0) = infoFromRequest req reqidxhdr
@@ -222,28 +224,41 @@ data Rsp = RspFile FilePath (Maybe FilePart) (Maybe HeaderValue) Bool (IO ())
222224----------------------------------------------------------------
223225
224226sendRsp :: Connection
227+ -> Maybe F.MutableFdCache
225228 -> H.HttpVersion
226229 -> H.Status
227230 -> H.ResponseHeaders
228231 -> Rsp
229232 -> IO ()
230−sendRsp conn ver s0 hs0 (RspFile path mPart mRange isHead hook) = do
233+sendRsp conn mfdc ver s0 hs0 (RspFile path mPart mRange isHead hook) = do
231234 ex <- fileRange s0 hs path mPart mRange
232235 case ex of
233236 Left _ex ->
234237#ifdef WARP_DEBUG
235238 print _ex >>
236239#endif
237− sendRsp conn ver s2 hs2 (RspBuilder body True)
240+ sendRsp conn mfdc ver s2 hs2 (RspBuilder body True)
238241 Right (s, hs1, beg, len)
239242 | len >= 0 ->
240243 if isHead then
241− sendRsp conn ver s hs1 (RspBuilder mempty False)
244+ sendRsp conn mfdc ver s hs1 (RspBuilder mempty False)
242245 else do
243246 lheader <- composeHeader ver s hs1
244− connSendFile conn path beg len hook [lheader]
247+#ifdef WINDOWS
248+ let fid = FileId path Nothing
249+ hook' = hook
250+#else
251+ (mfd, hook') <- case mfdc of
252+ -- Windows or settingsFdCacheDuration is 0
253+ Nothing -> return (Nothing, hook)
254+ Just fdc -> do
255+ (fd, fresher) <- F.getFd fdc path
256+ return (Just fd, hook >> fresher)
257+ let fid = FileId path mfd
258+#endif
259+ connSendFile conn fid beg len hook' [lheader]
245260 | otherwise -> do
246− sendRsp conn ver H.status416
261+ sendRsp conn mfdc ver H.status416
247262 (filter (\(k, _) -> k /= "content-length") hs1)
248263 (RspBuilder mempty True)
249264 where
@@ -254,7 +269,7 @@ sendRsp conn ver s0 hs0 (RspFile path mPart mRange isHead hook) = do
254269
255270----------------------------------------------------------------
256271
257−sendRsp conn ver s hs (RspBuilder body needsChunked) = do
272+sendRsp conn _ ver s hs (RspBuilder body needsChunked) = do
258273 header <- composeHeaderBuilder ver s hs needsChunked
259274 let hdrBdy
260275 | needsChunked = header <> chunkedTransferEncoding body
@@ -266,7 +281,7 @@ sendRsp conn ver s hs (RspBuilder body needsChunked) = do
266281
267282----------------------------------------------------------------
268283
269−sendRsp conn ver s hs (RspStream streamingBody needsChunked th) = do
284+sendRsp conn _ ver s hs (RspStream streamingBody needsChunked th) = do
270285 header <- composeHeaderBuilder ver s hs needsChunked
271286 (recv, finish) <- newBlazeRecv $ reuseBufferStrategy
272287 $ toBlazeBuffer (connWriteBuffer conn) (connBufferSize conn)
@@ -289,7 +304,7 @@ sendRsp conn ver s hs (RspStream streamingBody needsChunked th) = do
289304
290305----------------------------------------------------------------
291306
292−sendRsp conn _ _ _ (RspRaw withApp src tickle) =
307+sendRsp conn _ _ _ _ (RspRaw withApp src tickle) =
293308 withApp recv send
294309 where
295310 recv = do
warp/Network/Wai/Handler/Warp/Run.hs+4−5
@@ -50,16 +50,16 @@ socketConnection :: Socket -> IO Connection
5050socketConnection s = do
5151 bufferPool <- newBufferPool
5252 writeBuf <- allocateBuffer bufferSize
53+ let sendall = Sock.sendAll s
5354 return Connection {
5455 connSendMany = Sock.sendMany s
55− , connSendAll = Sock.sendAll s
56− , connSendFile = defaultSendFile s
56+ , connSendAll = sendall
57+ , connSendFile = defaultSendFile s writeBuf bufferSize sendall
5758 , connClose = sClose s >> freeBuffer writeBuf
5859 , connRecv = receive s bufferPool
5960 , connBufferPool = bufferPool
6061 , connWriteBuffer = writeBuf
6162 , connBufferSize = bufferSize
62− , connSendFileOverride = Override s
6363 }
6464
6565#if __GLASGOW_HASKELL__ < 702
@@ -268,14 +268,13 @@ fork set mkConn addr app dc fc tm counter = settingsFork set $ \ unmask ->
268268 -- We grab the connection before registering timeouts since the
269269 -- timeouts will be useless during connection creation, due to the
270270 -- fact that async exceptions are still masked.
271− bracket mkConn closeConn $ \(conn0, transport) ->
271+ bracket mkConn closeConn $ \(conn, transport) ->
272272
273273 -- We need to register a timeout handler for this thread, and
274274 -- cancel that handler as soon as we exit.
275275 bracket (T.registerKillThread tm) T.cancel $ \th ->
276276
277277 let ii = InternalInfo th fc dc
278− conn = setSendFile conn0 fc
279278 -- We now have fully registered a connection close handler
280279 -- in the case of all exceptions, so it is safe to one
281280 -- again allow async exceptions.
warp/Network/Wai/Handler/Warp/SendFile.hs+132−18
@@ -1,29 +1,143 @@
1−{-# LANGUAGE CPP #-}
1+{-# LANGUAGE CPP, ForeignFunctionInterface #-}
22
3−module Network.Wai.Handler.Warp.SendFile where
3+module Network.Wai.Handler.Warp.SendFile (
4+ defaultSendFile
5+ , readSendFile
6+ , packHeader -- for testing
7+ ) where
48
5−import Data.ByteString (ByteString)
9+import Data.ByteString.Internal
10+import Foreign.ForeignPtr (newForeignPtr_, withForeignPtr)
611import Network.Sendfile
712import Network.Socket (Socket)
8−import qualified Network.Wai.Handler.Warp.FdCache as F
13+import Network.Wai.Handler.Warp.Buffer
914import Network.Wai.Handler.Warp.Types
1015
11−defaultSendFile :: Socket -> FilePath -> Integer -> Integer -> IO () -> [ByteString] -> IO ()
12−defaultSendFile s path off len act hdr = sendfileWithHeader s path (PartOfFile off len) act hdr
16+#ifdef WINDOWS
17+import Control.Monad (when)
18+import qualified System.IO as IO
19+import Foreign.Ptr (plusPtr)
20+#else
21+# if __GLASGOW_HASKELL__ < 709
22+import Control.Applicative ((<$>))
23+# endif
24+import Data.Word (Word8)
25+import Control.Exception
26+import Foreign.C.Types
27+import Foreign.Ptr (Ptr, castPtr, plusPtr)
28+import System.Posix.IO (openFd, OpenFileFlags(..), defaultFileFlags, OpenMode(ReadOnly), closeFd)
29+import System.Posix.Types
30+#endif
31+
32+----------------------------------------------------------------
1333
34+defaultSendFile :: Socket -> Buffer -> BufSize -> (ByteString -> IO ()) -> SendFile
35+#ifdef SENDFILEFD
36+defaultSendFile s _ _ _ fid off len act hdr = case mfid of
37+ -- settingsFdCacheDuration is 0
38+ Nothing -> sendfileWithHeader s path (PartOfFile off len) act hdr
39+ Just fd -> sendfileFdWithHeader s fd (PartOfFile off len) act hdr
40+ where
41+ mfid = fileIdFd fid
42+ path = fileIdPath fid
43+#else
44+defaultSendFile _ = readSendFile
45+#endif
1446
15−#if SENDFILEFD
16−setSendFile :: Connection -> Maybe F.MutableFdCache -> Connection
17−setSendFile conn Nothing = conn
18−setSendFile conn (Just fdcs) = case connSendFileOverride conn of
19− NotOverride -> conn
20− Override s -> conn { connSendFile = sendFile fdcs s }
47+----------------------------------------------------------------
2148
22−sendFile :: F.MutableFdCache -> Socket -> FilePath -> Integer -> Integer -> IO () -> [ByteString] -> IO ()
23−sendFile fdcs s path off len act hdr = do
24− (fd, fresher) <- F.getFd fdcs path
25− sendfileFdWithHeader s fd (PartOfFile off len) (act>>fresher) hdr
49+packHeader :: Buffer -> BufSize -> (ByteString -> IO ())
50+ -> IO () -> [ByteString]
51+ -> Int
52+ -> IO Int
53+packHeader _ _ _ _ [] n = return n
54+packHeader buf siz send hook (PS bsfp off len : bss) n
55+ | len < room = withForeignPtr bsfp $ \bsp -> do
56+ let dst = buf `plusPtr` n
57+ src = bsp `plusPtr` off
58+ memcpy dst src len
59+ packHeader buf siz send hook bss (n + len)
60+ | otherwise = withForeignPtr bsfp $ \bsp -> do
61+ let dst = buf `plusPtr` n
62+ src = bsp `plusPtr` off
63+ memcpy dst src room
64+ fptr <- newForeignPtr_ buf
65+ let entire = PS fptr 0 siz
66+ bs = PS bsfp (off + room) (len - room)
67+ send entire
68+ hook
69+ packHeader buf siz send hook (bs:bss) 0
70+ where
71+ room = siz - n
72+
73+mini :: Int -> Integer -> Int
74+mini i n
75+ | fromIntegral i < n = i
76+ | otherwise = fromIntegral n
77+
78+#ifdef WINDOWS
79+readSendFile :: Buffer -> BufSize -> (ByteString -> IO ()) -> SendFile
80+readSendFile buf siz send fid off0 len0 hook headers = do
81+ fptr <- newForeignPtr_ buf
82+ hn <- packHeader buf siz send hook headers 0
83+ let room = siz - hn
84+ buf' = buf `plusPtr` hn
85+ IO.withBinaryFile path IO.ReadMode $ \h -> do
86+ IO.hSeek h IO.AbsoluteSeek off0
87+ n <- IO.hGetBufSome h buf' (mini room len0)
88+ let bs = PS fptr 0 (hn + n)
89+ n' = fromIntegral n
90+ send bs
91+ hook
92+ loop h fptr (len0 - n')
93+ where
94+ path = fileIdPath fid
95+ loop h fptr len
96+ | len <= 0 = return ()
97+ | otherwise = do
98+ n <- IO.hGetBufSome h buf (mini siz len)
99+ when (n /= 0) $ do
100+ let bs = PS fptr 0 n
101+ n' = fromIntegral n
102+ send bs
103+ hook
104+ loop h fptr (len - n')
26105#else
27−setSendFile :: Connection -> Maybe F.MutableFdCache -> Connection
28−setSendFile conn _ = conn
106+readSendFile :: Buffer -> BufSize -> (ByteString -> IO ()) -> SendFile
107+readSendFile buf siz send fid off0 len0 hook headers =
108+ bracket setup teardown $ \fd -> do
109+ fptr <- newForeignPtr_ buf
110+ hn <- packHeader buf siz send hook headers 0
111+ let room = siz - hn
112+ buf' = buf `plusPtr` hn
113+ n <- positionRead fd buf' (mini room len0) off0
114+ let bs = PS fptr 0 (hn + n)
115+ n' = fromIntegral n
116+ send bs
117+ hook
118+ loop fd fptr (len0 - n') (off0 + n')
119+ where
120+ path = fileIdPath fid
121+ setup = case fileIdFd fid of
122+ Just fd -> return fd
123+ Nothing -> openFd path ReadOnly Nothing defaultFileFlags{nonBlock=True}
124+ teardown fd = case fileIdFd fid of
125+ Just _ -> return ()
126+ Nothing -> closeFd fd
127+ loop fd fptr len off
128+ | len <= 0 = return ()
129+ | otherwise = do
130+ n <- positionRead fd buf (mini siz len) off
131+ let bs = PS fptr 0 n
132+ n' = fromIntegral n
133+ send bs
134+ hook
135+ loop fd fptr (len - n') (off + n')
136+
137+positionRead :: Fd -> Ptr Word8 -> Int -> Integer -> IO Int
138+positionRead (Fd fd) buf siz off =
139+ fromIntegral <$> c_pread fd (castPtr buf) (fromIntegral siz) (fromIntegral off)
140+
141+foreign import ccall unsafe "pread"
142+ c_pread :: CInt -> Ptr CChar -> ByteCount -> FileOffset -> IO ByteCount -- fixme
29143#endif
warp/Network/Wai/Handler/Warp/Types.hs+22−16
@@ -11,12 +11,15 @@ import Data.IORef (IORef, readIORef, writeIORef, newIORef)
1111import Data.Typeable (Typeable)
1212import Data.Word (Word16)
1313import Network.HTTP.Types.Header
14−import Network.Socket (Socket)
1514import Network.Wai.Handler.Warp.Buffer (Buffer,BufSize,BufferPool)
1615import qualified Network.Wai.Handler.Warp.Date as D
1716import qualified Network.Wai.Handler.Warp.FdCache as F
1817import qualified Network.Wai.Handler.Warp.Timeout as T
1918
19+#ifndef WINDOWS
20+import System.Posix.Types (Fd)
21+#endif
22+
2023----------------------------------------------------------------
2124
2225-- | TCP port number.
@@ -64,25 +67,28 @@ instance Exception InvalidRequest
6467
6568----------------------------------------------------------------
6669
67−-- | Whether or not 'ConnSendFileOverride' in 'Connection' can be
68−-- overridden. This is a kind of hack to keep the signature of
69−-- 'Connection' clean.
70−data ConnSendFileOverride = NotOverride -- ^ Don't override
71− | Override Socket -- ^ Override with this 'Socket'
70+#ifdef WINDOWS
71+type Fd = ()
72+#endif
7273
73−----------------------------------------------------------------
74+data FileId = FileId {
75+ fileIdPath :: FilePath
76+ , fileIdFd :: Maybe Fd
77+ }
78+
79+-- | fileid, offset, length, hook action, HTTP headers
80+type SendFile = FileId -> Integer -> Integer -> IO () -> [ByteString] -> IO ()
7481
7582-- | Data type to manipulate IO actions for connections.
7683data Connection = Connection
77− { connSendMany :: [ByteString] -> IO ()
78− , connSendAll :: ByteString -> IO ()
79− , connSendFile :: FilePath -> Integer -> Integer -> IO () -> [ByteString] -> IO () -- ^ filepath, offset, length, hook action, HTTP headers
80− , connClose :: IO ()
81− , connRecv :: IO ByteString
82− , connBufferPool :: BufferPool
83− , connWriteBuffer :: Buffer
84− , connBufferSize :: BufSize -- ^ The size of 'connWriteBuffer'
85− , connSendFileOverride :: ConnSendFileOverride
84+ { connSendMany :: [ByteString] -> IO ()
85+ , connSendAll :: ByteString -> IO ()
86+ , connSendFile :: SendFile
87+ , connClose :: IO ()
88+ , connRecv :: IO ByteString
89+ , connBufferPool :: BufferPool
90+ , connWriteBuffer :: Buffer
91+ , connBufferSize :: BufSize -- ^ The size of 'connWriteBuffer'
8692 }
8793
8894----------------------------------------------------------------
warp/test/FdCacheSpec.hs+1−1
@@ -3,7 +3,7 @@
33module FdCacheSpec where
44
55import Test.Hspec
6−#ifdef SENDFILEFD
6+#ifndef WINDOWS
77import Data.IORef
88import Network.Wai.Handler.Warp.FdCache
99import System.Posix.IO (fdRead)
warp/test/SendFileSpec.hsnew file+116
@@ -0,0 +1,116 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+
3+module SendFileSpec where
4+
5+import Control.Exception
6+import Control.Monad (when)
7+import Data.ByteString (ByteString)
8+import qualified Data.ByteString as BS
9+import Network.Wai.Handler.Warp.Buffer
10+import Network.Wai.Handler.Warp.SendFile
11+import Network.Wai.Handler.Warp.Types
12+import System.Directory
13+import System.Exit
14+import qualified System.IO as IO
15+import System.Process (system)
16+import Test.Hspec
17+
18+main :: IO ()
19+main = hspec spec
20+
21+spec :: Spec
22+spec = do
23+ describe "packHeader" $ do
24+ it "returns how much the buffer is consumed (1)" $
25+ tryPackHeader 10 ["foo"] `shouldReturn` 3
26+ it "returns how much the buffer is consumed (2)" $
27+ tryPackHeader 10 ["foo", "bar"] `shouldReturn` 6
28+ it "returns how much the buffer is consumed (3)" $
29+ tryPackHeader 10 ["0123456789"] `shouldReturn` 0
30+ it "returns how much the buffer is consumed (4)" $
31+ tryPackHeader 10 ["01234", "56789"] `shouldReturn` 0
32+ it "returns how much the buffer is consumed (5)" $
33+ tryPackHeader 10 ["01234567890", "12"] `shouldReturn` 3
34+ it "returns how much the buffer is consumed (6)" $
35+ tryPackHeader 10 ["012345678901234567890123456789012", "34"] `shouldReturn` 5
36+
37+ it "sends headers correctly (1)" $
38+ tryPackHeader2 10 ["foo"] "" `shouldReturn` True
39+ it "sends headers correctly (2)" $
40+ tryPackHeader2 10 ["foo", "bar"] "" `shouldReturn` True
41+ it "sends headers correctly (3)" $
42+ tryPackHeader2 10 ["0123456789"] "0123456789" `shouldReturn` True
43+ it "sends headers correctly (4)" $
44+ tryPackHeader2 10 ["01234", "56789"] "0123456789" `shouldReturn` True
45+ it "sends headers correctly (5)" $
46+ tryPackHeader2 10 ["01234567890", "12"] "0123456789" `shouldReturn` True
47+ it "sends headers correctly (6)" $
48+ tryPackHeader2 10 ["012345678901234567890123456789012", "34"] "012345678901234567890123456789" `shouldReturn` True
49+
50+ describe "readSendFile" $ do
51+ it "sends a file correctly (1)" $
52+ tryReadSendFile 10 0 1474 ["foo"] `shouldReturn` ExitSuccess
53+ it "sends a file correctly (2)" $
54+ tryReadSendFile 10 0 1474 ["012345678", "901234"] `shouldReturn` ExitSuccess
55+ it "sends a file correctly (3)" $
56+ tryReadSendFile 10 20 100 ["012345678", "901234"] `shouldReturn` ExitSuccess
57+
58+tryPackHeader :: Int -> [ByteString] -> IO Int
59+tryPackHeader siz hdrs = bracket (allocateBuffer siz) freeBuffer $ \buf ->
60+ packHeader buf siz send hook hdrs 0
61+ where
62+ send _ = return ()
63+ hook = return ()
64+
65+tryPackHeader2 :: Int -> [ByteString] -> ByteString -> IO Bool
66+tryPackHeader2 siz hdrs ans = bracket setup teardown $ \buf -> do
67+ _ <- packHeader buf siz send hook hdrs 0
68+ checkFile outputFile ans
69+ where
70+ setup = allocateBuffer siz
71+ teardown buf = freeBuffer buf >> removeFileIfExists outputFile
72+ outputFile = "tempfile"
73+ send = BS.appendFile outputFile
74+ hook = return ()
75+
76+tryReadSendFile :: Int -> Integer -> Integer -> [ByteString] -> IO ExitCode
77+tryReadSendFile siz off len hdrs = bracket setup teardown $ \buf -> do
78+ mapM_ (BS.appendFile expectedFile) hdrs
79+ copyfile inputFile expectedFile off len
80+ readSendFile buf siz send fid off len hook hdrs
81+ compareFiles expectedFile outputFile
82+ where
83+ hook = return ()
84+ setup = allocateBuffer siz
85+ teardown buf = do
86+ freeBuffer buf
87+ removeFileIfExists outputFile
88+ removeFileIfExists expectedFile
89+ inputFile = "test/inputFile"
90+ outputFile = "outputFile"
91+ expectedFile = "expectedFile"
92+ fid = FileId inputFile Nothing
93+ send = BS.appendFile outputFile
94+
95+checkFile :: FilePath -> ByteString -> IO Bool
96+checkFile path bs = do
97+ exist <- doesFileExist path
98+ if exist then do
99+ bs' <- BS.readFile path
100+ return $ bs == bs'
101+ else
102+ return $ bs == ""
103+
104+compareFiles :: FilePath -> FilePath -> IO ExitCode
105+compareFiles file1 file2 = system $ "cmp -s " ++ file1 ++ " " ++ file2
106+
107+copyfile :: FilePath -> FilePath -> Integer -> Integer -> IO ()
108+copyfile src dst off len =
109+ IO.withBinaryFile src IO.ReadMode $ \h -> do
110+ IO.hSeek h IO.AbsoluteSeek off
111+ BS.hGet h (fromIntegral len) >>= BS.appendFile dst
112+
113+removeFileIfExists :: FilePath -> IO ()
114+removeFileIfExists file = do
115+ exist <- doesFileExist file
116+ when exist $ removeFile file
warp/test/inputFilenew file+100
@@ -0,0 +1,100 @@
1+A acid
2+abacus major
3+abacus pythagoricus
4+A battery
5+abbey counter
6+abbey laird
7+abbey lands
8+abbey lubber
9+abbot cloth
10+Abbott papyrus
11+abb wool
12+A-b-c book
13+A-b-c method
14+abdomino-uterotomy
15+Abdul-baha
16+a-be
17+aberrant duct
18+aberration constant
19+abiding place
20+able-bodied
21+able-bodiedness
22+able-minded
23+able-mindedness
24+able seaman
25+aboli fruit
26+A bond
27+Abor-miri
28+a-borning
29+about-face
30+about ship
31+about-sledge
32+above-cited
33+above-found
34+above-given
35+above-mentioned
36+above-named
37+above-quoted
38+above-reported
39+above-said
40+above-water
41+above-written
42+Abraham-man
43+abraum salts
44+abraxas stone
45+Abri audit culture
46+abruptly acuminate
47+abruptly pinnate
48+absciss layer
49+absence state
50+absentee voting
51+absent-minded
52+absent-mindedly
53+absent-mindedness
54+absent treatment
55+absent voter
56+Absent voting
57+absinthe green
58+absinthe oil
59+absorption bands
60+absorption circuit
61+absorption coefficient
62+absorption current
63+absorption dynamometer
64+absorption factor
65+absorption lines
66+absorption pipette
67+absorption screen
68+absorption spectrum
69+absorption system
70+A b station
71+abstinence theory
72+abstract group
73+Abt system
74+abundance declaree
75+aburachan seed
76+abutment arch
77+abutment pier
78+abutting joint
79+acacia veld
80+academy blue
81+academy board
82+academy figure
83+acajou balsam
84+acanthosis nigricans
85+acanthus family
86+acanthus leaf
87+acaroid resin
88+Acca larentia
89+acceleration note
90+accelerator nerve
91+accent mark
92+acceptance bill
93+acceptance house
94+acceptance supra protest
95+acceptor supra protest
96+accession book
97+accession number
98+accession service
99+access road
100+accident insurance
warp/warp.cabal+3
@@ -115,6 +115,7 @@ Test-Suite spec
115115 ResponseHeaderSpec
116116 ResponseSpec
117117 RunSpec
118+ SendFileSpec
118119 Hs-Source-Dirs: test, .
119120 Type: exitcode-stdio-1.0
120121
@@ -145,6 +146,8 @@ Test-Suite spec
145146 , async
146147 , vault
147148 , stm >= 2.3
149+ , directory
150+ , process
148151 if flag(use-bytestring-builder)
149152 Build-Depends: bytestring < 0.10.2.0
150153 , bytestring-builder