where
import Control.Applicative
import Control.Concurrent.STM
-import Control.Exception
+import Control.Exception hiding (block)
import Control.Monad
import qualified Data.Attoparsec.Lazy as LP
+import qualified Data.ByteString as Strict
import qualified Data.ByteString.Lazy as Lazy
import Data.Maybe
+import Data.Monoid.Unicode
import qualified Data.Sequence as S
-import Data.Sequence.Unicode
-import Data.Text (Text)
+import Data.Sequence.Unicode hiding ((∅))
import Network.HTTP.Lucu.Config
import Network.HTTP.Lucu.Chunk
-import Network.HTTP.Lucu.DefaultPage
import Network.HTTP.Lucu.HandleLike
import Network.HTTP.Lucu.Interaction
import Network.HTTP.Lucu.Postprocess
import Network.HTTP.Lucu.Preprocess
import Network.HTTP.Lucu.Request
import Network.HTTP.Lucu.Response
+import Network.HTTP.Lucu.Resource.Internal
import Network.HTTP.Lucu.Resource.Tree
import Network.Socket
import Network.URI
, cQueue ∷ !InteractionQueue
}
+data ChunkReceivingState
+ = Initial
+ | InChunk !Int -- ^Number of remaining octets in the current
+ -- chunk. It's always positive.
+
requestReader ∷ HandleLike h
⇒ Config
→ ResTree
= do input ← hGetLBS h
acceptRequest (Context cnf tree fbs h port addr tQueue) input
`catches`
- [ Handler $ \ (_ ∷ IOException) → return ()
- , Handler $ \ e → case e of
- ThreadKilled → return ()
- _ → hPutStrLn stderr (show e)
- , Handler $ \ BlockedIndefinitelyOnSTM → hPutStrLn stderr "requestReader: blocked indefinitely"
- , Handler $ \ (e ∷ SomeException) → hPutStrLn stderr (show e)
+ [ Handler handleAsyncE
+ , Handler handleBIOS
+ , Handler handleOthers
]
+ where
+ handleAsyncE ∷ AsyncException → IO ()
+ handleAsyncE ThreadKilled = return ()
+ handleAsyncE e = dump e
+
+ handleBIOS ∷ BlockedIndefinitelyOnSTM → IO ()
+ handleBIOS = dump
+
+ handleOthers ∷ SomeException → IO ()
+ handleOthers = dump
+
+ dump ∷ Exception e ⇒ e → IO ()
+ dump e
+ = do hPutStrLn stderr "requestReader caught an exception:"
+ hPutStrLn stderr (show $ toException e)
acceptRequest ∷ HandleLike h ⇒ Context h → Lazy.ByteString → IO ()
acceptRequest ctx@(Context {..}) input
= do itr ← newInteraction cConfig cPort cAddr Nothing (Left sc)
atomically $
do writeTVar (itrState itr) Done
- writeDefaultPage itr
postprocess itr
enqueue ctx itr
→ STM (IO ())
acceptSemanticallyInvalidRequest ctx itr input
= do writeTVar (itrState itr) Done
- writeDefaultPage itr
postprocess itr
enqueue ctx itr
return $ acceptRequest ctx input
= do atomically $
do setResponseStatus itr NotFound
writeTVar (itrState itr) Done
- writeDefaultPage itr
postprocess itr
enqueue ctx itr
acceptRequest ctx input
⇒ Context h
→ Interaction
→ Lazy.ByteString
- → [Text]
+ → [Strict.ByteString]
→ ResourceDef
→ IO ()
acceptRequestForExistentResource ctx oldItr input rsrcPath rsrcDef
= do let itr = oldItr { itrResourcePath = Just rsrcPath }
atomically $ enqueue ctx itr
- do _ ← runResource rsrcDef itr
- if reqHasBody $ fromJust $ itrRequest itr then
- observeRequest ctx itr input
+ do _ ← spawnResource rsrcDef itr
+ if reqMustHaveBody $ fromJust $ itrRequest itr then
+ waitForReceiveBodyReq ctx itr input
else
acceptRequest ctx input
-observeRequest ∷ HandleLike h
- ⇒ Context h
- → Interaction
- → Lazy.ByteString
- → IO ()
-observeRequest ctx itr input
- = case fromJust $ itrReqBodyLength itr of
- Chunked
- → observeChunkedRequest ctx itr input 0
- Fixed len
- → observeNonChunkedRequest ctx itr input len
-
-observeChunkedRequest ∷ HandleLike h
+waitForReceiveBodyReq ∷ HandleLike h
⇒ Context h
→ Interaction
→ Lazy.ByteString
- → Int
→ IO ()
-observeChunkedRequest ctx itr input remaining
+waitForReceiveBodyReq ctx itr input
+ = case fromJust $ itrReqBodyLength itr of
+ Chunked
+ → waitForReceiveChunkedBodyReqForTheFirstTime ctx itr input
+ Fixed len
+ → waitForReceiveNonChunkedBodyReqForTheFirstTime ctx itr input len
+
+-- Toooooo long name for a function...
+waitForReceiveChunkedBodyReqForTheFirstTime ∷ HandleLike h
+ ⇒ Context h
+ → Interaction
+ → Lazy.ByteString
+ → IO ()
+waitForReceiveChunkedBodyReqForTheFirstTime ctx itr@(Interaction {..}) input
= join $
atomically $
- do isOver ← readTVar $ itrReqChunkIsOver itr
- if isOver then
- return $ acceptRequest ctx input
- else
- do wantedM ← readTVar $ itrReqBodyWanted itr
- case wantedM of
- Nothing
- → do wasteAll ← readTVar $ itrReqBodyWasteAll itr
- if wasteAll then
- return $ wasteCurrentChunk ctx itr input remaining
- else
- retry
- Just wanted
- → return $ readCurrentChunk ctx itr input wanted remaining
+ do req ← takeTMVar itrReceiveBodyReq
+ case req of
+ ReceiveBody wanted
+ → do putTMVar itrSendContinue $ fromJust itrExpectedContinue
+ return $ readCurrentChunk ctx itr input Initial wanted
+ WasteAll
+ → do putTMVar itrSendContinue False
+ return $ wasteAllChunks ctx itr input Initial
+
+waitForReceiveChunkedBodyReq ∷ HandleLike h
+ ⇒ Context h
+ → Interaction
+ → Lazy.ByteString
+ → ChunkReceivingState
+ → IO ()
+waitForReceiveChunkedBodyReq ctx itr@(Interaction {..}) input st
+ = do req ← atomically $ takeTMVar itrReceiveBodyReq
+ case req of
+ ReceiveBody wanted
+ → readCurrentChunk ctx itr input st wanted
+ WasteAll
+ → wasteAllChunks ctx itr input st
+
+wasteAllChunks ∷ HandleLike h
+ ⇒ Context h
+ → Interaction
+ → Lazy.ByteString
+ → ChunkReceivingState
+ → IO ()
+wasteAllChunks ctx itr = go
+ where
+ go ∷ Lazy.ByteString → ChunkReceivingState → IO ()
+ go input Initial
+ = case LP.parse chunkHeaderP input of
+ LP.Done input' chunkLen
+ | chunkLen ≡ 0 → gotFinalChunk input'
+ | otherwise → gotChunk input' chunkLen
+ LP.Fail _ _ _
+ → chunkWasMalformed itr
+ go input (InChunk chunkLen)
+ = gotChunk input chunkLen
+
+ gotChunk ∷ Lazy.ByteString → Int → IO ()
+ gotChunk input chunkLen
+ = let input' = Lazy.drop (fromIntegral chunkLen) input
+ in
+ case LP.parse chunkFooterP input' of
+ LP.Done input'' _
+ → go input'' Initial
+ LP.Fail _ _ _
+ → chunkWasMalformed itr
-wasteCurrentChunk ∷ HandleLike h
- ⇒ Context h
- → Interaction
- → Lazy.ByteString
- → Int
- → IO ()
-wasteCurrentChunk ctx itr input len
- | len > 0
- = let input' = Lazy.drop (fromIntegral len) input
- in
- case LP.parse chunkFooterP input' of
- LP.Done input'' _
- → observeChunkedRequest ctx itr input'' 0
+ gotFinalChunk ∷ Lazy.ByteString → IO ()
+ gotFinalChunk input
+ = case LP.parse chunkFooterP input of
+ LP.Done input' _
+ → case LP.parse chunkTrailerP input' of
+ LP.Done input'' _
+ → acceptRequest ctx input''
+ LP.Fail _ _ _
+ → chunkWasMalformed itr
LP.Fail _ _ _
→ chunkWasMalformed itr
- | otherwise
- = seekNextChunk ctx itr input
readCurrentChunk ∷ HandleLike h
⇒ Context h
→ Interaction
→ Lazy.ByteString
- → Int
+ → ChunkReceivingState
→ Int
→ IO ()
-readCurrentChunk ctx itr input wanted remaining
- | remaining > 0
- = do let bytesToRead = fromIntegral $ min wanted remaining
- (chunk, input') = Lazy.splitAt bytesToRead input
- actualReadBytes = fromIntegral $ Lazy.length chunk
- newWanted = case wanted - actualReadBytes of
- 0 → Nothing
- n → Just n
- newRemaining = remaining - actualReadBytes
- chunk' = S.fromList $ Lazy.toChunks chunk
- updateStates = atomically $
- do writeTVar (itrReqBodyWanted itr) newWanted
- oldBody ← readTVar $ itrReceivedBody itr
- oldBodyLen ← readTVar $ itrReceivedBodyLen itr
- writeTVar (itrReceivedBody itr) $ oldBody ⋈ chunk'
- writeTVar (itrReceivedBodyLen itr) $ oldBodyLen + actualReadBytes
- if newRemaining ≡ 0 then
- case LP.parse chunkFooterP input' of
- LP.Done input'' _
- → do updateStates
- observeChunkedRequest ctx itr input'' 0
- LP.Fail _ _ _
- → chunkWasMalformed itr
- else
- do updateStates
- observeChunkedRequest ctx itr input' newRemaining
- | otherwise
- = seekNextChunk ctx itr input
+readCurrentChunk ctx itr@(Interaction {..}) input0 st0 wanted
+ = go input0 st0
+ where
+ go ∷ Lazy.ByteString → ChunkReceivingState → IO ()
+ go input Initial
+ = case LP.parse chunkHeaderP input of
+ LP.Done input' chunkLen
+ | chunkLen ≡ 0
+ → gotFinalChunk input'
+ | otherwise
+ → gotChunk input' chunkLen
+ LP.Fail _ _ _
+ → chunkWasMalformed itr
+ go input (InChunk chunkLen)
+ = gotChunk input chunkLen
-seekNextChunk ∷ HandleLike h
- ⇒ Context h
- → Interaction
- → Lazy.ByteString
- → IO ()
-seekNextChunk ctx itr input
- = case LP.parse chunkHeaderP input of
- LP.Done input' len
- | len ≡ 0 -- Final chunk
- → case LP.parse chunkTrailerP input' of
+ gotChunk ∷ Lazy.ByteString → Int → IO ()
+ gotChunk input chunkLen
+ = do let bytesToRead = min wanted chunkLen
+ (block, input') = Lazy.splitAt (fromIntegral bytesToRead) input
+ block' = Strict.concat $ Lazy.toChunks block
+ actualReadBytes = Strict.length block'
+ chunkLen' = chunkLen - actualReadBytes
+ atomically $ putTMVar itrReceivedBody block'
+ if chunkLen' ≡ 0 then
+ case LP.parse chunkFooterP input' of
LP.Done input'' _
- → do atomically $
- writeTVar (itrReqChunkIsOver itr) True
- acceptRequest ctx input''
+ → waitForReceiveChunkedBodyReq ctx itr input'' Initial
LP.Fail _ _ _
→ chunkWasMalformed itr
- | otherwise -- Non-final chunk
- → observeChunkedRequest ctx itr input' len
- LP.Fail _ _ _
- → chunkWasMalformed itr
+ else
+ waitForReceiveChunkedBodyReq ctx itr input' $ InChunk chunkLen'
+
+ gotFinalChunk ∷ Lazy.ByteString → IO ()
+ gotFinalChunk input
+ = do atomically $ putTMVar itrReceivedBody (∅)
+ case LP.parse chunkFooterP input of
+ LP.Done input' _
+ → case LP.parse chunkTrailerP input' of
+ LP.Done input'' _
+ → acceptRequest ctx input''
+ LP.Fail _ _ _
+ → chunkWasMalformed itr
+ LP.Fail _ _ _
+ → chunkWasMalformed itr
chunkWasMalformed ∷ Interaction → IO ()
chunkWasMalformed itr
+ -- FIXME: This is a totally wrong way to abort!
= atomically $
do setResponseStatus itr BadRequest
writeTVar (itrWillClose itr) True
writeTVar (itrState itr) Done
- writeDefaultPage itr
postprocess itr
-observeNonChunkedRequest ∷ HandleLike h
- ⇒ Context h
- → Interaction
- → Lazy.ByteString
- → Int
- → IO ()
-observeNonChunkedRequest ctx itr input remaining
+waitForReceiveNonChunkedBodyReqForTheFirstTime ∷ HandleLike h
+ ⇒ Context h
+ → Interaction
+ → Lazy.ByteString
+ → Int
+ → IO ()
+waitForReceiveNonChunkedBodyReqForTheFirstTime ctx itr@(Interaction {..}) input bodyLen
= join $
atomically $
- do wantedM ← readTVar $ itrReqBodyWanted itr
- case wantedM of
- Nothing
- → do wasteAll ← readTVar $ itrReqBodyWasteAll itr
- if wasteAll then
- return $ wasteNonChunkedRequestBody ctx itr input remaining
- else
- retry
- Just wanted
- → return $ readNonChunkedRequestBody ctx itr input wanted remaining
+ do req ← takeTMVar itrReceiveBodyReq
+ case req of
+ ReceiveBody wanted
+ → do putTMVar itrSendContinue $ fromJust itrExpectedContinue
+ return $ readNonChunkedRequestBody ctx itr input bodyLen wanted
+ WasteAll
+ → do putTMVar itrSendContinue False
+ return $ wasteNonChunkedRequestBody ctx input bodyLen
+
+waitForReceiveNonChunkedBodyReq ∷ HandleLike h
+ ⇒ Context h
+ → Interaction
+ → Lazy.ByteString
+ → Int
+ → IO ()
+waitForReceiveNonChunkedBodyReq ctx itr@(Interaction {..}) input bodyLen
+ = do req ← atomically $ takeTMVar itrReceiveBodyReq
+ case req of
+ ReceiveBody wanted
+ → readNonChunkedRequestBody ctx itr input bodyLen wanted
+ WasteAll
+ → wasteNonChunkedRequestBody ctx input bodyLen
wasteNonChunkedRequestBody ∷ HandleLike h
⇒ Context h
- → Interaction
→ Lazy.ByteString
→ Int
→ IO ()
-wasteNonChunkedRequestBody ctx itr input remaining
- = do let input' = Lazy.drop (fromIntegral remaining) input
- atomically $ writeTVar (itrReqChunkIsOver itr) True
+wasteNonChunkedRequestBody ctx input bodyLen
+ = do let input' = Lazy.drop (fromIntegral bodyLen) input
acceptRequest ctx input'
readNonChunkedRequestBody ∷ HandleLike h
→ Int
→ Int
→ IO ()
-readNonChunkedRequestBody ctx itr input wanted remaining
- = do let bytesToRead = min wanted remaining
- (chunk, input') = Lazy.splitAt (fromIntegral bytesToRead) input
- actualReadBytes = fromIntegral $ Lazy.length chunk
- newRemaining = remaining - actualReadBytes
- isOver = actualReadBytes < bytesToRead ∨ newRemaining ≡ 0
- chunk' = S.fromList $ Lazy.toChunks chunk
- atomically $
- do writeTVar (itrReqChunkIsOver itr) isOver
- writeTVar (itrReqBodyWanted itr) Nothing
- writeTVar (itrReceivedBody itr) chunk'
- writeTVar (itrReceivedBodyLen itr) actualReadBytes
- if isOver then
- acceptRequest ctx input'
- else
- observeNonChunkedRequest ctx itr input' newRemaining
+readNonChunkedRequestBody ctx itr@(Interaction {..}) input bodyLen wanted
+ | bodyLen ≡ 0 = gotEndOfRequest
+ | otherwise = gotBody
+ where
+ gotBody ∷ IO ()
+ gotBody
+ = do let bytesToRead = min wanted bodyLen
+ (block, input') = Lazy.splitAt (fromIntegral bytesToRead) input
+ block' = Strict.concat $ Lazy.toChunks block
+ actualReadBytes = Strict.length block'
+ bodyLen' = bodyLen - actualReadBytes
+ atomically $ putTMVar itrReceivedBody block'
+ waitForReceiveNonChunkedBodyReq ctx itr input' bodyLen'
+
+ gotEndOfRequest ∷ IO ()
+ gotEndOfRequest
+ = do atomically $ putTMVar itrReceivedBody (∅)
+ acceptRequest ctx input
enqueue ∷ HandleLike h ⇒ Context h → Interaction → STM ()
enqueue (Context {..}) itr