7 module Network.HTTP.Lucu.RequestReader
11 import Control.Applicative
12 import Control.Concurrent.STM
13 import Control.Exception
15 import qualified Data.Attoparsec.Lazy as LP
16 import qualified Data.ByteString.Lazy as Lazy
18 import qualified Data.Sequence as S
19 import Data.Sequence.Unicode
20 import Data.Text (Text)
21 import Network.HTTP.Lucu.Config
22 import Network.HTTP.Lucu.Chunk
23 import Network.HTTP.Lucu.HandleLike
24 import Network.HTTP.Lucu.Interaction
25 import Network.HTTP.Lucu.Postprocess
26 import Network.HTTP.Lucu.Preprocess
27 import Network.HTTP.Lucu.Request
28 import Network.HTTP.Lucu.Response
29 import Network.HTTP.Lucu.Resource.Tree
32 import Prelude.Unicode
33 import System.IO (hPutStrLn, stderr)
39 , cFallbacks ∷ ![FallbackHandler]
43 , cQueue ∷ !InteractionQueue
46 requestReader ∷ HandleLike h
55 requestReader cnf tree fbs h port addr tQueue
56 = do input ← hGetLBS h
57 acceptRequest (Context cnf tree fbs h port addr tQueue) input
59 [ Handler $ \ (_ ∷ IOException) → return ()
60 , Handler $ \ e → case e of
61 ThreadKilled → return ()
62 _ → hPutStrLn stderr (show e)
63 , Handler $ \ BlockedIndefinitelyOnSTM → hPutStrLn stderr "requestReader: blocked indefinitely"
64 , Handler $ \ (e ∷ SomeException) → hPutStrLn stderr (show e)
67 acceptRequest ∷ HandleLike h ⇒ Context h → Lazy.ByteString → IO ()
68 acceptRequest ctx@(Context {..}) input
69 -- キューに最大パイプライン深度以上のリクエストが溜まってゐる時は、
72 do queue ← readTVar cQueue
73 when (S.length queue ≥ cnfMaxPipelineDepth cConfig) $
75 -- リクエストを讀む。パースできない場合は直ちに 400 Bad
76 -- Request 應答を設定し、それを出力してから切斷するやうに
77 -- ResponseWriter に通知する。
78 case LP.parse requestP input of
79 LP.Done input' req → acceptParsableRequest ctx req input'
80 LP.Fail _ _ _ → acceptNonparsableRequest ctx BadRequest
82 acceptNonparsableRequest ∷ HandleLike h ⇒ Context h → StatusCode → IO ()
83 acceptNonparsableRequest ctx@(Context {..}) sc
84 = do itr ← newInteraction cConfig cPort cAddr Nothing (Left sc)
86 do writeTVar (itrState itr) Done
90 acceptParsableRequest ∷ HandleLike h
95 acceptParsableRequest ctx@(Context {..}) req input
96 = do cert ← hGetPeerCert cHandle
97 itr ← newInteraction cConfig cPort cAddr cert (Right req)
99 $ do isErr ← (isError ∘ resStatus) <$> readTVar (itrResponse itr)
101 acceptSemanticallyInvalidRequest ctx itr input
103 return $ acceptSemanticallyValidRequest ctx itr (reqURI req) input
105 acceptSemanticallyInvalidRequest ∷ HandleLike h
110 acceptSemanticallyInvalidRequest ctx itr input
111 = do writeTVar (itrState itr) Done
114 return $ acceptRequest ctx input
116 acceptSemanticallyValidRequest ∷ HandleLike h
122 acceptSemanticallyValidRequest ctx@(Context {..}) itr uri input
123 = do rsrcM ← findResource cResTree cFallbacks uri
126 → acceptRequestForNonexistentResource ctx itr input
127 Just (rsrcPath, rsrcDef)
128 → acceptRequestForExistentResource ctx itr input rsrcPath rsrcDef
130 acceptRequestForNonexistentResource ∷ HandleLike h
135 acceptRequestForNonexistentResource ctx itr input
137 do setResponseStatus itr NotFound
138 writeTVar (itrState itr) Done
141 acceptRequest ctx input
143 acceptRequestForExistentResource ∷ HandleLike h
150 acceptRequestForExistentResource ctx oldItr input rsrcPath rsrcDef
151 = do let itr = oldItr { itrResourcePath = Just rsrcPath }
152 atomically $ enqueue ctx itr
153 do _ ← runResource rsrcDef itr
154 if reqMustHaveBody $ fromJust $ itrRequest itr then
155 observeRequest ctx itr input
157 acceptRequest ctx input
159 observeRequest ∷ HandleLike h
164 observeRequest ctx itr input
165 = case fromJust $ itrReqBodyLength itr of
167 → observeChunkedRequest ctx itr input 0
169 → observeNonChunkedRequest ctx itr input len
171 observeChunkedRequest ∷ HandleLike h
177 observeChunkedRequest ctx itr input remaining
180 do isOver ← readTVar $ itrReqChunkIsOver itr
182 return $ acceptRequest ctx input
184 do wanted ← readTVar $ itrReqBodyWanted itr
186 0 → do wasteAll ← readTVar $ itrReqBodyWasteAll itr
188 return $ wasteCurrentChunk ctx itr input remaining
191 _ → return $ readCurrentChunk ctx itr input wanted remaining
193 wasteCurrentChunk ∷ HandleLike h
199 wasteCurrentChunk ctx itr input len
201 = let input' = Lazy.drop (fromIntegral len) input
203 case LP.parse chunkFooterP input' of
205 → observeChunkedRequest ctx itr input'' 0
207 → chunkWasMalformed itr
209 = seekNextChunk ctx itr input
211 readCurrentChunk ∷ HandleLike h
218 readCurrentChunk ctx itr input wanted remaining
220 = do let bytesToRead = fromIntegral $ min wanted remaining
221 (chunk, input') = Lazy.splitAt bytesToRead input
222 actualReadBytes = fromIntegral $ Lazy.length chunk
223 newWanted = wanted - actualReadBytes
224 newRemaining = remaining - actualReadBytes
225 chunk' = S.fromList $ Lazy.toChunks chunk
226 updateStates = atomically $
227 do writeTVar (itrReqBodyWanted itr) newWanted
228 oldBody ← readTVar $ itrReceivedBody itr
229 oldBodyLen ← readTVar $ itrReceivedBodyLen itr
230 writeTVar (itrReceivedBody itr) $ oldBody ⋈ chunk'
231 writeTVar (itrReceivedBodyLen itr) $ oldBodyLen + actualReadBytes
232 if newRemaining ≡ 0 then
233 case LP.parse chunkFooterP input' of
236 observeChunkedRequest ctx itr input'' 0
238 → chunkWasMalformed itr
241 observeChunkedRequest ctx itr input' newRemaining
243 = seekNextChunk ctx itr input
245 seekNextChunk ∷ HandleLike h
250 seekNextChunk ctx itr input
251 = case LP.parse chunkHeaderP input of
253 | len ≡ 0 -- Final chunk
254 → case LP.parse chunkTrailerP input' of
257 writeTVar (itrReqChunkIsOver itr) True
258 acceptRequest ctx input''
260 → chunkWasMalformed itr
261 | otherwise -- Non-final chunk
262 → observeChunkedRequest ctx itr input' len
264 → chunkWasMalformed itr
266 chunkWasMalformed ∷ Interaction → IO ()
267 chunkWasMalformed itr
269 do setResponseStatus itr BadRequest
270 writeTVar (itrWillClose itr) True
271 writeTVar (itrState itr) Done
274 observeNonChunkedRequest ∷ HandleLike h
280 observeNonChunkedRequest ctx itr input remaining
283 do wanted ← readTVar $ itrReqBodyWanted itr
285 0 → do wasteAll ← readTVar $ itrReqBodyWasteAll itr
287 return $ wasteNonChunkedRequestBody ctx itr input remaining
290 _ → return $ readNonChunkedRequestBody ctx itr input wanted remaining
292 wasteNonChunkedRequestBody ∷ HandleLike h
298 wasteNonChunkedRequestBody ctx itr input remaining
299 = do let input' = Lazy.drop (fromIntegral remaining) input
300 atomically $ writeTVar (itrReqChunkIsOver itr) True
301 acceptRequest ctx input'
303 readNonChunkedRequestBody ∷ HandleLike h
310 readNonChunkedRequestBody ctx itr input wanted remaining
311 = do let bytesToRead = min wanted remaining
312 (chunk, input') = Lazy.splitAt (fromIntegral bytesToRead) input
313 actualReadBytes = fromIntegral $ Lazy.length chunk
314 newWanted = wanted - actualReadBytes
315 newRemaining = remaining - actualReadBytes
316 isOver = actualReadBytes < bytesToRead ∨ newRemaining ≡ 0
317 chunk' = S.fromList $ Lazy.toChunks chunk
319 do writeTVar (itrReqChunkIsOver itr) isOver
320 writeTVar (itrReqBodyWanted itr) newWanted
321 writeTVar (itrReceivedBody itr) chunk'
322 writeTVar (itrReceivedBodyLen itr) actualReadBytes
324 acceptRequest ctx input'
326 observeNonChunkedRequest ctx itr input' newRemaining
328 enqueue ∷ HandleLike h ⇒ Context h → Interaction → STM ()
329 enqueue (Context {..}) itr
330 = do queue ← readTVar cQueue
331 writeTVar cQueue (itr ⊲ queue)