X-Git-Url: http://git.cielonegro.org/gitweb.cgi?p=Lucu.git;a=blobdiff_plain;f=Network%2FHTTP%2FLucu%2FPostprocess.hs;h=7157b7d56e9dd14c4dcaa635ce47be599d2d15f6;hp=124b66bd2ba3e4c4a4ac9aef92c4f9c76e0957ef;hb=246d66d6d3130e03834a6c3badc38711a1879aae;hpb=34b0ad7f2ffc5e7ca67fa3468d01c5551edc8a8b diff --git a/Network/HTTP/Lucu/Postprocess.hs b/Network/HTTP/Lucu/Postprocess.hs index 124b66b..7157b7d 100644 --- a/Network/HTTP/Lucu/Postprocess.hs +++ b/Network/HTTP/Lucu/Postprocess.hs @@ -1,171 +1,140 @@ --- #hide +{-# LANGUAGE + DoAndIfThenElse + , OverloadedStrings + , RecordWildCards + , UnicodeSyntax + #-} module Network.HTTP.Lucu.Postprocess ( postprocess - , completeUnconditionalHeaders ) where - -import Control.Concurrent.STM -import Control.Monad -import Data.Char -import Data.Maybe -import GHC.Conc (unsafeIOToSTM) -import Network.HTTP.Lucu.Abortion -import Network.HTTP.Lucu.Config -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 が未設定なら、200 OK にする。 - - * ステータスが 2xx, 3xx, 4xx, 5xx のいずれでもなければ 500 にする。 - - * 405 Method Not Allowed なのに Allow ヘッダが無ければ 500 にする。 - - * 304 Not Modified 以外の 3xx なのに Location ヘッダが無ければ 500 に - する。 - - * 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 が無ければデフォルト値にする。 - 出來ない時、HEAD でなければContent-Type, Etag, Last-Modified を削除 - する。 - - * body を持つ事が出來ない時、body 破棄フラグを立てる。 - - * Connection: close が設定されてゐる時、切斷フラグを立てる。 - - * 切斷フラグが立ってゐる時、Connection: close を設定する。 - - * Server が無ければ設定。 - - * Date が無ければ設定。 - --} - -postprocess :: Interaction -> STM () -postprocess itr - = do res <- readItr itr itrResponse id - let sc = resStatus res - - when (not $ any (\ p -> p sc) [isSuccessful, isRedirection, isError]) - $ abortSTM InternalServerError [] - $ Just ("The status code is not good for a final status: " - ++ show sc) - - when (sc == MethodNotAllowed && getHeader "Allow" res == Nothing) - $ abortSTM InternalServerError [] - $ Just ("The status was " ++ show sc ++ " but no Allow header.") - - when (sc /= NotModified && isRedirection sc && getHeader "Location" res == Nothing) - $ abortSTM InternalServerError [] - $ Just ("The status code was " ++ show sc ++ " but no Location header.") - - when (itrRequest itr /= Nothing) - $ relyOnRequest itr - - do newRes <- unsafeIOToSTM - $ completeUnconditionalHeaders (itrConfig itr) res - writeItr itr itrResponse newRes +import Control.Applicative +import Control.Concurrent.STM +import Control.Monad +import Control.Monad.Unicode +import Data.Ascii (Ascii, CIAscii, AsciiBuilder) +import Data.Convertible.Base +import Data.Maybe +import Data.Monoid.Unicode +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.Interaction +import Network.HTTP.Lucu.Request +import Network.HTTP.Lucu.Response +import Prelude.Unicode + +postprocess ∷ NormalInteraction → STM () +postprocess ni@(NI {..}) + = do void $ tryPutTMVar niSendContinue False + abortOnCertainConditions ni + postprocessWithRequest ni + completeUnconditionalHeaders ni + +abortOnCertainConditions ∷ NormalInteraction → STM () +abortOnCertainConditions (NI {..}) + = readTVar niResponse ≫= go where - relyOnRequest :: Interaction -> STM () - relyOnRequest itr - = do status <- readItr itr itrResponse resStatus - - 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" - - cType <- readHeader itr "Content-Type" - when (cType == Nothing) - $ updateRes itr $ setHeader "Content-Type" defaultPageContentType - - 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") - $ abortSTM InternalServerError [] - $ Just ("Transfer-Encoding must end with `chunked' " - ++ "because this is an HTTP/1.1 request: " - ++ te) - - writeItr itr itrWillChunkBody True - else - case fmap (map toLower) teM of - Nothing -> return () - Just "identity" -> return () - Just te -> abortSTM InternalServerError [] - $ Just ("Transfer-Encoding must be `identity' because " - ++ "this is an HTTP/1.0 request: " - ++ te) - else - -- body 関連のヘッダを削除。但し HEAD なら Content-* は殘す - do updateRes itr $ deleteHeader "Transfer-Encoding" - when (reqMethod req /= HEAD) - $ do updateRes itr $ deleteHeader "Content-Type" - updateRes itr $ deleteHeader "Etag" - updateRes itr $ deleteHeader "Last-Modified" - - conn <- readHeader itr "Connection" - case fmap (map toLower) conn of - Just "close" -> writeItr itr itrWillClose True - _ -> return () - - willClose <- readItr itr itrWillClose id - when willClose - $ updateRes itr $ setHeader "Connection" "close" - - when (reqMethod req == HEAD || not canHaveBody) - $ writeTVar (itrWillDiscardBody itr) True - - readHeader :: Interaction -> String -> STM (Maybe String) - readHeader itr name - = readItr itr itrResponse $ getHeader name - - updateRes :: Interaction -> (Response -> Response) -> STM () - updateRes itr updator - = updateItr itr itrResponse updator - - -completeUnconditionalHeaders :: Config -> Response -> IO Response -completeUnconditionalHeaders conf res - = return res >>= compServer >>= compDate >>= return - where - compServer res - = case getHeader "Server" res of - Nothing -> return $ addHeader "Server" (cnfServerSoftware conf) 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 + go ∷ Response → STM () + go res@(Response {..}) + = do unless (any (\ p → p resStatus) [ isSuccessful + , isRedirection + , isError + ]) + $ abort' + $ cs ("Inappropriate status code for a response: " ∷ Ascii) + ⊕ cs resStatus + + when ( resStatus ≈ MethodNotAllowed ∧ + hasHeader "Allow" res ) + $ abort' + $ cs ("The status was " ∷ Ascii) + ⊕ cs resStatus + ⊕ cs (" but no \"Allow\" header." ∷ Ascii) + + when ( resStatus ≉ NotModified ∧ + isRedirection resStatus ∧ + hasHeader "Location" res ) + $ abort' + $ cs ("The status code was " ∷ Ascii) + ⊕ cs resStatus + ⊕ cs (" but no Location header." ∷ Ascii) + + abort' ∷ AsciiBuilder → STM () + abort' = throwSTM + ∘ mkAbortion' InternalServerError + ∘ cs + +postprocessWithRequest ∷ NormalInteraction → STM () +postprocessWithRequest ni@(NI {..}) + = do updateRes ni + $ deleteHeader "Content-Length" + ∘ deleteHeader "Transfer-Encoding" + + canHaveBody ← resCanHaveBody <$> readTVar niResponse + if canHaveBody then + do when niWillChunkBody + $ writeHeader ni "Transfer-Encoding" (Just "chunked") + when (reqMethod niRequest ≢ HEAD) + $ writeDefaultPageIfNeeded ni + else + -- These headers make sense for HEAD requests even when + -- there won't be a response entity body. + when (reqMethod niRequest ≢ HEAD) + $ updateRes ni + $ deleteHeader "Content-Type" + ∘ deleteHeader "Etag" + ∘ deleteHeader "Last-Modified" + + hasConnClose ← (≡ Just "close") <$> readCIHeader ni "Connection" + willClose ← readTVar niWillClose + when (hasConnClose ∧ (¬) willClose) + $ writeTVar niWillClose True + when ((¬) hasConnClose ∧ willClose) + $ writeHeader ni "Connection" (Just "close") + +writeDefaultPageIfNeeded ∷ NormalInteraction → STM () +writeDefaultPageIfNeeded ni@(NI {..}) + = do resHasCType ← readTVar niResponseHasCType + unless resHasCType + $ do writeHeader ni "Content-Type" $ Just defaultPageContentType + writeHeader ni "Content-Encoding" Nothing + res ← readTVar niResponse + let body = defaultPageForResponse niConfig (Just niRequest) res + putTMVar niBodyToSend body + +completeUnconditionalHeaders ∷ NormalInteraction → STM () +completeUnconditionalHeaders ni@(NI {..}) + = do srv ← readHeader ni "Server" + when (isNothing srv) $ + writeHeader ni "Server" $ Just $ cnfServerSoftware niConfig + + date ← readHeader ni "Date" + when (isNothing date) $ + do date' ← unsafeIOToSTM getCurrentDate + writeHeader ni "Date" $ Just date' + +writeHeader ∷ NormalInteraction → CIAscii → Maybe Ascii → STM () +{-# INLINE writeHeader #-} +writeHeader ni k v + = case v of + Just v' → updateRes ni $ setHeader k v' + Nothing → updateRes ni $ deleteHeader k + +readHeader ∷ NormalInteraction → CIAscii → STM (Maybe Ascii) +{-# INLINE readHeader #-} +readHeader (NI {..}) k + = getHeader k <$> readTVar niResponse + +readCIHeader ∷ NormalInteraction → CIAscii → STM (Maybe CIAscii) +{-# INLINE readCIHeader #-} +readCIHeader (NI {..}) k + = getCIHeader k <$> readTVar niResponse + +updateRes ∷ NormalInteraction → (Response → Response) → STM () +{-# INLINE updateRes #-} +updateRes (NI {..}) f + = do old ← readTVar niResponse + writeTVar niResponse $ f old