X-Git-Url: http://git.cielonegro.org/gitweb.cgi?a=blobdiff_plain;f=Network%2FHTTP%2FLucu%2FResponseWriter.hs;h=738207183ef8a04c387859dfdb1d16737b42d384;hb=cc55fb9a095c9c583ed6fe2ded3eaf6401fb760f;hp=71309746f07e9fb294e9bcc22faaec8ef47dbc54;hpb=b340a77fa7bd051dd13a41d0a5b1ad30220bc6b6;p=Lucu.git diff --git a/Network/HTTP/Lucu/ResponseWriter.hs b/Network/HTTP/Lucu/ResponseWriter.hs index 7130974..7382071 100644 --- a/Network/HTTP/Lucu/ResponseWriter.hs +++ b/Network/HTTP/Lucu/ResponseWriter.hs @@ -1,162 +1,174 @@ --- #hide +{-# LANGUAGE + DoAndIfThenElse + , RecordWildCards + , ScopedTypeVariables + , UnicodeSyntax + #-} module Network.HTTP.Lucu.ResponseWriter ( responseWriter ) where - -import qualified Data.ByteString.Lazy.Char8 as B -import Data.ByteString.Lazy.Char8 (ByteString) +import qualified Blaze.ByteString.Builder.HTTP as BB +import qualified Data.Ascii as A +import Control.Concurrent import Control.Concurrent.STM import Control.Exception import Control.Monad -import Data.Maybe +import Data.Monoid.Unicode import qualified Data.Sequence as S -import Data.Sequence (Seq, ViewR(..)) +import Data.Sequence (ViewR(..)) import Network.HTTP.Lucu.Config +import Network.HTTP.Lucu.HandleLike import Network.HTTP.Lucu.HttpVersion import Network.HTTP.Lucu.Interaction import Network.HTTP.Lucu.Postprocess import Network.HTTP.Lucu.Response import Prelude hiding (catch) -import System.IO -import Text.Printf - -import Control.Concurrent -import Debug.Trace -import GHC.Conc (unsafeIOToSTM) +import Prelude.Unicode +import System.IO (hPutStrLn, stderr) +data Context h + = Context { + cConfig ∷ !Config + , cHandle ∷ !h + , cQueue ∷ !InteractionQueue + , cReader ∷ !ThreadId + } -responseWriter :: Config -> Handle -> InteractionQueue -> ThreadId -> IO () +responseWriter ∷ HandleLike h ⇒ Config → h → InteractionQueue → ThreadId → IO () responseWriter cnf h tQueue readerTID - = catch awaitSomethingToWrite $ \ exc -> - case exc of - IOException _ -> return () - AsyncException ThreadKilled -> return () - BlockedIndefinitely -> putStrLn "requestWriter: blocked indefinitely" - _ -> print exc - where - awaitSomethingToWrite :: IO () - awaitSomethingToWrite - = do action - <- atomically $ - do -- キューが空でなくなるまで待つ - queue <- readTVar tQueue - when (S.null queue) - retry - let _ :> itr = S.viewr queue - - -- GettingBody 状態にあり、Continue が期待され - -- てゐて、それがまだ送信前なのであれば、 - -- Continue を送信する。 - state <- readItr itr itrState id + = awaitSomethingToWrite (Context cnf h tQueue readerTID) + `catches` + [ Handler $ \ (_ ∷ IOException) → return () + , Handler $ \ e → case e of + ThreadKilled → return () + _ → hPutStrLn stderr (show e) + , Handler $ \ BlockedIndefinitelyOnSTM → hPutStrLn stderr "requestWriter: blocked indefinitely" + , Handler $ \ (e ∷ SomeException) → hPutStrLn stderr (show e) + ] - if state == GettingBody then - writeContinueIfNecessary itr - else - if state >= DecidingBody then - writeHeaderOrBodyIfNecessary itr +awaitSomethingToWrite ∷ HandleLike h ⇒ Context h → IO () +awaitSomethingToWrite ctx@(Context {..}) + = join $ + atomically $ + -- キューが空でなくなるまで待つ + do queue ← readTVar cQueue + -- GettingBody 状態にあり、Continue が期待されてゐて、それがま + -- だ送信前なのであれば、Continue を送信する。 + case S.viewr queue of + EmptyR → retry + _ :> itr → do state ← readItr itrState itr + if state ≡ GettingBody then + writeContinueIfNeeded ctx itr + else + if state ≥ DecidingBody then + writeHeaderOrBodyIfNeeded ctx itr else retry - action - - writeContinueIfNecessary :: Interaction -> STM (IO ()) - writeContinueIfNecessary itr - = do expectedContinue <- readItr itr itrExpectedContinue id - if expectedContinue then - do wroteContinue <- readItr itr itrWroteContinue id - if wroteContinue then - -- 既に Continue を書込み濟 - retry - else - do reqBodyWanted <- readItr itr itrReqBodyWanted id - if reqBodyWanted /= Nothing then - return $ writeContinue itr - else - retry - else - retry - - writeHeaderOrBodyIfNecessary :: Interaction -> STM (IO ()) - writeHeaderOrBodyIfNecessary itr - -- DecidingBody 以降の状態にあり、まだヘッダを出力する前であ - -- れば、ヘッダを出力する。ヘッダ出力後であり、bodyToSend が - -- 空でなければ、それを出力する。空である時は、もし状態が - -- Done であれば後処理をする。 - = do wroteHeader <- readItr itr itrWroteHeader id - - if not wroteHeader then - return $ writeHeader itr - else - do bodyToSend <- readItr itr itrBodyToSend id - if B.null bodyToSend then - do state <- readItr itr itrState id - - if state == Done then - return $ finalize itr - else - retry - else - return $ writeBodyChunk itr +writeContinueIfNeeded ∷ HandleLike h ⇒ Context h → Interaction → STM (IO ()) +writeContinueIfNeeded ctx itr + = do expectedContinue ← readItr itrExpectedContinue itr + if expectedContinue then + do wroteContinue ← readItr itrWroteContinue itr + if wroteContinue then + -- 既に Continue を書込み濟 + retry + else + do reqBodyWanted ← readItr itrReqBodyWanted itr + if reqBodyWanted ≢ Nothing then + return $ writeContinue ctx itr + else + retry + else + retry - writeContinue :: Interaction -> IO () - writeContinue itr - = do let cont = Response { - resVersion = HttpVersion 1 1 - , resStatus = Continue - , resHeaders = [] - } - cont' <- completeUnconditionalHeaders cnf cont - hPutResponse h cont' - hFlush h - atomically $ writeItr itr itrWroteContinue True - awaitSomethingToWrite +-- DecidingBody 以降の状態にあり、まだヘッダを出力する前であれば、ヘッ +-- ダを出力する。ヘッダ出力後であり、bodyToSend が空でなければ、それを +-- 出力する。空である時は、もし状態がDone であれば後処理をする。 +writeHeaderOrBodyIfNeeded ∷ HandleLike h ⇒ Context h → Interaction → STM (IO ()) +writeHeaderOrBodyIfNeeded ctx itr + = do wroteHeader ← readItr itrWroteHeader itr + if not wroteHeader then + return $ writeHeader ctx itr + else + do noBodyToWrite ← isEmptyTMVar (itrBodyToSend itr) + if noBodyToWrite then + do state ← readItr itrState itr + if state ≡ Done then + return $ finalize ctx itr + else + retry + else + return $ writeBodyChunk ctx itr - writeHeader :: Interaction -> IO () - writeHeader itr - = do res <- atomically $ do writeItr itr itrWroteHeader True - readItr itr itrResponse id - hPutResponse h (fromJust res) - hFlush h - awaitSomethingToWrite - - writeBodyChunk :: Interaction -> IO () - writeBodyChunk itr - = do willDiscardBody <- atomically $ readItr itr itrWillDiscardBody id - willChunkBody <- atomically $ readItr itr itrWillChunkBody id - chunk <- atomically $ do chunk <- readItr itr itrBodyToSend id - writeItr itr itrBodyToSend B.empty - return chunk - unless willDiscardBody - $ do if willChunkBody then - do hPrintf h "%x\r\n" (toInteger $ B.length chunk) - B.hPut h chunk - hPutStr h "\r\n" - else - B.hPut h chunk - hFlush h - awaitSomethingToWrite +writeContinue ∷ HandleLike h ⇒ Context h → Interaction → IO () +writeContinue ctx@(Context {..}) itr + = do let cont = Response { + resVersion = HttpVersion 1 1 + , resStatus = Continue + , resHeaders = (∅) + } + cont' ← completeUnconditionalHeaders cConfig cont + hPutBuilder cHandle $ A.toBuilder $ printResponse cont' + hFlush cHandle + atomically $ writeItr itrWroteContinue True itr + awaitSomethingToWrite ctx - finishBodyChunk :: Interaction -> IO () - finishBodyChunk itr - = do willDiscardBody <- atomically $ readItr itr itrWillDiscardBody id - willChunkBody <- atomically $ readItr itr itrWillChunkBody id - when (not willDiscardBody && willChunkBody) - $ hPutStr h "0\r\n\r\n" >> hFlush h +writeHeader ∷ HandleLike h ⇒ Context h → Interaction → IO () +writeHeader ctx@(Context {..}) itr + = do res ← atomically + $ do writeItr itrWroteHeader True itr + readItr itrResponse itr + hPutBuilder cHandle $ A.toBuilder $ printResponse res + hFlush cHandle + awaitSomethingToWrite ctx - finalize :: Interaction -> IO () - finalize itr - = do finishBodyChunk itr - willClose <- atomically $ do queue <- readTVar tQueue +writeBodyChunk ∷ HandleLike h ⇒ Context h → Interaction → IO () +writeBodyChunk ctx@(Context {..}) itr + = join $ + atomically $ + do willDiscardBody ← readItr itrWillDiscardBody itr + if willDiscardBody then + do _ ← tryTakeTMVar (itrBodyToSend itr) + return $ awaitSomethingToWrite ctx + else + do willChunkBody ← readItr itrWillChunkBody itr + chunk ← takeTMVar (itrBodyToSend itr) + return $ + do if willChunkBody then + hPutBuilder cHandle $ BB.chunkedTransferEncoding chunk + else + hPutBuilder cHandle chunk + hFlush cHandle + awaitSomethingToWrite ctx - let (remaining :> _) = S.viewr queue - writeTVar tQueue remaining +finishBodyChunk ∷ HandleLike h ⇒ Context h → Interaction → IO () +finishBodyChunk (Context {..}) itr + = join $ + atomically $ + do willDiscardBody ← readItr itrWillDiscardBody itr + willChunkBody ← readItr itrWillChunkBody itr + if ((¬) willDiscardBody) ∧ willChunkBody then + return $ + do hPutBuilder cHandle BB.chunkedTransferTerminator + hFlush cHandle + else + return $ return () - readItr itr itrWillClose id - if willClose then - -- reader は恐らく hWaitForInput してゐる最中なので、 - -- スレッドを豫め殺して置かないとをかしくなる。 - do killThread readerTID - hClose h - else - awaitSomethingToWrite +finalize ∷ HandleLike h ⇒ Context h → Interaction → IO () +finalize ctx@(Context {..}) itr + = do finishBodyChunk ctx itr + willClose ← atomically $ + do queue ← readTVar cQueue + case S.viewr queue of + EmptyR → return () -- this should never happen + remaining :> _ → writeTVar cQueue remaining + readItr itrWillClose itr + if willClose then + -- reader は恐らく hWaitForInput してゐる最中なので、スレッ + -- ドを豫め殺して置かないとをかしくなる。 + do killThread cReader + hClose cHandle + else + awaitSomethingToWrite ctx