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 handleAsyncE ∷ AsyncException → IO ()
75 handleAsyncE ThreadKilled = return ()
76 handleAsyncE e = dump e
78 handleBIOS ∷ BlockedIndefinitelyOnSTM → IO ()
81 handleOthers ∷ SomeException → IO ()
84 dump ∷ Exception e ⇒ e → IO ()
86 = do hPutStrLn stderr "requestReader caught an exception:"
87 hPutStrLn stderr (show $ toException e)
89 acceptRequest ∷ HandleLike h ⇒ Context h → Lazy.ByteString → IO ()
90 acceptRequest ctx@(Context {..}) input
92 do queue ← readTVar cQueue
93 when (S.length queue ≥ cnfMaxPipelineDepth cConfig)
94 -- Too many requests in the pipeline...
96 if Lazy.null input then
99 case LP.parse request input of
100 LP.Done input' req → acceptParsableRequest ctx req input'
101 LP.Fail _ _ _ → acceptNonparsableRequest ctx
103 endOfRequests ∷ HandleLike h ⇒ Context h → IO ()
105 = enqueue ctx EndOfInteraction
107 acceptNonparsableRequest ∷ HandleLike h ⇒ Context h → IO ()
108 acceptNonparsableRequest ctx@(Context {..})
109 = do syi ← mkSyntacticallyInvalidInteraction cConfig
112 acceptParsableRequest ∷ HandleLike h
117 acceptParsableRequest ctx@(Context {..}) req input
118 = do let ar = preprocess (cnfServerHost cConfig) cPort req
119 if isError $ arInitialStatus ar then
120 acceptSemanticallyInvalidRequest ctx ar input
122 do rsrc ← findResource cResTree cFallbacks $ reqURI $ arRequest ar
126 arInitialStatus = fromStatusCode NotFound
128 acceptSemanticallyInvalidRequest ctx ar' input
130 → acceptRequestForResource ctx ar input path def
132 acceptSemanticallyInvalidRequest ∷ HandleLike h
137 acceptSemanticallyInvalidRequest ctx@(Context {..}) ar input
138 = do sei ← mkSemanticallyInvalidInteraction cConfig ar
140 acceptRequest ctx input
142 acceptRequestForResource ∷ HandleLike h
146 → [Strict.ByteString]
149 acceptRequestForResource ctx@(Context {..}) ar@(AugmentedRequest {..}) input rsrcPath rsrcDef
151 #if defined(HAVE_SSL)
152 cert ← hGetPeerCert cHandle
153 ni ← mkNormalInteraction cConfig cAddr cert ar rsrcPath
155 ni ← mkNormalInteraction cConfig cAddr ar rsrcPath
157 tid ← spawnResource rsrcDef ni
159 if reqMustHaveBody arRequest then
160 waitForReceiveBodyReq ctx ni tid input
162 acceptRequest ctx input
164 waitForReceiveBodyReq ∷ HandleLike h
170 waitForReceiveBodyReq ctx ni@(NI {..}) rsrcTid input
171 = case fromJust niReqBodyLength of
173 → waitForReceiveChunkedBodyReqForTheFirstTime ctx ni rsrcTid input
175 → waitForReceiveNonChunkedBodyReqForTheFirstTime ctx ni input len
177 -- Toooooo long name for a function...
178 waitForReceiveChunkedBodyReqForTheFirstTime ∷ HandleLike h
184 waitForReceiveChunkedBodyReqForTheFirstTime ctx ni@(NI {..}) rsrcTid input
187 do req ← takeTMVar niReceiveBodyReq
190 → do putTMVar niSendContinue niExpectedContinue
191 return $ readCurrentChunk ctx ni rsrcTid wanted input Initial
193 → do putTMVar niSendContinue False
194 return $ wasteAllChunks ctx rsrcTid input Initial
196 waitForReceiveChunkedBodyReq ∷ HandleLike h
201 → ChunkReceivingState
203 waitForReceiveChunkedBodyReq ctx ni@(NI {..}) rsrcTid input st
204 = do req ← atomically $ takeTMVar niReceiveBodyReq
207 → readCurrentChunk ctx ni rsrcTid wanted input st
209 → wasteAllChunks ctx rsrcTid input st
211 wasteAllChunks ∷ HandleLike h
215 → ChunkReceivingState
217 wasteAllChunks ctx rsrcTid = go
219 go ∷ Lazy.ByteString → ChunkReceivingState → IO ()
221 = case LP.parse chunkHeader input of
222 LP.Done input' chunkLen
223 | chunkLen ≡ 0 → gotFinalChunk input'
224 | otherwise → gotChunk input' chunkLen
226 → chunkWasMalformed rsrcTid eCtx e
227 "wasteAllChunks: chunkHeader"
228 go input (InChunk chunkLen)
229 = gotChunk input chunkLen
231 gotChunk ∷ Lazy.ByteString → Int → IO ()
232 gotChunk input chunkLen
233 = let input' = Lazy.drop (fromIntegral chunkLen) input
235 case LP.parse chunkFooter input' of
239 → chunkWasMalformed rsrcTid eCtx e
240 "wasteAllChunks: chunkFooter"
242 gotFinalChunk ∷ Lazy.ByteString → IO ()
244 = case LP.parse chunkTrailer input of
246 → acceptRequest ctx input'
248 → chunkWasMalformed rsrcTid eCtx e
249 "wasteAllChunks: chunkTrailer"
251 readCurrentChunk ∷ HandleLike h
257 → ChunkReceivingState
259 readCurrentChunk ctx ni@(NI {..}) rsrcTid wanted = go
261 go ∷ Lazy.ByteString → ChunkReceivingState → IO ()
263 = case LP.parse chunkHeader input of
264 LP.Done input' chunkLen
266 → gotFinalChunk input'
268 → gotChunk input' chunkLen
270 → chunkWasMalformed rsrcTid eCtx e
271 "readCurrentChunk: chunkHeader"
272 go input (InChunk chunkLen)
273 = gotChunk input chunkLen
275 gotChunk ∷ Lazy.ByteString → Int → IO ()
276 gotChunk input chunkLen
277 = do let bytesToRead = min wanted chunkLen
278 (block, input') = Lazy.splitAt (fromIntegral bytesToRead) input
279 block' = Strict.concat $ Lazy.toChunks block
280 actualReadBytes = Strict.length block'
281 chunkLen' = chunkLen - actualReadBytes
282 atomically $ putTMVar niReceivedBody block'
283 if chunkLen' ≡ 0 then
284 case LP.parse chunkFooter input' of
286 → waitForReceiveChunkedBodyReq ctx ni rsrcTid input'' Initial
288 → chunkWasMalformed rsrcTid eCtx e
289 "readCurrentChunk: chunkFooter"
291 waitForReceiveChunkedBodyReq ctx ni rsrcTid input' $ InChunk chunkLen'
293 gotFinalChunk ∷ Lazy.ByteString → IO ()
295 = do atomically $ putTMVar niReceivedBody (∅)
296 case LP.parse chunkTrailer input of
298 → acceptRequest ctx input'
300 → chunkWasMalformed rsrcTid eCtx e
301 "readCurrentChunk: chunkTrailer"
303 chunkWasMalformed ∷ ThreadId → [String] → String → String → IO ()
304 chunkWasMalformed tid eCtx e msg
305 = let abo = mkAbortion BadRequest [("Connection", "close")]
307 $ "chunkWasMalformed: "
310 ⊕ T.pack (intercalate ", " eCtx)
316 waitForReceiveNonChunkedBodyReqForTheFirstTime ∷ HandleLike h
322 waitForReceiveNonChunkedBodyReqForTheFirstTime ctx ni@(NI {..}) input bodyLen
325 do req ← takeTMVar niReceiveBodyReq
328 → do putTMVar niSendContinue niExpectedContinue
329 return $ readNonChunkedRequestBody ctx ni input bodyLen wanted
331 → do putTMVar niSendContinue False
332 return $ wasteNonChunkedRequestBody ctx input bodyLen
334 waitForReceiveNonChunkedBodyReq ∷ HandleLike h
340 waitForReceiveNonChunkedBodyReq ctx ni@(NI {..}) input bodyLen
341 = do req ← atomically $ takeTMVar niReceiveBodyReq
344 → readNonChunkedRequestBody ctx ni input bodyLen wanted
346 → wasteNonChunkedRequestBody ctx input bodyLen
348 wasteNonChunkedRequestBody ∷ HandleLike h
353 wasteNonChunkedRequestBody ctx input bodyLen
354 = do let input' = Lazy.drop (fromIntegral bodyLen) input
355 acceptRequest ctx input'
357 readNonChunkedRequestBody ∷ HandleLike h
364 readNonChunkedRequestBody ctx ni@(NI {..}) input bodyLen wanted
365 | bodyLen ≡ 0 = gotEndOfRequest
366 | otherwise = gotBody
370 = do let bytesToRead = min wanted bodyLen
371 (block, input') = Lazy.splitAt (fromIntegral bytesToRead) input
372 block' = Strict.concat $ Lazy.toChunks block
373 actualReadBytes = Strict.length block'
374 bodyLen' = bodyLen - actualReadBytes
375 atomically $ putTMVar niReceivedBody block'
376 waitForReceiveNonChunkedBodyReq ctx ni input' bodyLen'
378 gotEndOfRequest ∷ IO ()
380 = do atomically $ putTMVar niReceivedBody (∅)
381 acceptRequest ctx input
383 enqueue ∷ (HandleLike h, Interaction i) ⇒ Context h → i → IO ()
384 {-# INLINEABLE enqueue #-}
385 enqueue (Context {..}) itr
387 do queue ← readTVar cQueue
388 writeTVar cQueue (toInteraction itr ⊲ queue)