module Network.HTTP.Lucu.RequestReader
( requestReader
)
where
import Control.Concurrent.STM
import Control.Exception
import Control.Monad
import qualified Data.ByteString.Lazy.Char8 as B
import Data.ByteString.Lazy.Char8 (ByteString)
import Data.Maybe
import qualified Data.Sequence as S
import Data.Sequence ((<|))
import GHC.Conc (unsafeIOToSTM)
import Network.Socket
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.Parser
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.Tree
import Prelude hiding (catch)
import System.IO (stderr)
requestReader :: HandleLike h => Config -> ResTree -> [FallbackHandler] -> h -> SockAddr -> InteractionQueue -> IO ()
requestReader !cnf !tree !fbs !h !addr !tQueue
= do input <- hGetLBS h
acceptRequest input
`catches`
[ Handler (( \ _ -> return () ) :: IOException -> IO ())
, Handler ( \ ThreadKilled -> return () )
, Handler ( \ BlockedIndefinitely -> hPutStrLn stderr "requestReader: blocked indefinitely" )
, Handler (( \ e -> hPutStrLn stderr (show e) ) :: SomeException -> IO ())
]
where
acceptRequest :: ByteString -> IO ()
acceptRequest input
=
do atomically $ do queue <- readTVar tQueue
when (S.length queue >= cnfMaxPipelineDepth cnf)
retry
case parse requestP input of
(# Success req , input' #) -> acceptParsableRequest req input'
(# IllegalInput, _ #) -> acceptNonparsableRequest BadRequest
(# ReachedEOF , _ #) -> acceptNonparsableRequest BadRequest
acceptNonparsableRequest :: StatusCode -> IO ()
acceptNonparsableRequest status
=
do itr <- newInteraction cnf addr Nothing Nothing
atomically $ do updateItr itr itrResponse
$ \ res -> res {
resStatus = status
}
writeItr itr itrWillClose True
writeItr itr itrState Done
writeDefaultPage itr
postprocess itr
enqueue itr
acceptParsableRequest :: Request -> ByteString -> IO ()
acceptParsableRequest req input
=
do cert <- hGetPeerCert h
itr <- newInteraction cnf addr cert (Just req)
action
<- atomically $
do preprocess itr
isErr <- readItr itr itrResponse (isError . resStatus)
if isErr then
acceptSemanticallyInvalidRequest itr input
else
do rsrcM <- unsafeIOToSTM $ findResource tree fbs $ reqURI req
case rsrcM of
Nothing
-> acceptRequestForNonexistentResource itr input
Just (rsrcPath, rsrcDef)
-> acceptRequestForExistentResource itr input rsrcPath rsrcDef
action
acceptSemanticallyInvalidRequest :: Interaction -> ByteString -> STM (IO ())
acceptSemanticallyInvalidRequest itr input
=
do writeItr itr itrState Done
writeDefaultPage itr
postprocess itr
enqueue itr
return $ acceptRequest input
acceptRequestForNonexistentResource :: Interaction -> ByteString -> STM (IO ())
acceptRequestForNonexistentResource itr input
=
do updateItr itr itrResponse
$ \res -> res {
resStatus = NotFound
}
writeItr itr itrState Done
writeDefaultPage itr
postprocess itr
enqueue itr
return $ acceptRequest input
acceptRequestForExistentResource :: Interaction -> ByteString -> [String] -> ResourceDef -> STM (IO ())
acceptRequestForExistentResource oldItr input rsrcPath rsrcDef
=
do let itr = oldItr { itrResourcePath = Just rsrcPath }
requestHasBody <- readItr itr itrRequestHasBody id
enqueue itr
return $ do runResource rsrcDef itr
if requestHasBody then
observeRequest itr input
else
acceptRequest input
observeRequest :: Interaction -> ByteString -> IO ()
observeRequest itr input
=
do isChunked <- atomically $ readItr itr itrRequestIsChunked id
if isChunked then
observeChunkedRequest itr input
else
observeNonChunkedRequest itr input
observeChunkedRequest :: Interaction -> ByteString -> IO ()
observeChunkedRequest itr input
=
do action
<- atomically $
do isOver <- readItr itr itrReqChunkIsOver id
if isOver then
return $ acceptRequest input
else
do wantedM <- readItr itr itrReqBodyWanted id
if wantedM == Nothing then
do wasteAll <- readItr itr itrReqBodyWasteAll id
if wasteAll then
do remainingM <- readItr itr itrReqChunkRemaining id
if fmap (> 0) remainingM == Just True then
do let (_, input') = B.splitAt (fromIntegral
$ fromJust remainingM) input
(# footerR, input'' #) = parse chunkFooterP input'
if footerR == Success () then
do writeItr itr itrReqChunkRemaining $ Just 0
return $ observeChunkedRequest itr input''
else
return $ chunkWasMalformed itr
else
seekNextChunk itr input
else
retry
else
do remainingM <- readItr itr itrReqChunkRemaining id
if fmap (> 0) remainingM == Just True then
do let wanted = fromJust wantedM
remaining = fromJust remainingM
bytesToRead = fromIntegral $ min wanted remaining
(chunk, input') = B.splitAt bytesToRead input
actualReadBytes = fromIntegral $ B.length chunk
newWanted = case wanted actualReadBytes of
0 -> Nothing
n -> Just n
newRemaining = Just $ remaining actualReadBytes
updateStates
= do writeItr itr itrReqChunkRemaining newRemaining
writeItr itr itrReqBodyWanted newWanted
updateItr itr itrReceivedBody $ flip B.append chunk
if newRemaining == Just 0 then
case parse chunkFooterP input' of
(# Success _, input'' #)
-> do updateStates
return $ observeChunkedRequest itr input''
(# _, _ #)
-> return $ chunkWasMalformed itr
else
do updateStates
return $ observeChunkedRequest itr input'
else
seekNextChunk itr input
action
seekNextChunk :: Interaction -> ByteString -> STM (IO ())
seekNextChunk itr input
=
case parse chunkHeaderP input of
(# Success 0, input' #)
-> case parse chunkTrailerP input' of
(# Success _, input'' #)
-> do writeItr itr itrReqChunkLength $ Nothing
writeItr itr itrReqChunkRemaining $ Nothing
writeItr itr itrReqChunkIsOver True
return $ acceptRequest input''
(# _, _ #)
-> return $ chunkWasMalformed itr
(# Success len, input' #)
-> do writeItr itr itrReqChunkLength $ Just len
writeItr itr itrReqChunkRemaining $ Just len
return $ observeChunkedRequest itr input'
(# _, _ #)
-> return $ chunkWasMalformed itr
chunkWasMalformed :: Interaction -> IO ()
chunkWasMalformed itr
=
atomically $ do updateItr itr itrResponse
$ \ res -> res {
resStatus = BadRequest
}
writeItr itr itrWillClose True
writeItr itr itrState Done
writeDefaultPage itr
postprocess itr
observeNonChunkedRequest :: Interaction -> ByteString -> IO ()
observeNonChunkedRequest itr input
=
do action
<- atomically $
do wantedM <- readItr itr itrReqBodyWanted id
if wantedM == Nothing then
do wasteAll <- readItr itr itrReqBodyWasteAll id
if wasteAll then
do remainingM <- readItr itr itrReqChunkRemaining id
let (_, input') = if remainingM == Nothing then
(B.takeWhile (\ _ -> True) input, B.empty)
else
B.splitAt (fromIntegral $ fromJust remainingM) input
writeItr itr itrReqChunkRemaining $ Just 0
writeItr itr itrReqChunkIsOver True
return $ acceptRequest input'
else
retry
else
do remainingM <- readItr itr itrReqChunkRemaining id
let wanted = fromJust wantedM
bytesToRead = fromIntegral $ maybe wanted (min wanted) remainingM
(chunk, input') = B.splitAt bytesToRead input
newRemaining = fmap
(\ x -> x (fromIntegral $ B.length chunk))
remainingM
isOver = B.length chunk < bytesToRead || newRemaining == Just 0
writeItr itr itrReqChunkRemaining newRemaining
writeItr itr itrReqChunkIsOver isOver
writeItr itr itrReqBodyWanted Nothing
writeItr itr itrReceivedBody chunk
if isOver then
return $ acceptRequest input'
else
return $ observeNonChunkedRequest itr input'
action
enqueue :: Interaction -> STM ()
enqueue itr =
do queue <- readTVar tQueue
writeTVar tQueue (itr <| queue)