9 module Network.HTTP.Lucu.RequestReader
13 import Control.Concurrent
14 import Control.Concurrent.STM
15 import Control.Exception hiding (block)
17 import qualified Data.Attoparsec.Lazy as LP
18 import qualified Data.ByteString as Strict
19 import qualified Data.ByteString.Lazy as Lazy
22 import Data.Monoid.Unicode
23 import qualified Data.Sequence as S
24 import qualified Data.Text as T
25 import Network.HTTP.Lucu.Abortion
26 import Network.HTTP.Lucu.Config
27 import Network.HTTP.Lucu.Chunk
28 import Network.HTTP.Lucu.HandleLike
29 import Network.HTTP.Lucu.Interaction
30 import Network.HTTP.Lucu.Preprocess
31 import Network.HTTP.Lucu.Request
32 import Network.HTTP.Lucu.Response
33 import Network.HTTP.Lucu.Resource.Internal
34 import Network.HTTP.Lucu.Resource.Tree
35 import Network.HTTP.Lucu.Utils
37 import Prelude.Unicode
38 import System.IO (hPutStrLn, stderr)
44 , cFallbacks ∷ ![FallbackHandler]
48 , cQueue ∷ !InteractionQueue
51 data ChunkReceivingState
53 | InChunk !Int -- ^Number of remaining octets in the current
54 -- chunk. It's always positive.
56 requestReader ∷ HandleLike h
65 requestReader cnf tree fbs h port addr tQueue
66 = do input ← hGetLBS h
67 acceptRequest (Context cnf tree fbs h port addr tQueue) input
69 [ Handler handleAsyncE
71 , Handler handleOthers
74 enqueue' tQueue EndOfInteraction
76 handleAsyncE ∷ AsyncException → IO ()
77 handleAsyncE ThreadKilled = return ()
78 handleAsyncE e = dump e
80 handleBIOS ∷ BlockedIndefinitelyOnSTM → IO ()
83 handleOthers ∷ SomeException → IO ()
86 dump ∷ Exception e ⇒ e → IO ()
88 = do hPutStrLn stderr "requestReader caught an exception:"
89 hPutStrLn stderr $ show e
91 acceptRequest ∷ HandleLike h ⇒ Context h → Lazy.ByteString → IO ()
92 acceptRequest ctx@(Context {..}) input
94 do queue ← readTVar cQueue
95 when (S.length queue ≥ cnfMaxPipelineDepth cConfig)
96 -- Too many requests in the pipeline...
98 if Lazy.null input then
101 case LP.parse request input of
102 LP.Done input' req → acceptParsableRequest ctx req input'
103 LP.Fail _ _ _ → acceptNonparsableRequest ctx
105 acceptNonparsableRequest ∷ HandleLike h ⇒ Context h → IO ()
106 acceptNonparsableRequest ctx@(Context {..})
107 = do syi ← mkSyntacticallyInvalidInteraction cConfig
110 acceptParsableRequest ∷ HandleLike h
115 acceptParsableRequest ctx@(Context {..}) req input
116 = do let ar = preprocess (cnfServerHost cConfig) cPort req
117 if isError $ arInitialStatus ar then
118 acceptSemanticallyInvalidRequest ctx ar input
120 do rsrc ← findResource cResTree cFallbacks $ reqURI $ arRequest ar
124 arInitialStatus = fromStatusCode NotFound
126 acceptSemanticallyInvalidRequest ctx ar' input
128 → acceptRequestForResource ctx ar input path def
130 acceptSemanticallyInvalidRequest ∷ HandleLike h
135 acceptSemanticallyInvalidRequest ctx@(Context {..}) ar input
136 = do sei ← mkSemanticallyInvalidInteraction cConfig ar
138 acceptRequest ctx input
140 acceptRequestForResource ∷ HandleLike h
144 → [Strict.ByteString]
147 acceptRequestForResource ctx@(Context {..}) ar@(AugmentedRequest {..}) input rsrcPath rsrcDef
149 #if defined(HAVE_SSL)
150 cert ← hGetPeerCert cHandle
151 ni ← mkNormalInteraction cConfig cAddr cert ar rsrcPath
153 ni ← mkNormalInteraction cConfig cAddr ar rsrcPath
155 tid ← spawnResource rsrcDef ni
157 if reqMustHaveBody arRequest then
158 waitForReceiveBodyReq ctx ni tid input
160 acceptRequest ctx input
162 waitForReceiveBodyReq ∷ HandleLike h
168 waitForReceiveBodyReq ctx ni@(NI {..}) rsrcTid input
169 = case fromJust niReqBodyLength of
171 → waitForReceiveChunkedBodyReqForTheFirstTime ctx ni rsrcTid input
173 → waitForReceiveNonChunkedBodyReqForTheFirstTime ctx ni input len
175 -- Toooooo long name for a function...
176 waitForReceiveChunkedBodyReqForTheFirstTime ∷ HandleLike h
182 waitForReceiveChunkedBodyReqForTheFirstTime ctx ni@(NI {..}) rsrcTid input
185 do req ← takeTMVar niReceiveBodyReq
188 → do putTMVar niSendContinue niExpectedContinue
189 return $ readCurrentChunk ctx ni rsrcTid wanted input Initial
191 → do putTMVar niSendContinue False
192 return $ wasteAllChunks ctx rsrcTid input Initial
194 waitForReceiveChunkedBodyReq ∷ HandleLike h
199 → ChunkReceivingState
201 waitForReceiveChunkedBodyReq ctx ni@(NI {..}) rsrcTid input st
202 = do req ← atomically $ takeTMVar niReceiveBodyReq
205 → readCurrentChunk ctx ni rsrcTid wanted input st
207 → wasteAllChunks ctx rsrcTid input st
209 wasteAllChunks ∷ HandleLike h
213 → ChunkReceivingState
215 wasteAllChunks ctx rsrcTid = go
217 go ∷ Lazy.ByteString → ChunkReceivingState → IO ()
219 = case LP.parse chunkHeader input of
220 LP.Done input' chunkLen
221 | chunkLen ≡ 0 → gotFinalChunk input'
222 | otherwise → gotChunk input' chunkLen
224 → chunkWasMalformed rsrcTid eCtx e
225 "wasteAllChunks: chunkHeader"
226 go input (InChunk chunkLen)
227 = gotChunk input chunkLen
229 gotChunk ∷ Lazy.ByteString → Int → IO ()
230 gotChunk input chunkLen
231 = let input' = Lazy.drop (fromIntegral chunkLen) input
233 case LP.parse chunkFooter input' of
237 → chunkWasMalformed rsrcTid eCtx e
238 "wasteAllChunks: chunkFooter"
240 gotFinalChunk ∷ Lazy.ByteString → IO ()
242 = case LP.parse chunkTrailer input of
244 → acceptRequest ctx input'
246 → chunkWasMalformed rsrcTid eCtx e
247 "wasteAllChunks: chunkTrailer"
249 readCurrentChunk ∷ HandleLike h
255 → ChunkReceivingState
257 readCurrentChunk ctx ni@(NI {..}) rsrcTid wanted = go
259 go ∷ Lazy.ByteString → ChunkReceivingState → IO ()
261 = case LP.parse chunkHeader input of
262 LP.Done input' chunkLen
264 → gotFinalChunk input'
266 → gotChunk input' chunkLen
268 → chunkWasMalformed rsrcTid eCtx e
269 "readCurrentChunk: chunkHeader"
270 go input (InChunk chunkLen)
271 = gotChunk input chunkLen
273 gotChunk ∷ Lazy.ByteString → Int → IO ()
274 gotChunk input chunkLen
275 = do let bytesToRead = min wanted chunkLen
276 (block, input') = Lazy.splitAt (fromIntegral bytesToRead) input
277 block' = Strict.concat $ Lazy.toChunks block
278 actualReadBytes = Strict.length block'
279 chunkLen' = chunkLen - actualReadBytes
280 atomically $ putTMVar niReceivedBody block'
281 if chunkLen' ≡ 0 then
282 case LP.parse chunkFooter input' of
284 → waitForReceiveChunkedBodyReq ctx ni rsrcTid input'' Initial
286 → chunkWasMalformed rsrcTid eCtx e
287 "readCurrentChunk: chunkFooter"
289 waitForReceiveChunkedBodyReq ctx ni rsrcTid input' $ InChunk chunkLen'
291 gotFinalChunk ∷ Lazy.ByteString → IO ()
293 = do atomically $ putTMVar niReceivedBody (∅)
294 case LP.parse chunkTrailer input of
296 → acceptRequest ctx input'
298 → chunkWasMalformed rsrcTid eCtx e
299 "readCurrentChunk: chunkTrailer"
301 chunkWasMalformed ∷ ThreadId → [String] → String → String → IO ()
302 chunkWasMalformed tid eCtx e msg
303 = let abo = mkAbortion BadRequest [("Connection", "close")]
305 $ "chunkWasMalformed: "
308 ⊕ T.pack (intercalate ", " eCtx)
314 waitForReceiveNonChunkedBodyReqForTheFirstTime ∷ HandleLike h
320 waitForReceiveNonChunkedBodyReqForTheFirstTime ctx ni@(NI {..}) input bodyLen
323 do req ← takeTMVar niReceiveBodyReq
326 → do putTMVar niSendContinue niExpectedContinue
327 return $ readNonChunkedRequestBody ctx ni input bodyLen wanted
329 → do putTMVar niSendContinue False
330 return $ wasteNonChunkedRequestBody ctx input bodyLen
332 waitForReceiveNonChunkedBodyReq ∷ HandleLike h
338 waitForReceiveNonChunkedBodyReq ctx ni@(NI {..}) input bodyLen
339 = do req ← atomically $ takeTMVar niReceiveBodyReq
342 → readNonChunkedRequestBody ctx ni input bodyLen wanted
344 → wasteNonChunkedRequestBody ctx input bodyLen
346 wasteNonChunkedRequestBody ∷ HandleLike h
351 wasteNonChunkedRequestBody ctx input bodyLen
352 = do let input' = Lazy.drop (fromIntegral bodyLen) input
353 acceptRequest ctx input'
355 readNonChunkedRequestBody ∷ HandleLike h
362 readNonChunkedRequestBody ctx ni@(NI {..}) input bodyLen wanted
363 | bodyLen ≡ 0 = gotEndOfRequest
364 | otherwise = gotBody
368 = do let bytesToRead = min wanted bodyLen
369 (block, input') = Lazy.splitAt (fromIntegral bytesToRead) input
370 block' = Strict.concat $ Lazy.toChunks block
371 actualReadBytes = Strict.length block'
372 bodyLen' = bodyLen - actualReadBytes
373 atomically $ putTMVar niReceivedBody block'
374 waitForReceiveNonChunkedBodyReq ctx ni input' bodyLen'
376 gotEndOfRequest ∷ IO ()
378 = do atomically $ putTMVar niReceivedBody (∅)
379 acceptRequest ctx input
381 enqueue ∷ (HandleLike h, Interaction i) ⇒ Context h → i → IO ()
382 enqueue (Context {..}) = enqueue' cQueue
384 enqueue' ∷ Interaction i ⇒ InteractionQueue → i → IO ()
387 do queue ← readTVar tQueue
388 writeTVar tQueue (toInteraction itr ⊲ queue)