7 module Network.HTTP.Lucu.RequestReader
11 import Control.Applicative
12 import Control.Concurrent.STM
13 import Control.Exception hiding (block)
15 import qualified Data.Attoparsec.Lazy as LP
16 import qualified Data.ByteString as Strict
17 import qualified Data.ByteString.Lazy as Lazy
19 import Data.Monoid.Unicode
20 import qualified Data.Sequence as S
21 import Data.Sequence.Unicode hiding ((∅))
22 import Network.HTTP.Lucu.Config
23 import Network.HTTP.Lucu.Chunk
24 import Network.HTTP.Lucu.HandleLike
25 import Network.HTTP.Lucu.Interaction
26 import Network.HTTP.Lucu.Postprocess
27 import Network.HTTP.Lucu.Preprocess
28 import Network.HTTP.Lucu.Request
29 import Network.HTTP.Lucu.Response
30 import Network.HTTP.Lucu.Resource.Internal
31 import Network.HTTP.Lucu.Resource.Tree
34 import Prelude.Unicode
35 import System.IO (hPutStrLn, stderr)
41 , cFallbacks ∷ ![FallbackHandler]
45 , cQueue ∷ !InteractionQueue
48 data ChunkReceivingState
50 | InChunk !Int -- ^Number of remaining octets in the current
51 -- chunk. It's always positive.
53 requestReader ∷ HandleLike h
62 requestReader cnf tree fbs h port addr tQueue
63 = do input ← hGetLBS h
64 acceptRequest (Context cnf tree fbs h port addr tQueue) input
66 [ Handler handleAsyncE
68 , Handler handleOthers
71 handleAsyncE ∷ AsyncException → IO ()
72 handleAsyncE ThreadKilled = return ()
73 handleAsyncE e = dump e
75 handleBIOS ∷ BlockedIndefinitelyOnSTM → IO ()
78 handleOthers ∷ SomeException → IO ()
81 dump ∷ Exception e ⇒ e → IO ()
83 = do hPutStrLn stderr "requestReader caught an exception:"
84 hPutStrLn stderr (show $ toException e)
86 acceptRequest ∷ HandleLike h ⇒ Context h → Lazy.ByteString → IO ()
87 acceptRequest ctx@(Context {..}) input
88 -- キューに最大パイプライン深度以上のリクエストが溜まってゐる時は、
91 do queue ← readTVar cQueue
92 when (S.length queue ≥ cnfMaxPipelineDepth cConfig) $
94 -- リクエストを讀む。パースできない場合は直ちに 400 Bad
95 -- Request 應答を設定し、それを出力してから切斷するやうに
96 -- ResponseWriter に通知する。
97 case LP.parse requestP input of
98 LP.Done input' req → acceptParsableRequest ctx req input'
99 LP.Fail _ _ _ → acceptNonparsableRequest ctx BadRequest
101 acceptNonparsableRequest ∷ HandleLike h ⇒ Context h → StatusCode → IO ()
102 acceptNonparsableRequest ctx@(Context {..}) sc
103 = do itr ← newInteraction cConfig cPort cAddr Nothing (Left sc)
105 do writeTVar (itrState itr) Done
109 acceptParsableRequest ∷ HandleLike h
114 acceptParsableRequest ctx@(Context {..}) req input
115 = do cert ← hGetPeerCert cHandle
116 itr ← newInteraction cConfig cPort cAddr cert (Right req)
118 $ do isErr ← (isError ∘ resStatus) <$> readTVar (itrResponse itr)
120 acceptSemanticallyInvalidRequest ctx itr input
122 return $ acceptSemanticallyValidRequest ctx itr (reqURI req) input
124 acceptSemanticallyInvalidRequest ∷ HandleLike h
129 acceptSemanticallyInvalidRequest ctx itr input
130 = do writeTVar (itrState itr) Done
133 return $ acceptRequest ctx input
135 acceptSemanticallyValidRequest ∷ HandleLike h
141 acceptSemanticallyValidRequest ctx@(Context {..}) itr uri input
142 = do rsrcM ← findResource cResTree cFallbacks uri
145 → acceptRequestForNonexistentResource ctx itr input
146 Just (rsrcPath, rsrcDef)
147 → acceptRequestForExistentResource ctx itr input rsrcPath rsrcDef
149 acceptRequestForNonexistentResource ∷ HandleLike h
154 acceptRequestForNonexistentResource ctx itr input
156 do setResponseStatus itr NotFound
157 writeTVar (itrState itr) Done
160 acceptRequest ctx input
162 acceptRequestForExistentResource ∷ HandleLike h
166 → [Strict.ByteString]
169 acceptRequestForExistentResource ctx oldItr input rsrcPath rsrcDef
170 = do let itr = oldItr { itrResourcePath = Just rsrcPath }
171 atomically $ enqueue ctx itr
172 do _ ← spawnResource rsrcDef itr
173 if reqMustHaveBody $ fromJust $ itrRequest itr then
174 waitForReceiveBodyReq ctx itr input
176 acceptRequest ctx input
178 waitForReceiveBodyReq ∷ HandleLike h
183 waitForReceiveBodyReq ctx itr input
184 = case fromJust $ itrReqBodyLength itr of
186 → waitForReceiveChunkedBodyReqForTheFirstTime ctx itr input
188 → waitForReceiveNonChunkedBodyReqForTheFirstTime ctx itr input len
190 -- Toooooo long name for a function...
191 waitForReceiveChunkedBodyReqForTheFirstTime ∷ HandleLike h
196 waitForReceiveChunkedBodyReqForTheFirstTime ctx itr@(Interaction {..}) input
199 do req ← takeTMVar itrReceiveBodyReq
202 → do putTMVar itrSendContinue $ fromJust itrExpectedContinue
203 return $ readCurrentChunk ctx itr input Initial wanted
205 → do putTMVar itrSendContinue False
206 return $ wasteAllChunks ctx itr input Initial
208 waitForReceiveChunkedBodyReq ∷ HandleLike h
212 → ChunkReceivingState
214 waitForReceiveChunkedBodyReq ctx itr@(Interaction {..}) input st
215 = do req ← atomically $ takeTMVar itrReceiveBodyReq
218 → readCurrentChunk ctx itr input st wanted
220 → wasteAllChunks ctx itr input st
222 wasteAllChunks ∷ HandleLike h
226 → ChunkReceivingState
228 wasteAllChunks ctx itr = go
230 go ∷ Lazy.ByteString → ChunkReceivingState → IO ()
232 = case LP.parse chunkHeaderP input of
233 LP.Done input' chunkLen
234 | chunkLen ≡ 0 → gotFinalChunk input'
235 | otherwise → gotChunk input' chunkLen
237 → chunkWasMalformed itr
238 go input (InChunk chunkLen)
239 = gotChunk input chunkLen
241 gotChunk ∷ Lazy.ByteString → Int → IO ()
242 gotChunk input chunkLen
243 = let input' = Lazy.drop (fromIntegral chunkLen) input
245 case LP.parse chunkFooterP input' of
249 → chunkWasMalformed itr
251 gotFinalChunk ∷ Lazy.ByteString → IO ()
253 = case LP.parse chunkFooterP input of
255 → case LP.parse chunkTrailerP input' of
257 → acceptRequest ctx input''
259 → chunkWasMalformed itr
261 → chunkWasMalformed itr
263 readCurrentChunk ∷ HandleLike h
267 → ChunkReceivingState
270 readCurrentChunk ctx itr@(Interaction {..}) input0 st0 wanted
273 go ∷ Lazy.ByteString → ChunkReceivingState → IO ()
275 = case LP.parse chunkHeaderP input of
276 LP.Done input' chunkLen
278 → gotFinalChunk input'
280 → gotChunk input' chunkLen
282 → chunkWasMalformed itr
283 go input (InChunk chunkLen)
284 = gotChunk input chunkLen
286 gotChunk ∷ Lazy.ByteString → Int → IO ()
287 gotChunk input chunkLen
288 = do let bytesToRead = min wanted chunkLen
289 (block, input') = Lazy.splitAt (fromIntegral bytesToRead) input
290 block' = Strict.concat $ Lazy.toChunks block
291 actualReadBytes = Strict.length block'
292 chunkLen' = chunkLen - actualReadBytes
293 atomically $ putTMVar itrReceivedBody block'
294 if chunkLen' ≡ 0 then
295 case LP.parse chunkFooterP input' of
297 → waitForReceiveChunkedBodyReq ctx itr input'' Initial
299 → chunkWasMalformed itr
301 waitForReceiveChunkedBodyReq ctx itr input' $ InChunk chunkLen'
303 gotFinalChunk ∷ Lazy.ByteString → IO ()
305 = do atomically $ putTMVar itrReceivedBody (∅)
306 case LP.parse chunkFooterP input of
308 → case LP.parse chunkTrailerP input' of
310 → acceptRequest ctx input''
312 → chunkWasMalformed itr
314 → chunkWasMalformed itr
316 chunkWasMalformed ∷ Interaction → IO ()
317 chunkWasMalformed itr
318 -- FIXME: This is a totally wrong way to abort!
320 do setResponseStatus itr BadRequest
321 writeTVar (itrWillClose itr) True
322 writeTVar (itrState itr) Done
325 waitForReceiveNonChunkedBodyReqForTheFirstTime ∷ HandleLike h
331 waitForReceiveNonChunkedBodyReqForTheFirstTime ctx itr@(Interaction {..}) input bodyLen
334 do req ← takeTMVar itrReceiveBodyReq
337 → do putTMVar itrSendContinue $ fromJust itrExpectedContinue
338 return $ readNonChunkedRequestBody ctx itr input bodyLen wanted
340 → do putTMVar itrSendContinue False
341 return $ wasteNonChunkedRequestBody ctx input bodyLen
343 waitForReceiveNonChunkedBodyReq ∷ HandleLike h
349 waitForReceiveNonChunkedBodyReq ctx itr@(Interaction {..}) input bodyLen
350 = do req ← atomically $ takeTMVar itrReceiveBodyReq
353 → readNonChunkedRequestBody ctx itr input bodyLen wanted
355 → wasteNonChunkedRequestBody ctx input bodyLen
357 wasteNonChunkedRequestBody ∷ HandleLike h
362 wasteNonChunkedRequestBody ctx input bodyLen
363 = do let input' = Lazy.drop (fromIntegral bodyLen) input
364 acceptRequest ctx input'
366 readNonChunkedRequestBody ∷ HandleLike h
373 readNonChunkedRequestBody ctx itr@(Interaction {..}) input bodyLen wanted
374 | bodyLen ≡ 0 = gotEndOfRequest
375 | otherwise = gotBody
379 = do let bytesToRead = min wanted bodyLen
380 (block, input') = Lazy.splitAt (fromIntegral bytesToRead) input
381 block' = Strict.concat $ Lazy.toChunks block
382 actualReadBytes = Strict.length block'
383 bodyLen' = bodyLen - actualReadBytes
384 atomically $ putTMVar itrReceivedBody block'
385 waitForReceiveNonChunkedBodyReq ctx itr input' bodyLen'
387 gotEndOfRequest ∷ IO ()
389 = do atomically $ putTMVar itrReceivedBody (∅)
390 acceptRequest ctx input
392 enqueue ∷ HandleLike h ⇒ Context h → Interaction → STM ()
393 enqueue (Context {..}) itr
394 = do queue ← readTVar cQueue
395 writeTVar cQueue (itr ⊲ queue)