]> gitweb @ CieloNegro.org - Lucu.git/blobdiff - Network/HTTP/Lucu/RequestReader.hs
The library compiles again.
[Lucu.git] / Network / HTTP / Lucu / RequestReader.hs
index b0c22be45d93ab9e36612f7d635b4b10df955492..05b30420a95040bf9284b94d19bfb913fde51ae4 100644 (file)
+{-# LANGUAGE
+    DoAndIfThenElse
+  , RecordWildCards
+  , ScopedTypeVariables
+  , UnicodeSyntax
+  #-}
 module Network.HTTP.Lucu.RequestReader
-    ( requestReader -- Config -> ResTree -> Handle -> HostName -> InteractionQueue -> IO ()
+    ( requestReader
     )
     where
-
-import           Control.Concurrent.STM
-import           Control.Exception
-import           Control.Monad
-import qualified Data.ByteString.Lazy.Char8 as B
-import           Data.ByteString.Lazy.Char8 (ByteString)
-import           Data.Map as M
-import           Data.Map (Map)
-import           Data.Maybe
+import Control.Applicative
+import Control.Concurrent.STM
+import Control.Exception hiding (block)
+import Control.Monad
+import qualified Data.Attoparsec.Lazy as LP
+import qualified Data.ByteString as Strict
+import qualified Data.ByteString.Lazy as Lazy
+import Data.Maybe
+import Data.Monoid.Unicode
 import qualified Data.Sequence as S
-import           Data.Sequence (Seq, (<|), ViewR(..))
-import           Network
-import           Network.HTTP.Lucu.Config
-import           Network.HTTP.Lucu.DefaultPage
-import           Network.HTTP.Lucu.HttpVersion
-import           Network.HTTP.Lucu.Interaction
-import           Network.HTTP.Lucu.Parser
-import           Network.HTTP.Lucu.Postprocess
-import           Network.HTTP.Lucu.Preprocess
-import           Network.HTTP.Lucu.Request
-import           Network.HTTP.Lucu.Response
-import           Network.HTTP.Lucu.Resource
-import           Prelude hiding (catch)
-import           System.IO
-
-import GHC.Conc (unsafeIOToSTM)
-
-requestReader :: Config -> ResTree -> Handle -> HostName -> InteractionQueue -> IO ()
-requestReader cnf tree h host tQueue
-    = do catch (acceptRequest B.empty) $ \ exc ->
-             case exc of
-               IOException _               -> return ()
-               AsyncException ThreadKilled -> return ()
-               BlockedIndefinitely         -> putStrLn "requestReader: blocked indefinitely"
-               _                           -> print exc
+import Data.Sequence.Unicode hiding ((∅))
+import Network.HTTP.Lucu.Config
+import Network.HTTP.Lucu.Chunk
+import Network.HTTP.Lucu.HandleLike
+import Network.HTTP.Lucu.Interaction
+import Network.HTTP.Lucu.Postprocess
+import Network.HTTP.Lucu.Preprocess
+import Network.HTTP.Lucu.Request
+import Network.HTTP.Lucu.Response
+import Network.HTTP.Lucu.Resource.Internal
+import Network.HTTP.Lucu.Resource.Tree
+import Network.Socket
+import Network.URI
+import Prelude.Unicode
+import System.IO (hPutStrLn, stderr)
+
+data Context h
+    = Context {
+        cConfig    ∷ !Config
+      , cResTree   ∷ !ResTree
+      , cFallbacks ∷ ![FallbackHandler]
+      , cHandle    ∷ !h
+      , cPort      ∷ !PortNumber
+      , cAddr      ∷ !SockAddr
+      , cQueue     ∷ !InteractionQueue
+      }
+
+data ChunkReceivingState
+    = Initial
+    | InChunk !Int -- ^Number of remaining octets in the current
+                   -- chunk. It's always positive.
+
+requestReader ∷ HandleLike h
+              ⇒ Config
+              → ResTree
+              → [FallbackHandler]
+              → h
+              → PortNumber
+              → SockAddr
+              → InteractionQueue
+              → IO ()
+requestReader cnf tree fbs h port addr tQueue
+    = do input ← hGetLBS h
+         acceptRequest (Context cnf tree fbs h port addr tQueue) input
+      `catches`
+      [ Handler handleAsyncE
+      , Handler handleBIOS
+      , Handler handleOthers
+      ]
     where
-      acceptRequest :: ByteString -> IO ()
-      acceptRequest soFar
-          -- キューに最大パイプライン深度以上のリクエストが溜まってゐる
-          -- 時は、それが限度以下になるまで待つ。
-          = do atomically $ do queue    <- readTVar tQueue
-                               when (S.length queue >= cnfMaxPipelineDepth cnf)
-                                    retry
-
-               -- リクエストを讀む。パースできない場合は直ちに 400 Bad
-               -- Request 應答を設定し、それを出力してから切斷するやう
-               -- に ResponseWriter に通知する。
-               hWaitForInput h (-1)
-               chunk <- B.hGetNonBlocking h 1024
-
-               let input = B.append soFar chunk
-               case parse requestP input of
-                 (Success req , input') -> acceptParsableRequest req input'
-                 (IllegalInput, _     ) -> acceptNonparsableRequest BadRequest
-                 (ReachedEOF  , _     ) -> if B.length input >= 1024 * 1024 then
-                                               -- ヘッダ長過ぎ
-                                               acceptNonparsableRequest RequestEntityTooLarge
-                                           else
-                                               acceptRequest input
-
-      acceptNonparsableRequest :: StatusCode -> IO ()
-      acceptNonparsableRequest status
-          = do itr <- newInteraction cnf host Nothing
-               let res = Response {
-                           resVersion = HttpVersion 1 1
-                         , resStatus  = status
-                         , resHeaders = []
-                         }
-               atomically $ do writeItr itr itrResponse $ Just res
-                               writeItr itr itrWillClose True
-                               writeItr itr itrState     Done
-                               writeDefaultPage itr
-                               postprocess itr
-                               enqueue itr
-
-      acceptParsableRequest :: Request -> ByteString -> IO ()
-      acceptParsableRequest req soFar
-          = do itr <- newInteraction cnf host (Just req)
-               action
-                   <- atomically $
-                      do preprocess itr
-                         isErr <- readItrF itr itrResponse (isError . resStatus)
-                         if isErr == Just True then
-                             acceptSemanticallyInvalidRequest itr soFar
-                           else
-                             case findResource tree $ (reqURI . fromJust . itrRequest) itr of
-                               Nothing -- Resource が無かった
-                                   -> acceptRequestForNonexistentResource itr soFar
-
-                               Just rsrcDef -- あった
-                                   -> acceptRequestForExistentResource itr soFar rsrcDef
-               action
-
-      acceptSemanticallyInvalidRequest :: Interaction -> ByteString -> STM (IO ())
-      acceptSemanticallyInvalidRequest itr soFar
-          = do writeItr itr itrState Done
-               writeDefaultPage itr
-               postprocess itr
-               enqueue itr
-               return $ acceptRequest soFar
-
-      acceptRequestForNonexistentResource :: Interaction -> ByteString -> STM (IO ())
-      acceptRequestForNonexistentResource itr soFar
-          = do let res = Response {
-                           resVersion = HttpVersion 1 1
-                         , resStatus  = NotFound
-                         , resHeaders = []
-                         }
-               writeItr itr itrResponse $ Just res
-               writeItr itr itrState Done
-               writeDefaultPage itr
-               postprocess itr
-               enqueue itr
-               return $ acceptRequest soFar
-
-      acceptRequestForExistentResource :: Interaction -> ByteString -> ResourceDef -> STM (IO ())
-      acceptRequestForExistentResource itr soFar rsrcDef
-          = do requestHasBody <- readItr itr itrRequestHasBody id
-               writeItr itr itrState (if requestHasBody
-                                      then ExaminingHeader
-                                      else DecidingHeader)
-               enqueue itr
-               return $ do runResource rsrcDef itr
-                           if requestHasBody then
-                               observeRequest itr soFar
-                             else
-                               acceptRequest soFar
-
-      observeRequest :: Interaction -> ByteString -> IO ()
-      observeRequest itr soFar
-          = do isChunked <- atomically $ readItr itr itrRequestIsChunked id
-               if isChunked then
-                   observeChunkedRequest itr soFar
-                 else
-                   observeNonChunkedRequest itr soFar
-
-      observeChunkedRequest :: Interaction -> ByteString -> IO ()
-      observeChunkedRequest itr soFar
-          = fail "FIXME: not implemented"
-
-      observeNonChunkedRequest :: Interaction -> ByteString -> IO ()
-      observeNonChunkedRequest itr soFar
-          = fail "FIXME: not implemented"
-{-
-          = do action
-                   <- atomically $
-                      do wantedM <- readItr itr itrReqBodyWanted id
-                         if wantedM == Nothing then
-                             do wasteAll <- readItr itr itrReqBodyWasteAll id
-                                if wasteAll then
-                                    return $ wasteAllReqBody itr soFar
-                                  else
-                                    retry
-                           else
-                             -- 受信要求が來た。
-                             if B.empty soFar then
-                                 return $ receiveNonChunkedReqBody itr
-                             else
-                                 do remaining <- readItr itr itrReqChunkRemaining fromJust
-
-                                    let wanted = fromJust wanted
-                                        (chunk, input') = B.splitAt (min wanted remaining) soFar
-                                        newRemaining    = remaining - B.length chunk
-                                        isOver          = newRemaining == 0
-
-                                    writeItr itr itrReqChunkRemaining newRemaining
-                                    writeItr itr itrReqChunkIsOver isOver
-                                    writeItr itr itrReqBodyWanted (if isOver then
-                                                                       Nothing
-                                                                   else
-                                                                       Just wanted)
-                                    writeItr itr itrReceivedBody chunk
-
-                                    if isOver then
-                                        return $ acceptRequest input'
-                                      else
-                                        return $ observeNonChunkedRequest itr input'
-               action
-
-      receiveNonChunkedReqBody :: Interaction -> IO ()
-      receiveNonChunkedReqBody itr
-          = do wanted    <- atomically $ readItr itr itrReqBodyWanted fromJust
-               remaining <- atomically $ readItr itr itrReqChunkRemaining fromJust
-                            
-               hWaitForInput h (-1)
-               chunk <- B.hGetNonBlocking h (min wanted remaining)
-
-               let newRemaining = remaining - B.length chunk
-                   isOver       = newRemaining == 0
-
-               atomically $ do writeItr itr itrReqChunkRemaining newRemaining
-                               writeItr itr itrReqChunkIsOver isOver
-                               writeItr itr itrReqBodyWanted (if isOver then
-                                                                  Nothing
-                                                              else
-                                                                  Just wanted)
-                               writeItr itr itrReceivedBody chunk
-
-               if isOver then
-                   return $ acceptRequest B.empty
-                 else
-                   return $ observeNonChunkedRequest itr B.empty
-
-
-      wasteAllReqBody :: Interaction -> ByteString -> IO ()
-      wasteAllReqBody itr soFar
-          = 
-                         
--}
-
-      enqueue :: Interaction -> STM ()
-      enqueue itr = do queue <- readTVar tQueue
-                       writeTVar tQueue (itr <| queue)
\ No newline at end of file
+      handleAsyncE ∷ AsyncException → IO ()
+      handleAsyncE ThreadKilled = return ()
+      handleAsyncE e            = dump e
+
+      handleBIOS ∷ BlockedIndefinitelyOnSTM → IO ()
+      handleBIOS = dump
+
+      handleOthers ∷ SomeException → IO ()
+      handleOthers = dump
+
+      dump ∷ Exception e ⇒ e → IO ()
+      dump e
+          = do hPutStrLn stderr "requestReader caught an exception:"
+               hPutStrLn stderr (show $ toException e)
+
+acceptRequest ∷ HandleLike h ⇒ Context h → Lazy.ByteString → IO ()
+acceptRequest ctx@(Context {..}) input
+    -- キューに最大パイプライン深度以上のリクエストが溜まってゐる時は、
+    -- それが限度以下になるまで待つ。
+    = do atomically $
+             do queue ← readTVar cQueue
+                when (S.length queue ≥ cnfMaxPipelineDepth cConfig) $
+                    retry
+         -- リクエストを讀む。パースできない場合は直ちに 400 Bad
+         -- Request 應答を設定し、それを出力してから切斷するやうに
+         -- ResponseWriter に通知する。
+         case LP.parse requestP input of
+           LP.Done input' req → acceptParsableRequest ctx req input'
+           LP.Fail _ _ _      → acceptNonparsableRequest ctx BadRequest
+
+acceptNonparsableRequest ∷ HandleLike h ⇒ Context h → StatusCode → IO ()
+acceptNonparsableRequest ctx@(Context {..}) sc
+    = do itr ← newInteraction cConfig cPort cAddr Nothing (Left sc)
+         atomically $
+             do writeTVar (itrState itr) Done
+                postprocess itr
+                enqueue ctx itr
+
+acceptParsableRequest ∷ HandleLike h
+                      ⇒ Context h
+                      → Request
+                      → Lazy.ByteString
+                      → IO ()
+acceptParsableRequest ctx@(Context {..}) req input
+    = do cert ← hGetPeerCert cHandle
+         itr  ← newInteraction cConfig cPort cAddr cert (Right req)
+         join $ atomically
+              $ do isErr ← (isError ∘ resStatus) <$> readTVar (itrResponse itr)
+                   if isErr then
+                       acceptSemanticallyInvalidRequest ctx itr input
+                   else
+                       return $ acceptSemanticallyValidRequest ctx itr (reqURI req) input
+
+acceptSemanticallyInvalidRequest ∷ HandleLike h
+                                 ⇒ Context h
+                                 → Interaction
+                                 → Lazy.ByteString
+                                 → STM (IO ())
+acceptSemanticallyInvalidRequest ctx itr input
+    = do writeTVar (itrState itr) Done
+         postprocess itr
+         enqueue ctx itr
+         return $ acceptRequest ctx input
+
+acceptSemanticallyValidRequest ∷ HandleLike h
+                               ⇒ Context h
+                               → Interaction
+                               → URI
+                               → Lazy.ByteString
+                               → IO ()
+acceptSemanticallyValidRequest ctx@(Context {..}) itr uri input
+    = do rsrcM ← findResource cResTree cFallbacks uri
+         case rsrcM of
+           Nothing
+               → acceptRequestForNonexistentResource ctx itr input
+           Just (rsrcPath, rsrcDef)
+               → acceptRequestForExistentResource ctx itr input rsrcPath rsrcDef
+
+acceptRequestForNonexistentResource ∷ HandleLike h
+                                    ⇒ Context h
+                                    → Interaction
+                                    → Lazy.ByteString
+                                    → IO ()
+acceptRequestForNonexistentResource ctx itr input
+    = do atomically $
+             do setResponseStatus itr NotFound
+                writeTVar (itrState itr) Done
+                postprocess itr
+                enqueue ctx itr
+         acceptRequest ctx input
+
+acceptRequestForExistentResource ∷ HandleLike h
+                                 ⇒ Context h
+                                 → Interaction
+                                 → Lazy.ByteString
+                                 → [Strict.ByteString]
+                                 → ResourceDef
+                                 → IO ()
+acceptRequestForExistentResource ctx oldItr input rsrcPath rsrcDef
+    = do let itr = oldItr { itrResourcePath = Just rsrcPath }
+         atomically $ enqueue ctx itr
+         do _ ← spawnResource rsrcDef itr
+            if reqMustHaveBody $ fromJust $ itrRequest itr then
+                waitForReceiveBodyReq ctx itr input
+            else
+                acceptRequest ctx input
+
+waitForReceiveBodyReq ∷ HandleLike h
+                      ⇒ Context h
+                      → Interaction
+                      → Lazy.ByteString
+                      → IO ()
+waitForReceiveBodyReq ctx itr input
+    = case fromJust $ itrReqBodyLength itr of
+        Chunked
+            → waitForReceiveChunkedBodyReqForTheFirstTime ctx itr input
+        Fixed len
+            → waitForReceiveNonChunkedBodyReqForTheFirstTime ctx itr input len
+
+-- Toooooo long name for a function...
+waitForReceiveChunkedBodyReqForTheFirstTime ∷ HandleLike h
+                                            ⇒ Context h
+                                            → Interaction
+                                            → Lazy.ByteString
+                                            → IO ()
+waitForReceiveChunkedBodyReqForTheFirstTime ctx itr@(Interaction {..}) input
+    = join $
+      atomically $
+      do req ← takeTMVar itrReceiveBodyReq
+         case req of
+           ReceiveBody wanted
+               → do putTMVar itrSendContinue $ fromJust itrExpectedContinue
+                    return $ readCurrentChunk ctx itr input Initial wanted
+           WasteAll
+               → do putTMVar itrSendContinue False
+                    return $ wasteAllChunks ctx itr input Initial
+
+waitForReceiveChunkedBodyReq ∷ HandleLike h
+                             ⇒ Context h
+                             → Interaction
+                             → Lazy.ByteString
+                             → ChunkReceivingState
+                             → IO ()
+waitForReceiveChunkedBodyReq ctx itr@(Interaction {..}) input st
+    = do req ← atomically $ takeTMVar itrReceiveBodyReq
+         case req of
+           ReceiveBody wanted
+               → readCurrentChunk ctx itr input st wanted
+           WasteAll
+               → wasteAllChunks ctx itr input st
+
+wasteAllChunks ∷ HandleLike h
+               ⇒ Context h
+               → Interaction
+               → Lazy.ByteString
+               → ChunkReceivingState
+               → IO ()
+wasteAllChunks ctx itr = go
+    where
+      go ∷ Lazy.ByteString → ChunkReceivingState → IO ()
+      go input Initial
+          = case LP.parse chunkHeaderP input of
+              LP.Done input' chunkLen
+                  | chunkLen ≡ 0 → gotFinalChunk input'
+                  | otherwise    → gotChunk input' chunkLen
+              LP.Fail _ _ _
+                  → chunkWasMalformed itr
+      go input (InChunk chunkLen)
+          = gotChunk input chunkLen
+
+      gotChunk ∷ Lazy.ByteString → Int → IO ()
+      gotChunk input chunkLen
+          = let input' = Lazy.drop (fromIntegral chunkLen) input
+            in
+              case LP.parse chunkFooterP input' of
+                LP.Done input'' _
+                    → go input'' Initial
+                LP.Fail _ _ _
+                    → chunkWasMalformed itr
+
+      gotFinalChunk ∷ Lazy.ByteString → IO ()
+      gotFinalChunk input
+          = case LP.parse chunkFooterP input of
+              LP.Done input' _
+                  → case LP.parse chunkTrailerP input' of
+                       LP.Done input'' _
+                           → acceptRequest ctx input''
+                       LP.Fail _ _ _
+                           → chunkWasMalformed itr
+              LP.Fail _ _ _
+                  → chunkWasMalformed itr
+
+readCurrentChunk ∷ HandleLike h
+                 ⇒ Context h
+                 → Interaction
+                 → Lazy.ByteString
+                 → ChunkReceivingState
+                 → Int
+                 → IO ()
+readCurrentChunk ctx itr@(Interaction {..}) input0 st0 wanted
+    = go input0 st0
+    where
+      go ∷ Lazy.ByteString → ChunkReceivingState → IO ()
+      go input Initial
+          = case LP.parse chunkHeaderP input of
+              LP.Done input' chunkLen
+                  | chunkLen ≡ 0
+                      → gotFinalChunk input'
+                  | otherwise
+                      → gotChunk input' chunkLen
+              LP.Fail _ _ _
+                  → chunkWasMalformed itr
+      go input (InChunk chunkLen)
+          = gotChunk input chunkLen
+
+      gotChunk ∷ Lazy.ByteString → Int → IO ()
+      gotChunk input chunkLen
+          = do let bytesToRead     = min wanted chunkLen
+                   (block, input') = Lazy.splitAt (fromIntegral bytesToRead) input
+                   block'          = Strict.concat $ Lazy.toChunks block
+                   actualReadBytes = Strict.length block'
+                   chunkLen'       = chunkLen - actualReadBytes
+               atomically $ putTMVar itrReceivedBody block'
+               if chunkLen' ≡ 0 then
+                   case LP.parse chunkFooterP input' of
+                     LP.Done input'' _
+                         → waitForReceiveChunkedBodyReq ctx itr input'' Initial
+                     LP.Fail _ _ _
+                         → chunkWasMalformed itr
+               else
+                   waitForReceiveChunkedBodyReq ctx itr input' $ InChunk chunkLen'
+
+      gotFinalChunk ∷ Lazy.ByteString → IO ()
+      gotFinalChunk input
+          = do atomically $ putTMVar itrReceivedBody (∅)
+               case LP.parse chunkFooterP input of
+                 LP.Done input' _
+                     → case LP.parse chunkTrailerP input' of
+                          LP.Done input'' _
+                              → acceptRequest ctx input''
+                          LP.Fail _ _ _
+                              → chunkWasMalformed itr
+                 LP.Fail _ _ _
+                     → chunkWasMalformed itr
+
+chunkWasMalformed ∷ Interaction → IO ()
+chunkWasMalformed itr
+    -- FIXME: This is a totally wrong way to abort!
+    = atomically $
+      do setResponseStatus itr BadRequest
+         writeTVar (itrWillClose itr) True
+         writeTVar (itrState     itr) Done
+         postprocess itr
+
+waitForReceiveNonChunkedBodyReqForTheFirstTime ∷ HandleLike h
+                                               ⇒ Context h
+                                               → Interaction
+                                               → Lazy.ByteString
+                                               → Int
+                                               → IO ()
+waitForReceiveNonChunkedBodyReqForTheFirstTime ctx itr@(Interaction {..}) input bodyLen
+    = join $
+      atomically $
+      do req ← takeTMVar itrReceiveBodyReq
+         case req of
+           ReceiveBody wanted
+               → do putTMVar itrSendContinue $ fromJust itrExpectedContinue
+                    return $ readNonChunkedRequestBody ctx itr input bodyLen wanted
+           WasteAll
+               → do putTMVar itrSendContinue False
+                    return $ wasteNonChunkedRequestBody ctx input bodyLen
+
+waitForReceiveNonChunkedBodyReq ∷ HandleLike h
+                                ⇒ Context h
+                                → Interaction
+                                → Lazy.ByteString
+                                → Int
+                                → IO ()
+waitForReceiveNonChunkedBodyReq ctx itr@(Interaction {..}) input bodyLen
+    = do req ← atomically $ takeTMVar itrReceiveBodyReq
+         case req of
+           ReceiveBody wanted
+               → readNonChunkedRequestBody ctx itr input bodyLen wanted
+           WasteAll
+               → wasteNonChunkedRequestBody ctx input bodyLen
+
+wasteNonChunkedRequestBody ∷ HandleLike h
+                           ⇒ Context h
+                           → Lazy.ByteString
+                           → Int
+                           → IO ()
+wasteNonChunkedRequestBody ctx input bodyLen
+    = do let input' = Lazy.drop (fromIntegral bodyLen) input
+         acceptRequest ctx input'
+
+readNonChunkedRequestBody ∷ HandleLike h
+                          ⇒ Context h
+                          → Interaction
+                          → Lazy.ByteString
+                          → Int
+                          → Int
+                          → IO ()
+readNonChunkedRequestBody ctx itr@(Interaction {..}) input bodyLen wanted
+    | bodyLen ≡ 0 = gotEndOfRequest
+    | otherwise   = gotBody
+    where
+      gotBody ∷ IO ()
+      gotBody
+          = do let bytesToRead     = min wanted bodyLen
+                   (block, input') = Lazy.splitAt (fromIntegral bytesToRead) input
+                   block'          = Strict.concat $ Lazy.toChunks block
+                   actualReadBytes = Strict.length block'
+                   bodyLen'        = bodyLen - actualReadBytes
+               atomically $ putTMVar itrReceivedBody block'
+               waitForReceiveNonChunkedBodyReq ctx itr input' bodyLen'
+
+      gotEndOfRequest ∷ IO ()
+      gotEndOfRequest
+          = do atomically $ putTMVar itrReceivedBody (∅)
+               acceptRequest ctx input
+
+enqueue ∷ HandleLike h ⇒ Context h → Interaction → STM ()
+enqueue (Context {..}) itr
+    = do queue ← readTVar cQueue
+         writeTVar cQueue (itr ⊲ queue)