on master help sign in
Using http2's new APIs: token based headers.
commit7c2440981daeb54b941a7d6f5d384c5fcaaf5346 authorKazu Yamamoto <kazu@iij.ad.jp> dateThu 17 Mar 2016, 10:01 (11 years ago) parent251c744
11 files changed+416−243
warp/Network/Wai/Handler/Warp/File.hs+3−26 warp/Network/Wai/Handler/Warp/HTTP2/File.hs+192 warp/Network/Wai/Handler/Warp/HTTP2/HPACK.hs+59−26 warp/Network/Wai/Handler/Warp/HTTP2/Receiver.hs+32−29 warp/Network/Wai/Handler/Warp/HTTP2/Request.hs+31−125 warp/Network/Wai/Handler/Warp/HTTP2/Sender.hs+16−6 warp/Network/Wai/Handler/Warp/HTTP2/Types.hs+12−12 warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+35−19 warp/Network/Wai/Handler/Warp/HashMap.hs+3 warp/Network/Wai/Handler/Warp/PackInt.hs+31 warp/warp.cabal+2
warp/Network/Wai/Handler/Warp/File.hs+3−26
@@ -7,25 +7,21 @@ module Network.Wai.Handler.Warp.File (
77 , conditionalRequest
88 , addContentHeadersForFilePart
99 , parseByteRanges
10− , packInteger -- testing
1110 ) where
1211
1312import Control.Applicative ((<|>))
14−import Control.Monad
1513import Data.Array ((!))
1614import qualified Data.ByteString as B hiding (pack)
1715import qualified Data.ByteString.Char8 as B (pack, readInteger)
18−import Data.ByteString.Internal (ByteString(..), unsafeCreate)
16+import Data.ByteString (ByteString)
1917import Data.Maybe (fromMaybe)
20−import Data.Word8 (Word8)
21−import Foreign.Ptr (Ptr, plusPtr)
22−import Foreign.Storable (poke)
2318import Network.HTTP.Date
2419import qualified Network.HTTP.Types as H
2520import qualified Network.HTTP.Types.Header as H
2621import Network.Wai
2722import qualified Network.Wai.Handler.Warp.FileInfoCache as I
2823import Network.Wai.Handler.Warp.Header
24+import Network.Wai.Handler.Warp.PackInt
2925import Numeric (showInt)
3026
3127#ifndef MIN_VERSION_http_types
@@ -183,28 +179,9 @@ addContentHeaders hs off len size
183179 | otherwise = let !ctrng = contentRangeHeader off (off + len - 1) size
184180 in ctrng:hs'
185181 where
186− !lengthBS = packInteger len
182+ !lengthBS = packIntegral len
187183 !hs' = (H.hContentLength, lengthBS) : (acceptRange,"bytes") : hs
188184
189−-- |
190−--
191−-- prop> packInteger (abs n) == B.pack (show (abs n))
192−-- prop> \(Large n) -> let n' = fromIntegral (abs n :: Int) in packInteger n' == B.pack (show n')
193−
194−packInteger :: Integer -> ByteString
195−packInteger 0 = "0"
196−packInteger n | n < 0 = error "packInteger"
197−packInteger n = unsafeCreate len go0
198− where
199− n' = fromIntegral n + 1 :: Double
200− len = ceiling $ logBase 10 n'
201− go0 p = go n $ p `plusPtr` (len - 1)
202− go :: Integer -> Ptr Word8 -> IO ()
203− go i p = do
204− let (d,r) = i `divMod` 10
205− poke p (48 + fromIntegral r)
206− when (d /= 0) $ go d (p `plusPtr` (-1))
207−
208185-- |
209186--
210187-- >>> addContentHeadersForFilePart [] (FilePart 2 10 16)
warp/Network/Wai/Handler/Warp/HTTP2/File.hsnew file+192
@@ -0,0 +1,192 @@
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE OverloadedStrings #-}
3+{-# LANGUAGE BangPatterns, RecordWildCards #-}
4+
5+module Network.Wai.Handler.Warp.HTTP2.File (
6+ RspFileInfo(..)
7+ , conditionalRequest
8+ , addContentHeadersForFilePart
9+ , parseByteRanges
10+ ) where
11+
12+import Control.Applicative ((<|>))
13+import qualified Data.ByteString as B hiding (pack)
14+import qualified Data.ByteString.Char8 as B (pack, readInteger)
15+import Data.ByteString (ByteString)
16+import Data.Maybe (fromMaybe)
17+import Network.HTTP.Date
18+import qualified Network.HTTP.Types as H
19+import Network.Wai
20+import qualified Network.Wai.Handler.Warp.FileInfoCache as I
21+import Network.Wai.Handler.Warp.PackInt
22+import Numeric (showInt)
23+import Network.HPACK
24+import Network.HPACK.Token
25+
26+#ifndef MIN_VERSION_http_types
27+#define MIN_VERSION_http_types(x,y,z) 1
28+#endif
29+
30+-- $setup
31+-- >>> import Test.QuickCheck
32+
33+----------------------------------------------------------------
34+
35+data RspFileInfo = WithoutBody !H.Status
36+ | WithBody !H.Status !TokenHeaderList !Integer !Integer
37+ deriving (Eq,Show)
38+
39+----------------------------------------------------------------
40+
41+conditionalRequest :: I.FileInfo
42+ -> TokenHeaderList -- Response
43+ -> ValueTable -- Request
44+ -> RspFileInfo
45+conditionalRequest (I.FileInfo _ size mtime date) ths0 reqtbl = case condition of
46+ nobody@(WithoutBody _) -> nobody
47+ WithBody s _ off len -> let !hs = (tokenLastModified,date) :
48+ addContentHeaders ths0 off len size
49+ in WithBody s hs off len
50+ where
51+ !mcondition = ifmodified reqtbl size mtime
52+ <|> ifunmodified reqtbl size mtime
53+ <|> ifrange reqtbl size mtime
54+ !condition = fromMaybe (unconditional reqtbl size) mcondition
55+
56+----------------------------------------------------------------
57+
58+{-# INLINE ifModifiedSince #-}
59+ifModifiedSince :: ValueTable -> Maybe HTTPDate
60+ifModifiedSince reqtbl = getHeaderValue tokenIfModifiedSince reqtbl >>= parseHTTPDate
61+
62+{-# INLINE ifUnmodifiedSince #-}
63+ifUnmodifiedSince :: ValueTable -> Maybe HTTPDate
64+ifUnmodifiedSince reqtbl = getHeaderValue tokenIfUnmodifiedSince reqtbl >>= parseHTTPDate
65+
66+{-# INLINE ifRange #-}
67+ifRange :: ValueTable -> Maybe HTTPDate
68+ifRange reqtbl = getHeaderValue tokenIfRange reqtbl >>= parseHTTPDate
69+
70+----------------------------------------------------------------
71+
72+{-# INLINE ifmodified #-}
73+ifmodified :: ValueTable -> Integer -> HTTPDate -> Maybe RspFileInfo
74+ifmodified reqtbl size mtime = do
75+ date <- ifModifiedSince reqtbl
76+ return $ if date /= mtime
77+ then unconditional reqtbl size
78+ else WithoutBody H.notModified304
79+
80+{-# INLINE ifunmodified #-}
81+ifunmodified :: ValueTable -> Integer -> HTTPDate -> Maybe RspFileInfo
82+ifunmodified reqtbl size mtime = do
83+ date <- ifUnmodifiedSince reqtbl
84+ return $ if date == mtime
85+ then unconditional reqtbl size
86+ else WithoutBody H.preconditionFailed412
87+
88+{-# INLINE ifrange #-}
89+ifrange :: ValueTable -> Integer -> HTTPDate -> Maybe RspFileInfo
90+ifrange reqtbl size mtime = do
91+ date <- ifRange reqtbl
92+ rng <- getHeaderValue tokenRange reqtbl
93+ return $ if date == mtime
94+ then parseRange rng size
95+ else WithBody H.ok200 [] 0 size
96+
97+{-# INLINE unconditional #-}
98+unconditional :: ValueTable -> Integer -> RspFileInfo
99+unconditional reqtbl size = case getHeaderValue tokenRange reqtbl of
100+ Nothing -> WithBody H.ok200 [] 0 size
101+ Just rng -> parseRange rng size
102+
103+----------------------------------------------------------------
104+
105+{-# INLINE parseRange #-}
106+parseRange :: ByteString -> Integer -> RspFileInfo
107+parseRange rng size = case parseByteRanges rng of
108+ Nothing -> WithoutBody H.requestedRangeNotSatisfiable416
109+ Just [] -> WithoutBody H.requestedRangeNotSatisfiable416
110+ Just (r:_) -> let (!beg, !end) = checkRange r size
111+ !len = end - beg + 1
112+ s = if beg == 0 && end == size - 1 then
113+ H.ok200
114+ else
115+ H.partialContent206
116+ in WithBody s [] beg len
117+
118+{-# INLINE checkRange #-}
119+checkRange :: H.ByteRange -> Integer -> (Integer, Integer)
120+checkRange (H.ByteRangeFrom beg) size = (beg, size - 1)
121+checkRange (H.ByteRangeFromTo beg end) size = (beg, min (size - 1) end)
122+checkRange (H.ByteRangeSuffix count) size = (max 0 (size - count), size - 1)
123+
124+{-# INLINE parseByteRanges #-}
125+-- | Parse the value of a Range header into a 'H.ByteRanges'.
126+parseByteRanges :: B.ByteString -> Maybe H.ByteRanges
127+parseByteRanges bs1 = do
128+ bs2 <- stripPrefix "bytes=" bs1
129+ (r, bs3) <- range bs2
130+ ranges (r:) bs3
131+ where
132+ range bs2 = do
133+ (i, bs3) <- B.readInteger bs2
134+ if i < 0 -- has prefix "-" ("-0" is not valid, but here treated as "0-")
135+ then Just (H.ByteRangeSuffix (negate i), bs3)
136+ else do
137+ bs4 <- stripPrefix "-" bs3
138+ case B.readInteger bs4 of
139+ Just (j, bs5) | j >= i -> Just (H.ByteRangeFromTo i j, bs5)
140+ _ -> Just (H.ByteRangeFrom i, bs4)
141+ ranges front bs3
142+ | B.null bs3 = Just (front [])
143+ | otherwise = do
144+ bs4 <- stripPrefix "," bs3
145+ (r, bs5) <- range bs4
146+ ranges (front . (r:)) bs5
147+
148+ stripPrefix x y
149+ | x `B.isPrefixOf` y = Just (B.drop (B.length x) y)
150+ | otherwise = Nothing
151+
152+----------------------------------------------------------------
153+
154+{-# INLINE contentRangeHeader #-}
155+-- | @contentRangeHeader beg end total@ constructs a Content-Range 'H.Header'
156+-- for the range specified.
157+contentRangeHeader :: Integer -> Integer -> Integer -> TokenHeader
158+contentRangeHeader beg end total = (tokenContentRange, range)
159+ where
160+ range = B.pack
161+ -- building with ShowS
162+ $ 'b' : 'y': 't' : 'e' : 's' : ' '
163+ : (if beg > end then ('*':) else
164+ showInt beg
165+ . ('-' :)
166+ . showInt end)
167+ ( '/'
168+ : showInt total "")
169+
170+{-# INLINE addContentHeaders #-}
171+addContentHeaders :: TokenHeaderList -> Integer -> Integer -> Integer -> TokenHeaderList
172+addContentHeaders ths off len size
173+ | len == size = ths'
174+ | otherwise = let !ctrng = contentRangeHeader off (off + len - 1) size
175+ in ctrng:ths'
176+ where
177+ !lengthBS = packIntegral len
178+ !ths' = (tokenContentLength, lengthBS) : (tokenAcceptRanges,"bytes") : ths
179+
180+{-# INLINE addContentHeadersForFilePart #-}
181+-- |
182+--
183+-- >>> addContentHeadersForFilePart [] (FilePart 2 10 16)
184+-- [("Content-Range","bytes 2-11/16"),("Content-Length","10"),("Accept-Ranges","bytes")]
185+-- >>> addContentHeadersForFilePart [] (FilePart 0 16 16)
186+-- [("Content-Length","16"),("Accept-Ranges","bytes")]
187+addContentHeadersForFilePart :: TokenHeaderList -> FilePart -> TokenHeaderList
188+addContentHeadersForFilePart hs part = addContentHeaders hs off len size
189+ where
190+ off = filePartOffset part
191+ len = filePartByteCount part
192+ size = filePartFileSize part
warp/Network/Wai/Handler/Warp/HTTP2/HPACK.hs+59−26
@@ -1,18 +1,22 @@
1−{-# LANGUAGE OverloadedStrings #-}
1+{-# LANGUAGE OverloadedStrings, BangPatterns #-}
22{-# LANGUAGE NamedFieldPuns, RecordWildCards #-}
33
4−module Network.Wai.Handler.Warp.HTTP2.HPACK where
4+module Network.Wai.Handler.Warp.HTTP2.HPACK (
5+ hpackEncodeHeader
6+ , hpackEncodeHeaderLoop
7+ , hpackDecodeHeader
8+ , just
9+ ) where
510
6−import Control.Arrow (first)
711import qualified Control.Exception as E
8−import qualified Data.ByteString.Char8 as B8
9−import Data.CaseInsensitive (foldedCase)
12+import Control.Monad (unless)
13+import Data.ByteString (ByteString)
1014import Network.HPACK hiding (Buffer)
15+import Network.HPACK.Token
1116import qualified Network.HTTP.Types as H
1217import Network.HTTP2
1318import Network.Wai.Handler.Warp.HTTP2.Types
14−import Network.Wai.Handler.Warp.Header
15−import Network.Wai.Handler.Warp.Response
19+import Network.Wai.Handler.Warp.PackInt
1620import qualified Network.Wai.Handler.Warp.Settings as S
1721import Network.Wai.Handler.Warp.Types
1822
@@ -26,27 +30,56 @@ strategy = EncodeStrategy { compressionAlgo = Linear, useHuffman = False }
2630-- So, we don't need to split it.
2731hpackEncodeHeader :: Context -> Buffer -> BufSize
2832 -> InternalInfo -> S.Settings
29− -> H.Status -> H.ResponseHeaders
30− -> IO (HeaderList, Int)
31−hpackEncodeHeader Context{..} buf siz ii settings s hdr0 = do
32− hdr1 <- addServerAndDate hdr0
33− let hs = (":status", status) : map (first foldedCase) hdr1
34− encodeHeaderBuffer buf siz strategy True encodeDynamicTable hs
35− where
36− status = B8.pack $ show $ H.statusCode s
37− getdate = getDate ii
38− rspidxhdr = indexResponseHeader hdr0
39− defServer = S.settingsServerName settings
40− addServerAndDate = addDate getdate rspidxhdr . addServer defServer rspidxhdr
41−
42−hpackEncodeHeaderLoop :: Context -> Buffer -> BufSize -> HeaderList
43− -> IO (HeaderList, Int)
33+ -> H.Status -> (TokenHeaderList,ValueTable)
34+ -> IO (TokenHeaderList, Int)
35+hpackEncodeHeader Context{..} buf siz ii settings s (ths0,tbl) = do
36+ let !defServer = S.settingsServerName settings
37+ !ths1 = addHeader tokenServer defServer tbl ths0
38+ date <- getDate ii
39+ let !ths2 = addHeader tokenDate date tbl ths1
40+ !status = packIntegral $ H.statusCode s
41+ !ths3 = (tokenStatus, status) : ths2
42+ encodeTokenHeader buf siz strategy True encodeDynamicTable ths3
43+
44+{-# INLINE addHeader #-}
45+addHeader :: Token -> ByteString -> ValueTable -> TokenHeaderList -> TokenHeaderList
46+addHeader t v tbl ths = case getHeaderValue t tbl of
47+ Nothing -> (t,v) : ths
48+ Just _ -> ths
49+
50+hpackEncodeHeaderLoop :: Context -> Buffer -> BufSize -> TokenHeaderList
51+ -> IO (TokenHeaderList, Int)
4452hpackEncodeHeaderLoop Context{..} buf siz hs =
45− encodeHeaderBuffer buf siz strategy False encodeDynamicTable hs
53+ encodeTokenHeader buf siz strategy False encodeDynamicTable hs
4654
4755----------------------------------------------------------------
4856
49−hpackDecodeHeader :: HeaderBlockFragment -> Context -> IO HeaderList
50−hpackDecodeHeader hdrblk Context{..} =
51− decodeHeader decodeDynamicTable hdrblk `E.catch` \(E.SomeException _) ->
57+hpackDecodeHeader :: HeaderBlockFragment -> Context -> IO (TokenHeaderList, ValueTable)
58+hpackDecodeHeader hdrblk Context{..} = do
59+ tbl@(_,vt) <- decodeTokenHeader decodeDynamicTable hdrblk `E.catch` handl
60+ unless (checkRequestHeader vt) $
61+ E.throwIO $ ConnectionError ProtocolError "the header key is illegal"
62+ return tbl
63+ where
64+ handl IllegalHeaderName =
65+ E.throwIO $ ConnectionError ProtocolError "the header key is illegal"
66+ handl _ =
5267 E.throwIO $ ConnectionError CompressionError "cannot decompress the header"
68+
69+{-# INLINE checkRequestHeader #-}
70+checkRequestHeader :: ValueTable -> Bool
71+checkRequestHeader reqvt
72+ | getHeaderValue tokenStatus reqvt /= Nothing = False
73+ | getHeaderValue tokenPath reqvt == Nothing = False
74+ | getHeaderValue tokenMethod reqvt == Nothing = False
75+ | getHeaderValue tokenAuthority reqvt == Nothing = False
76+ | getHeaderValue tokenConnection reqvt /= Nothing = False
77+ | just (getHeaderValue tokenTE reqvt) (/= "trailers") = False
78+ | otherwise = True
79+
80+{-# INLINE just #-}
81+just :: Maybe a -> (a -> Bool) -> Bool
82+just Nothing _ = False
83+just (Just x) p
84+ | p x = True
85+ | otherwise = False
warp/Network/Wai/Handler/Warp/HTTP2/Receiver.hs+32−29
@@ -15,7 +15,8 @@ import qualified Control.Exception as E
1515import Control.Monad (when, unless, void)
1616import Data.ByteString (ByteString)
1717import qualified Data.ByteString as BS
18−import Data.Maybe (isJust)
18+import Network.HPACK
19+import Network.HPACK.Token
1920import Network.HTTP2
2021import Network.HTTP2.Priority (toPrecedence, delete, prepare)
2122import Network.Wai.Handler.Warp.HTTP2.EncodeFrame
@@ -23,6 +24,7 @@ import Network.Wai.Handler.Warp.HTTP2.HPACK
2324import Network.Wai.Handler.Warp.HTTP2.Request
2425import Network.Wai.Handler.Warp.HTTP2.Types
2526import Network.Wai.Handler.Warp.IORef
27+import Network.Wai.Handler.Warp.ReadInt
2628import Network.Wai.Handler.Warp.Types
2729
2830----------------------------------------------------------------
@@ -103,30 +105,26 @@ frameReceiver ctx mkreq recvN = loop 0 `E.catch` sendGoaway
103105 state <- readIORef streamState
104106 state' <- stream ftyp header pl ctx state strm
105107 case state' of
106− Open (NoBody hdr pri) -> do
108+ Open (NoBody tbl@(_,reqvt) pri) -> do
107109 resetContinued
108− case validateHeaders hdr of
109− Just vh -> do
110− when (isJust (vhCL vh) && vhCL vh /= Just 0) $
111− E.throwIO $ StreamError ProtocolError streamId
112− writeIORef streamPrecedence $ toPrecedence pri
113− writeIORef streamState HalfClosed
114− let (!req, !ii) = mkreq vh (return "")
115− atomically $ writeTQueue inputQ $ Input strm req ii
116− Nothing -> E.throwIO $ StreamError ProtocolError streamId
117− Open (HasBody hdr pri) -> do
110+ let mcl = readInt <$> getHeaderValue tokenContentLength reqvt
111+ when (just mcl (== (0 :: Int))) $
112+ E.throwIO $ StreamError ProtocolError streamId
113+ writeIORef streamPrecedence $ toPrecedence pri
114+ writeIORef streamState HalfClosed
115+ let (!req, !ii) = mkreq tbl (return "")
116+ atomically $ writeTQueue inputQ $ Input strm req reqvt ii
117+ Open (HasBody tbl@(_,reqvt) pri) -> do
118118 resetContinued
119− case validateHeaders hdr of
120− Just vh -> do
121− q <- newTQueueIO
122− writeIORef streamPrecedence $ toPrecedence pri
123− writeIORef streamState (Open (Body q))
124− writeIORef streamContentLength $ vhCL vh
125− readQ <- newReadBody q
126− bodySource <- mkSource readQ
127− let (!req, !ii) = mkreq vh (readSource bodySource)
128− atomically $ writeTQueue inputQ $ Input strm req ii
129− Nothing -> E.throwIO $ StreamError ProtocolError streamId
119+ q <- newTQueueIO
120+ writeIORef streamPrecedence $ toPrecedence pri
121+ writeIORef streamState (Open (Body q))
122+ let mcl = readInt <$> getHeaderValue tokenContentLength reqvt
123+ writeIORef streamContentLength mcl
124+ readQ <- newReadBody q
125+ bodySource <- mkSource readQ
126+ let (!req, !ii) = mkreq tbl (readSource bodySource)
127+ atomically $ writeTQueue inputQ $ Input strm req reqvt ii
130128 s@(Open Continued{}) -> do
131129 setContinued
132130 writeIORef streamState s
@@ -219,11 +217,14 @@ control _ _ _ _ =
219217
220218----------------------------------------------------------------
221219
220+{-# INLINE guardIt #-}
222221guardIt :: Either HTTP2Error a -> IO a
223222guardIt x = case x of
224223 Left err -> E.throwIO err
225224 Right frame -> return frame
226225
226+
227+{-# INLINE checkPriority #-}
227228checkPriority :: Priority -> StreamId -> IO ()
228229checkPriority p me
229230 | dep == me = E.throwIO $ StreamError ProtocolError me
@@ -242,11 +243,11 @@ stream FrameHeaders header@FrameHeader{flags} bs ctx (Open JustOpened) Stream{st
242243 let !endOfStream = testEndStream flags
243244 !endOfHeader = testEndHeader flags
244245 if endOfHeader then do
245− hdr <- hpackDecodeHeader frag ctx
246+ tbl <- hpackDecodeHeader frag ctx -- fixme
246247 return $ if endOfStream then
247− Open (NoBody hdr pri)
248+ Open (NoBody tbl pri)
248249 else
249− Open (HasBody hdr pri)
250+ Open (HasBody tbl pri)
250251 else do
251252 let !siz = BS.length frag
252253 return $ Open $ Continued [frag] siz 1 endOfStream pri
@@ -300,11 +301,11 @@ stream FrameContinuation FrameHeader{flags} frag ctx (Open (Continued rfrags siz
300301 E.throwIO $ ConnectionError EnhanceYourCalm "Header is too fragmented"
301302 if endOfHeader then do
302303 let !hdrblk = BS.concat $ reverse rfrags'
303− hdr <- hpackDecodeHeader hdrblk ctx
304+ tbl <- hpackDecodeHeader hdrblk ctx
304305 return $ if endOfStream then
305− Open (NoBody hdr pri)
306+ Open (NoBody tbl pri)
306307 else
307− Open (HasBody hdr pri)
308+ Open (HasBody tbl pri)
308309 else
309310 return $ Open $ Continued rfrags' siz' n' endOfStream pri
310311
@@ -350,11 +351,13 @@ stream _ FrameHeader{streamId} _ _ _ _ = E.throwIO $ StreamError ProtocolError s
350351
351352----------------------------------------------------------------
352353
354+{-# INLINE newReadBody #-}
353355newReadBody :: TQueue ByteString -> IO (IO ByteString)
354356newReadBody q = do
355357 ref <- newIORef False
356358 return $ readBody q ref
357359
360+{-# INLINE readBody #-}
358361readBody :: TQueue ByteString -> IORef Bool -> IO ByteString
359362readBody q ref = do
360363 eof <- readIORef ref
warp/Network/Wai/Handler/Warp/HTTP2/Request.hs+31−125
@@ -4,163 +4,69 @@
44module Network.Wai.Handler.Warp.HTTP2.Request (
55 mkRequest
66 , MkReq
7− , ValidHeaders(..)
8− , validateHeaders
97 ) where
108
11−#if __GLASGOW_HASKELL__ < 709
12−import Control.Applicative ((<$>))
13−#endif
9+import Control.Applicative ((<|>))
10+import Control.Arrow (first)
1411import Data.ByteString (ByteString)
15−import qualified Data.ByteString as BS
1612import qualified Data.ByteString.Char8 as B8
17−import Data.CaseInsensitive (mk)
18−import Data.Maybe (isJust)
13+import Data.Maybe (fromJust)
1914import qualified Data.Vault.Lazy as Vault
20−#if __GLASGOW_HASKELL__ < 709
21−import Data.Monoid (mempty)
22−#endif
23−import Data.Word8 (isUpper,_colon)
2415import Network.HPACK
25−import Network.HTTP.Types (RequestHeaders)
16+import Network.HPACK.Token
2617import qualified Network.HTTP.Types as H
2718import Network.Socket (SockAddr)
2819import Network.Wai
2920import Network.Wai.Handler.Warp.HTTP2.Types
3021import Network.Wai.Handler.Warp.HashMap (hashByteString)
31−import Network.Wai.Handler.Warp.ReadInt
3222import Network.Wai.Handler.Warp.Request (pauseTimeoutKey, getFileInfoKey)
33−import Network.Wai.Handler.Warp.Types
34−import Network.Wai.Internal (Request(..))
3523import qualified Network.Wai.Handler.Warp.Settings as S (Settings, settingsNoParsePath)
3624import qualified Network.Wai.Handler.Warp.Timeout as Timeout
25+import Network.Wai.Handler.Warp.Types
26+import Network.Wai.Internal (Request(..))
3727
38−data ValidHeaders = ValidHeaders {
39− vhMethod :: !ByteString
40− , vhPath :: !ByteString
41− , vhAuth :: !(Maybe ByteString)
42− , vhRange :: !(Maybe ByteString)
43− , vhReferer :: !(Maybe ByteString)
44− , vhUA :: !(Maybe ByteString)
45− , vhCL :: !(Maybe Int)
46− , vhHeader :: !RequestHeaders
47− } deriving Show
48−
49−type MkReq = ValidHeaders -> IO ByteString -> (Request,InternalInfo)
28+type MkReq = (TokenHeaderList,ValueTable) -> IO ByteString -> (Request,InternalInfo)
5029
5130mkRequest :: InternalInfo1 -> S.Settings -> SockAddr -> MkReq
52−mkRequest ii1 settings addr (ValidHeaders m p ma mrng mrr mua _ hdr) body =
53− (req,ii)
31+mkRequest ii1 settings addr (reqths,reqvt) body = (req,ii)
5432 where
55− (unparsedPath,query) = B8.break (=='?') p
56− !path = H.extractPath unparsedPath
57− !rawPath = if S.settingsNoParsePath settings then unparsedPath else path
58− !h = hashByteString rawPath
5933 !req = Request {
60− requestMethod = m
34+ requestMethod = colonMethod
6135 , httpVersion = http2ver
6236 , rawPathInfo = rawPath
6337 , pathInfo = H.decodePathSegments path
6438 , rawQueryString = query
6539 , queryString = H.parseQuery query
66− , requestHeaders = case ma of
67− Nothing -> hdr
68− Just hv -> (mk "host", hv) : hdr
40+ , requestHeaders = headers
6941 , isSecure = True
7042 , remoteHost = addr
7143 , requestBody = body
7244 , vault = vaultValue
7345 , requestBodyLength = ChunkedBody -- fixme
74− , requestHeaderHost = ma
75− , requestHeaderRange = mrng
76− , requestHeaderReferer = mrr
77− , requestHeaderUserAgent = mua
46+ , requestHeaderHost = mHost
47+ , requestHeaderRange = mRange
48+ , requestHeaderReferer = mReferer
49+ , requestHeaderUserAgent = mUserAgent
7850 }
51+ headers = map (first tokenKey) ths
52+ where
53+ ths = case mHost of
54+ Nothing -> (tokenHost, colonAuth) : reqths
55+ Just _ -> reqths
56+ !colonPath = fromJust $ getHeaderValue tokenPath reqvt
57+ !colonMethod = fromJust $ getHeaderValue tokenMethod reqvt
58+ !mAuth = getHeaderValue tokenAuthority reqvt
59+ !colonAuth = fromJust $ mAuth
60+ !mHost = getHeaderValue tokenHost reqvt <|> mAuth
61+ !mRange = getHeaderValue tokenRange reqvt
62+ !mReferer = getHeaderValue tokenReferer reqvt
63+ !mUserAgent = getHeaderValue tokenUserAgent reqvt
64+ (unparsedPath,query) = B8.break (=='?') colonPath
65+ !path = H.extractPath unparsedPath
66+ !rawPath = if S.settingsNoParsePath settings then unparsedPath else path
67+ !h = hashByteString rawPath
7968 !ii = toInternalInfo ii1 h
8069 !th = threadHandle ii
8170 !vaultValue = Vault.insert pauseTimeoutKey (Timeout.pause th)
8271 $ Vault.insert getFileInfoKey (getFileInfo ii)
8372 Vault.empty
84−
85−----------------------------------------------------------------
86−
87−data Special = Special {
88− colonMethod :: !(Maybe ByteString)
89− , colonPath :: !(Maybe ByteString)
90− , colonAuth :: !(Maybe ByteString)
91− , sRange :: !(Maybe ByteString)
92− , sReferer :: !(Maybe ByteString)
93− , sUA :: !(Maybe ByteString)
94− , contentLen :: !(Maybe ByteString)
95− } deriving Show
96−
97−emptySpecial :: Special
98−emptySpecial = Special Nothing Nothing Nothing Nothing Nothing Nothing Nothing
99−
100−-- |
101−--
102−-- >>> validateHeaders [(":method","GET"),(":path","path")]
103−-- Just (ValidHeaders {vhMethod = "GET", vhPath = "path", vhAuth = Nothing, vhRange = Nothing, vhReferer = Nothing, vhUA = Nothing, vhCL = Nothing, vhHeader = []})
104−-- >>> validateHeaders [(":method","GET"),(":path","path"),(":authority","authority"),("accept-language","en")]
105−-- Just (ValidHeaders {vhMethod = "GET", vhPath = "path", vhAuth = Just "authority", vhRange = Nothing, vhReferer = Nothing, vhUA = Nothing, vhCL = Nothing, vhHeader = [("accept-language","en")]})
106−-- >>> validateHeaders [(":method","GET"),(":path","path"),("cookie","a=b"),("accept-language","en"),("cookie","c=d"),("cookie","e=f")]
107−-- Just (ValidHeaders {vhMethod = "GET", vhPath = "path", vhAuth = Nothing, vhRange = Nothing, vhReferer = Nothing, vhUA = Nothing, vhCL = Nothing, vhHeader = [("accept-language","en"),("cookie","a=b; c=d; e=f")]})
108−validateHeaders :: HeaderList -> Maybe ValidHeaders
109−validateHeaders hs = case pseudo hs emptySpecial of
110− Just (Special (Just m) (Just p) ma mrng mrr mua mcl, !h)
111− -> Just $! ValidHeaders m p ma mrng mrr mua (readInt <$> mcl) h
112− _ -> Nothing
113− where
114− pseudo [] !s = Just (s,[])
115− pseudo h@((k,v):kvs) !s
116− | k == ":method" = if isJust (colonMethod s) then
117− Nothing
118− else
119− pseudo kvs (s { colonMethod = Just v })
120− | k == ":path" = if isJust (colonPath s) then
121− Nothing
122− else
123− pseudo kvs (s { colonPath = Just v })
124− | k == ":authority" = if isJust (colonAuth s) then
125− Nothing
126− else
127− pseudo kvs (s { colonAuth = Just v })
128− | k == ":scheme" = pseudo kvs s -- fixme: how to store :scheme?
129− | isPseudo k = Nothing
130− | otherwise = normal h (s,id,id)
131−
132− normal [] (!s,b,c) = Just (s, mkH b c)
133− normal ((k,v):kvs) (!s,b,c)
134− | isPseudo k = Nothing
135− | k == "connection" = Nothing
136− | k == "te" = if v == "trailers" then
137− normal kvs (s, b . ((mk k,v) :), c)
138− else
139− Nothing
140− | k == "range"
141− = normal kvs (s {sRange = Just v }, b . ((mk k,v) :), c)
142− | k == "referer"
143− = normal kvs (s { sReferer = Just v }, b . ((mk k,v) :), c)
144− | k == "user-agent"
145− = normal kvs (s { sUA = Just v }, b . ((mk k,v) :), c)
146− | k == "content-length"
147− = normal kvs (s { contentLen = Just v }, b . ((mk k,v) :), c)
148− | k == "host" = if isJust (colonAuth s) then
149− normal kvs (s, b, c)
150− else
151− normal kvs (s { colonAuth = Just v }, b, c)
152− | k == "cookie" = normal kvs (s, b, c . (v:))
153− | otherwise = case BS.find isUpper k of
154− Nothing -> normal kvs (s, b . ((mk k,v) :), c)
155− Just _ -> Nothing
156−
157− mkH b c = h
158− where
159− !h = b anchor
160− !cookieList = c []
161− !anchor
162− | null cookieList = []
163− | otherwise = let !v = BS.intercalate "; " cookieList
164− in [("cookie",v)]
165− isPseudo "" = False
166− isPseudo k = BS.head k == _colon
warp/Network/Wai/Handler/Warp/HTTP2/Sender.hs+16−6
@@ -44,14 +44,17 @@ data Leftover = LZero
4444
4545----------------------------------------------------------------
4646
47+{-# INLINE getStreamWindowSize #-}
4748getStreamWindowSize :: Stream -> IO WindowSize
4849getStreamWindowSize Stream{streamWindow} = atomically $ readTVar streamWindow
4950
51+{-# INLINE waitStreamWindowSize #-}
5052waitStreamWindowSize :: Stream -> STM ()
5153waitStreamWindowSize Stream{streamWindow} = do
5254 w <- readTVar streamWindow
5355 check (w > 0)
5456
57+{-# INLINE waitStreaming #-}
5558waitStreaming :: TBQueue a -> STM ()
5659waitStreaming tbq = do
5760 isEmpty <- isEmptyTBQueue tbq
@@ -111,6 +114,7 @@ frameSender ctx@Context{outputQ,controlQ,connectionWindow,encodeDynamicTable}
111114 setLimit alist
112115 return off'
113116
117+ {-# INLINE setLimit #-}
114118 setLimit alist = case lookup SettingsHeaderTableSize alist of
115119 Nothing -> return ()
116120 Just siz -> setLimitForEncoding siz encodeDynamicTable
@@ -192,6 +196,7 @@ frameSender ctx@Context{outputQ,controlQ,connectionWindow,encodeDynamicTable}
192196 enqueueControl controlQ $ CFrame rst
193197 return off
194198
199+ {-# INLINE flushN #-}
195200 -- Flush the connection buffer to the socket, where the first 'n' bytes of
196201 -- the buffer are filled.
197202 flushN :: Int -> IO ()
@@ -212,8 +217,8 @@ frameSender ctx@Context{outputQ,controlQ,connectionWindow,encodeDynamicTable}
212217 fillFrameHeader FrameHeaders kvlen sid flag buf
213218 continue sid kvlen hs
214219
215− bufHeaderPayload = connWriteBuffer `plusPtr` frameHeaderLength
216− headerPayloadLim = connBufferSize - frameHeaderLength
220+ !bufHeaderPayload = connWriteBuffer `plusPtr` frameHeaderLength
221+ !headerPayloadLim = connBufferSize - frameHeaderLength
217222
218223 continue _ kvlen [] = return kvlen
219224 continue sid kvlen hs = do
@@ -227,6 +232,7 @@ frameSender ctx@Context{outputQ,controlQ,connectionWindow,encodeDynamicTable}
227232 fillFrameHeader FrameContinuation kvlen' sid flag connWriteBuffer
228233 continue sid kvlen' hs'
229234
235+ {-# INLINE maybeEnqueueNext #-}
230236 -- Re-enqueue the stream in the output queue.
231237 maybeEnqueueNext :: Stream -> Maybe (TBQueue Sequence) -> Maybe DynaNext -> IO ()
232238 maybeEnqueueNext _ _ Nothing = return ()
@@ -234,6 +240,7 @@ frameSender ctx@Context{outputQ,controlQ,connectionWindow,encodeDynamicTable}
234240 where
235241 !out = ONext strm next mtbq
236242
243+ {-# INLINE sendHeadersIfNecessary #-}
237244 -- Send headers if there is not room for a 1-byte data frame, and return
238245 -- the offset of the next frame's first header byte.
239246 sendHeadersIfNecessary off
@@ -248,19 +255,21 @@ frameSender ctx@Context{outputQ,controlQ,connectionWindow,encodeDynamicTable}
248255 let !sid = streamNumber strm
249256 !buf = connWriteBuffer `plusPtr` off
250257 !off' = off + frameHeaderLength + datPayloadLen
251− flag = case mnext of
252− Nothing -> setEndStream defaultFlags
253− _ -> defaultFlags
258+ !done = isNothing mnext
259+ flag | done = setEndStream defaultFlags
260+ | otherwise = defaultFlags
254261 fillFrameHeader FrameData datPayloadLen sid flag buf
255− when (isNothing mnext) $ closed ctx strm Finished
262+ when done $ closed ctx strm Finished
256263 atomically $ modifyTVar' connectionWindow (subtract datPayloadLen)
257264 atomically $ modifyTVar' (streamWindow strm) (subtract datPayloadLen)
258265 return off'
259266
267+ {-# INLINE fillFrameHeader #-}
260268 fillFrameHeader ftyp len sid flag buf = encodeFrameHeaderBuf ftyp hinfo buf
261269 where
262270 hinfo = FrameHeader len flag sid
263271
272+ {-# INLINE ignore #-}
264273 ignore :: E.SomeException -> IO ()
265274 ignore _ = return ()
266275
@@ -456,6 +465,7 @@ nextForFile len fd start bytes refresh =
456465 Next len $ Just (fillBufFile fd start bytes refresh)
457466#endif
458467
468+{-# INLINE mini #-}
459469mini :: Int -> Integer -> Int
460470mini i n
461471 | fromIntegral i < n = i
warp/Network/Wai/Handler/Warp/HTTP2/Types.hs+12−12
@@ -41,7 +41,7 @@ isHTTP2 tls = useHTTP2
4141
4242----------------------------------------------------------------
4343
44−data Input = Input Stream Request InternalInfo
44+data Input = Input Stream Request ValueTable InternalInfo
4545
4646----------------------------------------------------------------
4747
@@ -51,10 +51,10 @@ type BytesFilled = Int
5151
5252data Next = Next !BytesFilled (Maybe DynaNext)
5353
54−data Rspn = RspnNobody H.Status H.ResponseHeaders
55− | RspnStreaming H.Status H.ResponseHeaders (TBQueue Sequence)
56− | RspnBuilder H.Status H.ResponseHeaders Builder
57− | RspnFile H.Status H.ResponseHeaders FilePath (Maybe FilePart)
54+data Rspn = RspnNobody H.Status (TokenHeaderList, ValueTable)
55+ | RspnStreaming H.Status (TokenHeaderList, ValueTable) (TBQueue Sequence)
56+ | RspnBuilder H.Status (TokenHeaderList, ValueTable) Builder
57+ | RspnFile H.Status (TokenHeaderList, ValueTable) FilePath (Maybe FilePart)
5858
5959rspnStatus :: Rspn -> H.Status
6060rspnStatus (RspnNobody s _) = s
@@ -62,11 +62,11 @@ rspnStatus (RspnStreaming s _ _) = s
6262rspnStatus (RspnBuilder s _ _) = s
6363rspnStatus (RspnFile s _ _ _ ) = s
6464
65−rspnHeaders :: Rspn -> H.ResponseHeaders
66−rspnHeaders (RspnNobody _ h) = h
67−rspnHeaders (RspnStreaming _ h _) = h
68−rspnHeaders (RspnBuilder _ h _) = h
69−rspnHeaders (RspnFile _ h _ _ ) = h
65+rspnHeaders :: Rspn -> (TokenHeaderList, ValueTable)
66+rspnHeaders (RspnNobody _ t) = t
67+rspnHeaders (RspnStreaming _ t _) = t
68+rspnHeaders (RspnBuilder _ t _) = t
69+rspnHeaders (RspnFile _ t _ _ ) = t
7070
7171data Output = ORspn !Stream !Rspn !InternalInfo
7272 | ONext !Stream !DynaNext !(Maybe (TBQueue Sequence))
@@ -144,8 +144,8 @@ data OpenState =
144144 !Int -- The number of continuation frames
145145 !Bool -- End of stream
146146 !Priority
147− | NoBody HeaderList !Priority
148− | HasBody HeaderList !Priority
147+ | NoBody (TokenHeaderList,ValueTable) !Priority
148+ | HasBody (TokenHeaderList,ValueTable) !Priority
149149 | Body !(TQueue ByteString)
150150
151151data ClosedCode = Finished
warp/Network/Wai/Handler/Warp/HTTP2/Worker.hs+35−19
@@ -22,13 +22,13 @@ import Control.Monad (when)
2222import Data.ByteString.Builder (byteString)
2323import qualified Network.HTTP.Types as H
2424import Network.HTTP2
25+import Network.HPACK
2526import Network.Wai
26−import Network.Wai.Handler.Warp.File
2727import Network.Wai.Handler.Warp.FileInfoCache
2828import Network.Wai.Handler.Warp.HTTP2.EncodeFrame
29+import Network.Wai.Handler.Warp.HTTP2.File
2930import Network.Wai.Handler.Warp.HTTP2.Manager
3031import Network.Wai.Handler.Warp.HTTP2.Types
31−import Network.Wai.Handler.Warp.Header
3232import Network.Wai.Handler.Warp.IORef
3333import qualified Network.Wai.Handler.Warp.Response as R
3434import qualified Network.Wai.Handler.Warp.Settings as S
@@ -41,14 +41,15 @@ import Network.Wai.Internal (Response(..), ResponseReceived(..), ResponseReceive
4141-- | The wai definition is 'type Application = Request -> (Response -> IO ResponseReceived) -> IO ResponseReceived'.
4242-- This type implements the second argument (Response -> IO ResponseReceived)
4343-- with extra arguments.
44−type Responder = InternalInfo -> ThreadContinue -> Stream -> Request ->
44+type Responder = InternalInfo -> ValueTable ->
45+ ThreadContinue -> Stream -> Request ->
4546 Response -> IO ResponseReceived
4647
4748-- | This function is passed to workers.
4849-- They also pass 'Response's from 'Application's to this function.
4950-- This function enqueues commands for the HTTP/2 sender.
5051response :: S.Settings -> Context -> Manager -> Responder
51−response settings Context{outputQ} mgr ii tconf strm req rsp = case rsp of
52+response settings Context{outputQ} mgr ii reqvt tconf strm req rsp = case rsp of
5253 ResponseStream s0 hs0 strmbdy
5354 | noBody s0 -> responseNoBody s0 hs0
5455 | isHead -> responseNoBody s0 hs0
@@ -73,18 +74,21 @@ response settings Context{outputQ} mgr ii tconf strm req rsp = case rsp of
7374 -- log message are written here even the window size of streams
7475 -- is 0.
7576
76− responseNoBody s hs = do
77+ responseNoBody s hs0 = toHeaderTable hs0 >>= responseNoBody' s
78+
79+ responseNoBody' s tbl = do
7780 logger req s Nothing
7881 setThreadContinue tconf True
79− let rspn = RspnNobody s hs
82+ let rspn = RspnNobody s tbl
8083 out = ORspn strm rspn ii
8184 enqueueOutput outputQ out
8285 return ResponseReceived
8386
84− responseBuilderBody s hs bdy = do
87+ responseBuilderBody s hs0 bdy = do
8588 logger req s Nothing
8689 setThreadContinue tconf True
87− let rspn = RspnBuilder s hs bdy
90+ tbl <- toHeaderTable hs0
91+ let rspn = RspnBuilder s tbl bdy
8892 out = ORspn strm rspn ii
8993 enqueueOutput outputQ out
9094 return ResponseReceived
@@ -93,20 +97,24 @@ response settings Context{outputQ} mgr ii tconf strm req rsp = case rsp of
9397 efinfo <- E.try $ getFileInfo ii path
9498 case efinfo of
9599 Left (_ex :: E.IOException) -> response404 hs0
96− Right finfo -> case conditionalRequest finfo hs0 (indexRequestHeader (requestHeaders req)) of
97− WithoutBody s -> responseNoBody s hs0
98− WithBody s hs beg len -> responseFile2XX s hs path (Just (FilePart beg len (fileInfoSize finfo)))
100+ Right finfo -> do
101+ (rspths0,vt) <- toHeaderTable hs0
102+ case conditionalRequest finfo rspths0 reqvt of
103+ WithoutBody s -> responseNoBody s hs0
104+ WithBody s rspths beg len -> responseFile2XX s (rspths,vt) path (Just (FilePart beg len (fileInfoSize finfo)))
99105
100− responseFileXXX s0 hs0 path mpart = responseFile2XX s0 hs0 path mpart
106+ responseFileXXX s0 hs0 path mpart = do
107+ tbl <- toHeaderTable hs0
108+ responseFile2XX s0 tbl path mpart
101109
102− responseFile2XX s hs path mpart
110+ responseFile2XX s tbl path mpart
103111 | isHead = do
104112 logger req s Nothing
105− responseNoBody s hs
113+ responseNoBody' s tbl
106114 | otherwise = do
107115 logger req s (filePartByteCount <$> mpart)
108116 setThreadContinue tconf True
109− let rspn = RspnFile s hs path mpart
117+ let rspn = RspnFile s tbl path mpart
110118 out = ORspn strm rspn ii
111119 enqueueOutput outputQ out
112120 return ResponseReceived
@@ -131,7 +139,8 @@ response settings Context{outputQ} mgr ii tconf strm req rsp = case rsp of
131139 -- Since 'StreamingBody' is loop, we cannot control it.
132140 -- So, let's serialize 'Builder' with a designated queue.
133141 tbq <- newTBQueueIO 10 -- fixme: hard coding: 10
134− let rspn = RspnStreaming s0 hs0 tbq
142+ tbl <- toHeaderTable hs0
143+ let rspn = RspnStreaming s0 tbl tbq
135144 out = ORspn strm rspn ii
136145 enqueueOutput outputQ out
137146 let push b = do
@@ -153,11 +162,11 @@ worker ctx@Context{inputQ,controlQ} set app responder tm = do
153162 setThreadContinue tcont True
154163 ex <- E.try $ do
155164 T.pause th
156− inp@(Input strm req ii) <- atomically $ readTQueue inputQ
165+ inp@(Input strm req reqvt ii) <- atomically $ readTQueue inputQ
157166 setStreamInfo sinfo inp
158167 T.resume th
159168 T.tickle th
160− app req $ responder ii tcont strm req
169+ app req $ responder ii reqvt tcont strm req
161170 cont1 <- case ex of
162171 Right ResponseReceived -> return True
163172 Left e@(SomeException _)
@@ -177,7 +186,7 @@ worker ctx@Context{inputQ,controlQ} set app responder tm = do
177186 minp <- getStreamInfo sinfo
178187 case minp of
179188 Nothing -> return ()
180− Just (Input strm req _ii) -> do
189+ Just (Input strm req _reqvt _ii) -> do
181190 closed ctx strm Killed
182191 let frame = resetFrame InternalError (streamNumber strm)
183192 enqueueControl controlQ $ CFrame frame
@@ -196,12 +205,15 @@ worker ctx@Context{inputQ,controlQ} set app responder tm = do
196205-- Otherwise, the worker get finished.
197206newtype ThreadContinue = ThreadContinue (IORef Bool)
198207
208+{-# INLINE newThreadContinue #-}
199209newThreadContinue :: IO ThreadContinue
200210newThreadContinue = ThreadContinue <$> newIORef True
201211
212+{-# INLINE setThreadContinue #-}
202213setThreadContinue :: ThreadContinue -> Bool -> IO ()
203214setThreadContinue (ThreadContinue ref) x = writeIORef ref x
204215
216+{-# INLINE getThreadContinue #-}
205217getThreadContinue :: ThreadContinue -> IO Bool
206218getThreadContinue (ThreadContinue ref) = readIORef ref
207219
@@ -210,14 +222,18 @@ getThreadContinue (ThreadContinue ref) = readIORef ref
210222-- | The type to store enough information for 'settingsOnException'.
211223newtype StreamInfo = StreamInfo (IORef (Maybe Input))
212224
225+{-# INLINE newStreamInfo #-}
213226newStreamInfo :: IO StreamInfo
214227newStreamInfo = StreamInfo <$> newIORef Nothing
215228
229+{-# INLINE clearStreamInfo #-}
216230clearStreamInfo :: StreamInfo -> IO ()
217231clearStreamInfo (StreamInfo ref) = writeIORef ref Nothing
218232
233+{-# INLINE setStreamInfo #-}
219234setStreamInfo :: StreamInfo -> Input -> IO ()
220235setStreamInfo (StreamInfo ref) inp = writeIORef ref $ Just inp
221236
237+{-# INLINE getStreamInfo #-}
222238getStreamInfo :: StreamInfo -> IO (Maybe Input)
223239getStreamInfo (StreamInfo ref) = readIORef ref
warp/Network/Wai/Handler/Warp/HashMap.hs+3
@@ -6,6 +6,7 @@ import Data.IntMap.Strict (IntMap)
66import qualified Data.IntMap.Strict as I
77import Data.Map.Strict (Map)
88import qualified Data.Map.Strict as M
9+import Prelude hiding (lookup)
910
1011type Hash = Int
1112newtype HashMap k v = HashMap (IntMap (Map k v))
@@ -24,6 +25,8 @@ insert h k v (HashMap hm) = HashMap $ I.insertWith f h m hm
2425 where
2526 m = M.singleton k v
2627 f = M.union -- fimxe
28+{-# SPECIALIZE insert :: Hash -> String -> v -> HashMap String v -> HashMap String v #-}
2729
2830lookup :: Ord k => Hash -> k -> HashMap k v -> Maybe v
2931lookup h k (HashMap hm) = I.lookup h hm >>= M.lookup k
32+{-# SPECIALIZE lookup :: Hash -> String -> HashMap String v -> Maybe v #-}
warp/Network/Wai/Handler/Warp/PackInt.hsnew file+31
@@ -0,0 +1,31 @@
1+{-# LANGUAGE OverloadedStrings #-}
2+
3+module Network.Wai.Handler.Warp.PackInt where
4+
5+import Control.Monad (when)
6+import Data.ByteString.Internal (ByteString(..), unsafeCreate)
7+import Data.Word8 (Word8)
8+import Foreign.Ptr (Ptr, plusPtr)
9+import Foreign.Storable (poke)
10+
11+-- |
12+--
13+-- prop> packIntegral (abs n) == B.pack (show (abs n))
14+-- prop> \(Large n) -> let n' = fromIntegral (abs n :: Int) in packIntegral n' == B.pack (show n')
15+
16+packIntegral :: Integral a => a -> ByteString
17+packIntegral 0 = "0"
18+packIntegral n | n < 0 = error "packIntegral"
19+packIntegral n = unsafeCreate len go0
20+ where
21+ n' = fromIntegral n + 1 :: Double
22+ len = ceiling $ logBase 10 n'
23+ go0 p = go n $ p `plusPtr` (len - 1)
24+ go :: Integral a => a -> Ptr Word8 -> IO ()
25+ go i p = do
26+ let (d,r) = i `divMod` 10
27+ poke p (48 + fromIntegral r)
28+ when (d /= 0) $ go d (p `plusPtr` (-1))
29+
30+{-# SPECIALIZE packIntegral :: Int -> ByteString #-}
31+{-# SPECIALIZE packIntegral :: Integer -> ByteString #-}
warp/warp.cabal+2
@@ -72,6 +72,7 @@ Library
7272 Network.Wai.Handler.Warp.HashMap
7373 Network.Wai.Handler.Warp.HTTP2
7474 Network.Wai.Handler.Warp.HTTP2.EncodeFrame
75+ Network.Wai.Handler.Warp.HTTP2.File
7576 Network.Wai.Handler.Warp.HTTP2.HPACK
7677 Network.Wai.Handler.Warp.HTTP2.Manager
7778 Network.Wai.Handler.Warp.HTTP2.Receiver
@@ -82,6 +83,7 @@ Library
8283 Network.Wai.Handler.Warp.Header
8384 Network.Wai.Handler.Warp.IO
8485 Network.Wai.Handler.Warp.IORef
86+ Network.Wai.Handler.Warp.PackInt
8587 Network.Wai.Handler.Warp.ReadInt
8688 Network.Wai.Handler.Warp.Recv
8789 Network.Wai.Handler.Warp.Request