X-Git-Url: http://git.cielonegro.org/gitweb.cgi?a=blobdiff_plain;f=Network%2FHTTP%2FLucu%2FPostprocess.hs;h=131cc8ebb3e65f7426a3bf245cc14185a1502795;hb=1f0a19c;hp=929413cd0ce86a19ce389067f98a8e61265aaa0b;hpb=a9e9f50818285bf66cd64e5a248175eecb8e1fea;p=Lucu.git diff --git a/Network/HTTP/Lucu/Postprocess.hs b/Network/HTTP/Lucu/Postprocess.hs index 929413c..131cc8e 100644 --- a/Network/HTTP/Lucu/Postprocess.hs +++ b/Network/HTTP/Lucu/Postprocess.hs @@ -1,150 +1,161 @@ +{-# LANGUAGE + DoAndIfThenElse + , OverloadedStrings + , RecordWildCards + , UnicodeSyntax + #-} module Network.HTTP.Lucu.Postprocess - ( postprocess -- Interaction -> STM () - , completeUnconditionalHeaders -- Response -> IO Response + ( postprocess + , completeUnconditionalHeaders ) where - -import Control.Concurrent.STM -import Control.Monad -import Data.Char -import Data.Maybe -import GHC.Conc (unsafeIOToSTM) -import Network.HTTP.Lucu.Headers -import Network.HTTP.Lucu.HttpVersion -import Network.HTTP.Lucu.Interaction -import Network.HTTP.Lucu.RFC1123DateTime -import Network.HTTP.Lucu.Request -import Network.HTTP.Lucu.Response -import Network.HTTP.Lucu.Utils -import System.Time - -{- - - * Response が未設定なら、HTTP/1.1 500 Internal Server Error にする。 - - * Content-Length があれば、それを削除する。 - - * HTTP/1.1 であり、且つ body を持つ事が出來る時、Transfer-Encoding の - 最後の要素が chunked でなければ 500 Internal Error にする。 - Transfer-Encoding が未設定であれば、chunked に設定する。 - - * HTTP/1.0 なのに Transfer-Encoding が設定濟なら 500 Internal Server - Error にする。但し identity だけは許す。 - - * body を持つ事が出來る時、Content-Type が無ければ - application/octet-stream にする。出來ない時、HEAD でなければ - Content-Type を削除する。 - - * body を持つ事が出來ない時、body 破棄フラグを立てる。 - - * Connection: close が設定されてゐる時、切斷フラグを立てる。 - - * 切斷フラグが立ってゐる時、Connection: close を設定する。 - - * Server が無ければ設定。 - - * Date が無ければ設定。 - --} - -{- Postprocess は body を補完した後で實行する事 -} - -postprocess :: Interaction -> STM () -postprocess itr - = do res <- readItr itr itrResponse id - - when (res == Nothing) - $ setStatus itr InternalServerError - - when (itrRequest itr /= Nothing) - $ relyOnRequest itr - - do oldRes <- readItr itr itrResponse id - newRes <- unsafeIOToSTM $ completeUnconditionalHeaders $ fromJust oldRes - writeItr itr itrResponse $ Just newRes +import Control.Applicative +import Control.Concurrent.STM +import Control.Monad +import Control.Monad.Unicode +import Data.Ascii (Ascii, CIAscii, AsciiBuilder) +import qualified Data.Ascii as A +import Data.Monoid.Unicode +import Data.Time +import qualified Data.Time.HTTP as HTTP +import GHC.Conc (unsafeIOToSTM) +import Network.HTTP.Lucu.Abortion +import Network.HTTP.Lucu.Config +import Network.HTTP.Lucu.DefaultPage +import Network.HTTP.Lucu.Headers +import Network.HTTP.Lucu.HttpVersion +import Network.HTTP.Lucu.Interaction +import Network.HTTP.Lucu.Request +import Network.HTTP.Lucu.Response +import Prelude.Unicode + +postprocess ∷ Interaction → STM () +postprocess itr@(Interaction {..}) + = do abortOnCertainConditions itr + + case itrRequest of + Just req → postprocessWithRequest itr req + Nothing → return () + + updateResIO itr $ completeUnconditionalHeaders itrConfig + +abortOnCertainConditions ∷ Interaction → STM () +abortOnCertainConditions (Interaction {..}) + = readTVar itrResponse ≫= go where - relyOnRequest itr - = do status <- readItr itr itrResponse (resStatus . fromJust) - - let req = fromJust $ itrRequest itr - reqVer = reqVersion req - canHaveBody = if reqMethod req == HEAD then - False - else - not (isInformational status || - status == NoContent || - status == ResetContent || - status == NotModified ) - - updateRes itr $ deleteHeader "Content-Length" - - if canHaveBody then - do teM <- readHeader itr "Transfer-Encoding" - if reqVer == HttpVersion 1 1 then - - do case teM of - Nothing -> updateRes itr $ setHeader "Transfer-Encoding" "chunked" - Just te -> let teList = [trim isWhiteSpace x - | x <- splitBy (== ',') (map toLower te)] - in - when (teList == [] || last teList /= "chunked") - $ setStatus itr InternalServerError - - writeItr itr itrWillChunkBody True - else - case fmap (map toLower) teM of - Nothing -> return () - Just "identity" -> return () - _ -> setStatus itr InternalServerError - - cType <- readHeader itr "Content-Type" - when (cType == Nothing) - $ updateRes itr $ setHeader "Content-Type" "application/octet-stream" - else - -- body 関連のヘッダを削除。但し HEAD なら Content-* は殘す - do updateRes itr $ deleteHeader "Transfer-Encoding" - when (reqMethod req /= HEAD) - $ updateRes itr $ deleteHeader "Content-Type" - - conn <- readHeader itr "Connection" - case fmap (map toLower) conn of - Just "close" -> writeItr itr itrWillClose True - _ -> updateRes itr $ setHeader "Connection" "close" - - when (reqMethod req == HEAD || not canHaveBody) - $ writeTVar (itrWillDiscardBody itr) True - - setStatus :: Interaction -> StatusCode -> STM () - setStatus itr status - = writeTVar (itrResponse itr) (Just $ Response { - resVersion = HttpVersion 1 1 - , resStatus = status - , resHeaders = [] - }) - - readHeader :: Interaction -> String -> STM (Maybe String) - readHeader itr name - = do valueMM <- readItrF itr itrResponse $ getHeader name - case valueMM of - Just (Just val) -> return $ Just val - _ -> return Nothing - - updateRes :: Interaction -> (Response -> Response) -> STM () - updateRes itr updator - = updateItrF itr itrResponse updator - - -completeUnconditionalHeaders :: Response -> IO Response -completeUnconditionalHeaders res - = return res >>= compServer >>= compDate >>= return + go ∷ Response → STM () + go res@(Response {..}) + = do unless (any (\ p → p resStatus) [ isSuccessful + , isRedirection + , isError + ]) + $ abort' + $ A.toAsciiBuilder "Inappropriate status code for a response: " + ⊕ printStatusCode resStatus + + when ( resStatus ≡ MethodNotAllowed ∧ + hasHeader "Allow" res ) + $ abort' + $ A.toAsciiBuilder "The status was " + ⊕ printStatusCode resStatus + ⊕ A.toAsciiBuilder " but no \"Allow\" header." + + when ( resStatus ≢ NotModified ∧ + isRedirection resStatus ∧ + hasHeader "Location" res ) + $ abort' + $ A.toAsciiBuilder "The status code was " + ⊕ printStatusCode resStatus + ⊕ A.toAsciiBuilder " but no Location header." + + abort' ∷ AsciiBuilder → STM () + abort' = abortSTM InternalServerError [] + ∘ Just + ∘ A.toText + ∘ A.fromAsciiBuilder + +postprocessWithRequest ∷ Interaction → Request → STM () +postprocessWithRequest itr@(Interaction {..}) (Request {..}) + = do willDiscardBody ← readTVar itrWillDiscardBody + canHaveBody ← if willDiscardBody then + return False + else + resCanHaveBody <$> readTVar itrResponse + + updateRes itr + $ deleteHeader "Content-Length" + ∘ deleteHeader "Transfer-Encoding" + + if canHaveBody then + do when (reqVersion ≡ HttpVersion 1 1) + $ do writeHeader itr "Transfer-Encoding" (Just "chunked") + writeTVar itrWillChunkBody True + writeDefaultPageIfNeeded itr + else + do writeTVar itrWillDiscardBody True + -- These headers make sense for HEAD requests even + -- when there won't be a response entity body. + when (reqMethod ≢ HEAD) + $ updateRes itr + $ deleteHeader "Content-Type" + ∘ deleteHeader "Etag" + ∘ deleteHeader "Last-Modified" + + hasConnClose ← (≡ Just "close") <$> readCIHeader itr "Connection" + willClose ← readTVar itrWillClose + when (hasConnClose ∧ (¬) willClose) + $ writeTVar itrWillClose True + when ((¬) hasConnClose ∧ willClose) + $ writeHeader itr "Connection" (Just "close") + +writeDefaultPageIfNeeded ∷ Interaction → STM () +writeDefaultPageIfNeeded itr@(Interaction {..}) + = do resHasCType ← readTVar itrResponseHasCType + unless resHasCType + $ do writeHeader itr "Content-Type" (Just defaultPageContentType) + writeHeader itr "Content-Encoding" Nothing + res ← readTVar itrResponse + let page = getDefaultPage itrConfig itrRequest res + putTMVar itrBodyToSend page + +writeHeader ∷ Interaction → CIAscii → Maybe Ascii → STM () +{-# INLINE writeHeader #-} +writeHeader itr k v + = case v of + Just v' → updateRes itr $ setHeader k v' + Nothing → updateRes itr $ deleteHeader k + +readCIHeader ∷ Interaction → CIAscii → STM (Maybe CIAscii) +{-# INLINE readCIHeader #-} +readCIHeader (Interaction {..}) k + = getCIHeader k <$> readTVar itrResponse + +updateRes ∷ Interaction → (Response → Response) → STM () +{-# INLINE updateRes #-} +updateRes (Interaction {..}) f + = do old ← readTVar itrResponse + writeTVar itrResponse (f old) + +updateResIO ∷ Interaction → (Response → IO Response) → STM () +{-# INLINE updateResIO #-} +updateResIO (Interaction {..}) f + = do old ← readTVar itrResponse + new ← unsafeIOToSTM $ f old + writeTVar itrResponse new + +completeUnconditionalHeaders ∷ Config → Response → IO Response +completeUnconditionalHeaders conf = (compDate =≪) ∘ compServer where - compServer res - = case getHeader "Server" res of - Nothing -> return $ addHeader "Server" "Lucu/1.0" res - Just _ -> return res - - compDate res - = case getHeader "Date" res of - Nothing -> do time <- getClockTime - return $ addHeader "Date" (formatHTTPDateTime time) res - Just _ -> return res \ No newline at end of file + compServer res' + = case getHeader "Server" res' of + Nothing → return $ setHeader "Server" (cnfServerSoftware conf) res' + Just _ → return res' + + compDate res' + = case getHeader "Date" res' of + Nothing → do date ← getCurrentDate + return $ setHeader "Date" date res' + Just _ → return res' + +getCurrentDate ∷ IO Ascii +getCurrentDate = HTTP.toAscii <$> getCurrentTime