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.Internal
30 import Network.HTTP.Lucu.Resource.Tree
33 import Prelude.Unicode
34 import System.IO (hPutStrLn, stderr)
40 , cFallbacks ∷ ![FallbackHandler]
44 , cQueue ∷ !InteractionQueue
47 requestReader ∷ HandleLike h
56 requestReader cnf tree fbs h port addr tQueue
57 = do input ← hGetLBS h
58 acceptRequest (Context cnf tree fbs h port addr tQueue) input
60 [ Handler handleAsyncE
62 , Handler handleOthers
65 handleAsyncE ∷ AsyncException → IO ()
66 handleAsyncE ThreadKilled = return ()
67 handleAsyncE e = dump e
69 handleBIOS ∷ BlockedIndefinitelyOnSTM → IO ()
72 handleOthers ∷ SomeException → IO ()
75 dump ∷ Exception e ⇒ e → IO ()
77 = do hPutStrLn stderr "requestReader caught an exception:"
78 hPutStrLn stderr (show $ toException e)
80 acceptRequest ∷ HandleLike h ⇒ Context h → Lazy.ByteString → IO ()
81 acceptRequest ctx@(Context {..}) input
82 -- キューに最大パイプライン深度以上のリクエストが溜まってゐる時は、
85 do queue ← readTVar cQueue
86 when (S.length queue ≥ cnfMaxPipelineDepth cConfig) $
88 -- リクエストを讀む。パースできない場合は直ちに 400 Bad
89 -- Request 應答を設定し、それを出力してから切斷するやうに
90 -- ResponseWriter に通知する。
91 case LP.parse requestP input of
92 LP.Done input' req → acceptParsableRequest ctx req input'
93 LP.Fail _ _ _ → acceptNonparsableRequest ctx BadRequest
95 acceptNonparsableRequest ∷ HandleLike h ⇒ Context h → StatusCode → IO ()
96 acceptNonparsableRequest ctx@(Context {..}) sc
97 = do itr ← newInteraction cConfig cPort cAddr Nothing (Left sc)
99 do writeTVar (itrState itr) Done
103 acceptParsableRequest ∷ HandleLike h
108 acceptParsableRequest ctx@(Context {..}) req input
109 = do cert ← hGetPeerCert cHandle
110 itr ← newInteraction cConfig cPort cAddr cert (Right req)
112 $ do isErr ← (isError ∘ resStatus) <$> readTVar (itrResponse itr)
114 acceptSemanticallyInvalidRequest ctx itr input
116 return $ acceptSemanticallyValidRequest ctx itr (reqURI req) input
118 acceptSemanticallyInvalidRequest ∷ HandleLike h
123 acceptSemanticallyInvalidRequest ctx itr input
124 = do writeTVar (itrState itr) Done
127 return $ acceptRequest ctx input
129 acceptSemanticallyValidRequest ∷ HandleLike h
135 acceptSemanticallyValidRequest ctx@(Context {..}) itr uri input
136 = do rsrcM ← findResource cResTree cFallbacks uri
139 → acceptRequestForNonexistentResource ctx itr input
140 Just (rsrcPath, rsrcDef)
141 → acceptRequestForExistentResource ctx itr input rsrcPath rsrcDef
143 acceptRequestForNonexistentResource ∷ HandleLike h
148 acceptRequestForNonexistentResource ctx itr input
150 do setResponseStatus itr NotFound
151 writeTVar (itrState itr) Done
154 acceptRequest ctx input
156 acceptRequestForExistentResource ∷ HandleLike h
163 acceptRequestForExistentResource ctx oldItr input rsrcPath rsrcDef
164 = do let itr = oldItr { itrResourcePath = Just rsrcPath }
165 atomically $ enqueue ctx itr
166 do _ ← spawnResource rsrcDef itr
167 if reqMustHaveBody $ fromJust $ itrRequest itr then
168 observeRequest ctx itr input
170 acceptRequest ctx input
172 observeRequest ∷ HandleLike h
177 observeRequest ctx itr input
178 = case fromJust $ itrReqBodyLength itr of
180 → observeChunkedRequest ctx itr input 0
182 → observeNonChunkedRequest ctx itr input len
184 observeChunkedRequest ∷ HandleLike h
190 observeChunkedRequest ctx itr input remaining
193 do isOver ← readTVar $ itrReqChunkIsOver itr
195 return $ acceptRequest ctx input
197 do wanted ← readTVar $ itrReqBodyWanted itr
199 0 → do wasteAll ← readTVar $ itrReqBodyWasteAll itr
201 return $ wasteCurrentChunk ctx itr input remaining
204 _ → return $ readCurrentChunk ctx itr input wanted remaining
206 wasteCurrentChunk ∷ HandleLike h
212 wasteCurrentChunk ctx itr input len
214 = let input' = Lazy.drop (fromIntegral len) input
216 case LP.parse chunkFooterP input' of
218 → observeChunkedRequest ctx itr input'' 0
220 → chunkWasMalformed itr
222 = seekNextChunk ctx itr input
224 readCurrentChunk ∷ HandleLike h
231 readCurrentChunk ctx itr input wanted remaining
233 = do let bytesToRead = fromIntegral $ min wanted remaining
234 (chunk, input') = Lazy.splitAt bytesToRead input
235 actualReadBytes = fromIntegral $ Lazy.length chunk
236 newWanted = wanted - actualReadBytes
237 newRemaining = remaining - actualReadBytes
238 chunk' = S.fromList $ Lazy.toChunks chunk
239 updateStates = atomically $
240 do writeTVar (itrReqBodyWanted itr) newWanted
241 oldBody ← readTVar $ itrReceivedBody itr
242 oldBodyLen ← readTVar $ itrReceivedBodyLen itr
243 writeTVar (itrReceivedBody itr) $ oldBody ⋈ chunk'
244 writeTVar (itrReceivedBodyLen itr) $ oldBodyLen + actualReadBytes
245 if newRemaining ≡ 0 then
246 case LP.parse chunkFooterP input' of
249 observeChunkedRequest ctx itr input'' 0
251 → chunkWasMalformed itr
254 observeChunkedRequest ctx itr input' newRemaining
256 = seekNextChunk ctx itr input
258 seekNextChunk ∷ HandleLike h
263 seekNextChunk ctx itr input
264 = case LP.parse chunkHeaderP input of
266 | len ≡ 0 -- Final chunk
267 → case LP.parse chunkTrailerP input' of
270 writeTVar (itrReqChunkIsOver itr) True
271 acceptRequest ctx input''
273 → chunkWasMalformed itr
274 | otherwise -- Non-final chunk
275 → observeChunkedRequest ctx itr input' len
277 → chunkWasMalformed itr
279 chunkWasMalformed ∷ Interaction → IO ()
280 chunkWasMalformed itr
282 do setResponseStatus itr BadRequest
283 writeTVar (itrWillClose itr) True
284 writeTVar (itrState itr) Done
287 observeNonChunkedRequest ∷ HandleLike h
293 observeNonChunkedRequest ctx itr input remaining
296 do wanted ← readTVar $ itrReqBodyWanted itr
298 0 → do wasteAll ← readTVar $ itrReqBodyWasteAll itr
300 return $ wasteNonChunkedRequestBody ctx itr input remaining
303 _ → return $ readNonChunkedRequestBody ctx itr input wanted remaining
305 wasteNonChunkedRequestBody ∷ HandleLike h
311 wasteNonChunkedRequestBody ctx itr input remaining
312 = do let input' = Lazy.drop (fromIntegral remaining) input
313 atomically $ writeTVar (itrReqChunkIsOver itr) True
314 acceptRequest ctx input'
316 readNonChunkedRequestBody ∷ HandleLike h
323 readNonChunkedRequestBody ctx itr input wanted remaining
324 = do let bytesToRead = min wanted remaining
325 (chunk, input') = Lazy.splitAt (fromIntegral bytesToRead) input
326 actualReadBytes = fromIntegral $ Lazy.length chunk
327 newWanted = wanted - actualReadBytes
328 newRemaining = remaining - actualReadBytes
329 isOver = actualReadBytes < bytesToRead ∨ newRemaining ≡ 0
330 chunk' = S.fromList $ Lazy.toChunks chunk
332 do writeTVar (itrReqChunkIsOver itr) isOver
333 writeTVar (itrReqBodyWanted itr) newWanted
334 writeTVar (itrReceivedBody itr) chunk'
335 writeTVar (itrReceivedBodyLen itr) actualReadBytes
337 acceptRequest ctx input'
339 observeNonChunkedRequest ctx itr input' newRemaining
341 enqueue ∷ HandleLike h ⇒ Context h → Interaction → STM ()
342 enqueue (Context {..}) itr
343 = do queue ← readTVar cQueue
344 writeTVar cQueue (itr ⊲ queue)