X-Git-Url: http://git.cielonegro.org/gitweb.cgi?a=blobdiff_plain;f=Network%2FHTTP%2FLucu%2FPostprocess.hs;h=0bd33ed1b00fc17b8f27a260cb57f88743e5972c;hb=37b2602949bdb71894852304753f3aab34de191b;hp=b7b910f3142ed54caa0078e2a78a35105ec9fe36;hpb=3c7a58ab749a55a30466a033b170536bcdf18b98;p=Lucu.git diff --git a/Network/HTTP/Lucu/Postprocess.hs b/Network/HTTP/Lucu/Postprocess.hs index b7b910f..0bd33ed 100644 --- a/Network/HTTP/Lucu/Postprocess.hs +++ b/Network/HTTP/Lucu/Postprocess.hs @@ -1,39 +1,47 @@ 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 qualified Data.ByteString as Strict (ByteString) +import qualified Data.ByteString.Char8 as C8 hiding (ByteString) +import Data.IORef import Data.Maybe +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.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 +import System.IO.Unsafe {- - * Response が未設定なら、HTTP/1.1 500 Internal Server Error にする。 + * Response が未設定なら、200 OK にする。 - * Content-Length があれば、それを削除する。 + * ステータスが 2xx, 3xx, 4xx, 5xx のいずれでもなければ 500 にする。 - * HTTP/1.1 であり、且つ body を持つ事が出來る時、Transfer-Encoding の - 最後の要素が chunked でなければ 500 Internal Error にする。 - Transfer-Encoding が未設定であれば、chunked に設定する。 + * 405 Method Not Allowed なのに Allow ヘッダが無ければ 500 にする。 - * HTTP/1.0 なのに Transfer-Encoding が設定濟なら 500 Internal Server - Error にする。但し identity だけは許す。 + * 304 Not Modified 以外の 3xx なのに Location ヘッダが無ければ 500 に + する。 - * body を持つ事が出來る時、Content-Type が無ければ - application/octet-stream にする。出來ない時、HEAD でなければ - Content-Type を削除する。 + * Content-Length があれば、それを削除する。Transfer-Encoding があって + も削除する。 + + * HTTP/1.1 であり、body を持つ事が出來る時、Transfer-Encoding を + chunked に設定する。 + + * body を持つ事が出來る時、Content-Type が無ければデフォルト値にする。 + 出來ない時、HEAD でなければContent-Type, Etag, Last-Modified を削除 + する。 * body を持つ事が出來ない時、body 破棄フラグを立てる。 @@ -47,93 +55,122 @@ import System.Time -} -{- Postprocess は body を補完した後で實行する事 -} - postprocess :: Interaction -> STM () -postprocess itr - = do res <- readTVar (itrResponse itr) - - when (res == Nothing) - $ setStatus itr InternalServerError - - when (itrRequest itr /= Nothing) - $ relyOnRequest itr - - do oldRes <- readTVar (itrResponse itr) - newRes <- unsafeIOToSTM $ completeUnconditionalHeaders $ fromJust oldRes - setRes itr newRes +postprocess !itr + = do reqM <- readItr itr itrRequest id + res <- readItr itr itrResponse id + let sc = resStatus res + + unless (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 (C8.pack "Allow") res == Nothing) + $ abortSTM InternalServerError [] + $ Just ("The status was " ++ show sc ++ " but no Allow header.") + + when (sc /= NotModified && isRedirection sc && getHeader (C8.pack "Location") res == Nothing) + $ abortSTM InternalServerError [] + $ Just ("The status code was " ++ show sc ++ " but no Location header.") + + when (reqM /= Nothing) relyOnRequest + + -- itrResponse の内容は relyOnRequest によって變へられてゐる可 + -- 能性が高い。 + do oldRes <- readItr itr itrResponse id + newRes <- unsafeIOToSTM + $ completeUnconditionalHeaders (itrConfig itr) oldRes + writeItr itr itrResponse newRes where - relyOnRequest itr - = do resM <- readTVar (itrResponse itr) + relyOnRequest :: STM () + relyOnRequest + = do status <- readItr itr itrResponse resStatus + req <- readItr itr itrRequest fromJust - let req = fromJust $ itrRequest itr - reqVer = reqVersion req - res = fromJust resM - status = resStatus res + let reqVer = reqVersion req canHaveBody = if reqMethod req == HEAD then False else - isInformational status || - status == NoContent || - status == ResetContent || - status == NotModified + not (isInformational status || + status == NoContent || + status == ResetContent || + status == NotModified ) + + updateRes $! deleteHeader (C8.pack "Content-Length") + updateRes $! deleteHeader (C8.pack "Transfer-Encoding") - setRes itr (deleteHeader res "Content-Length") + cType <- readHeader (C8.pack "Content-Type") + when (cType == Nothing) + $ updateRes $ setHeader (C8.pack "Content-Type") defaultPageContentType if canHaveBody then - do if reqVer == HttpVersion 1 1 then - - case getHeader res "Transfer-Encoding" of - Nothing -> setRes itr (setHeader res "Transfer-Encoding" "chunked") - Just te -> let teList = [trim isWhiteSpace x - | x <- splitBy (== ',') (map toLower te)] - in - when (teList == [] || last teList /= "chunked") - $ setStatus itr InternalServerError - else - case getHeader res "Transfer-Encoding" of - Nothing -> return () - Just "identity" -> return () - _ -> setStatus itr InternalServerError - - when (getHeader res "Content-Type" == Nothing) - $ setRes itr (setHeader res "Content-Type" "application/octet-stream") + when (reqVer == HttpVersion 1 1) + $ do updateRes $! setHeader (C8.pack "Transfer-Encoding") (C8.pack "chunked") + writeItr itr itrWillChunkBody True else -- body 関連のヘッダを削除。但し HEAD なら Content-* は殘す - do setRes itr (deleteHeader res "Transfer-Encoding") - when (reqMethod req /= HEAD) - $ setRes itr (deleteHeader res "Content-Type") + when (reqMethod req /= HEAD) + $ do updateRes $! deleteHeader (C8.pack "Content-Type") + updateRes $! deleteHeader (C8.pack "Etag") + updateRes $! deleteHeader (C8.pack "Last-Modified") - if fmap (map toLower) (getHeader res "Connection") == Just "close" then - writeTVar (itrWillClose itr) True - else - setRes itr (setHeader res "Connection" "close") + conn <- readHeader (C8.pack "Connection") + case conn of + Nothing -> return () + Just value -> when (value `noCaseEq` C8.pack "close") + $ writeItr itr itrWillClose True + + willClose <- readItr itr itrWillClose id + when willClose + $ updateRes $! setHeader (C8.pack "Connection") (C8.pack "close") when (reqMethod req == HEAD || not canHaveBody) $ writeTVar (itrWillDiscardBody itr) True - setStatus itr status - = writeTVar (itrResponse itr) (Just $ Response { - resVersion = HttpVersion 1 1 - , resStatus = status - , resHeaders = [] - }) + readHeader :: Strict.ByteString -> STM (Maybe Strict.ByteString) + readHeader !name + = readItr itr itrResponse $ getHeader name - setRes itr res - = writeTVar (itrResponse itr) (Just res) + updateRes :: (Response -> Response) -> STM () + updateRes !updator + = updateItr itr itrResponse updator -completeUnconditionalHeaders :: Response -> IO Response -completeUnconditionalHeaders res - = return res >>= compServer >>= compDate >>= return +completeUnconditionalHeaders :: Config -> Response -> IO Response +completeUnconditionalHeaders !conf !res + = compServer res >>= compDate where - compServer res - = case getHeader res "Server" of - Nothing -> return $ addHeader res "Server" "Lucu/1.0" - Just _ -> return res - - compDate res - = case getHeader res "Date" of - Nothing -> do time <- getClockTime - return $ addHeader res "Date" $ formatHTTPDateTime time - Just _ -> return res \ No newline at end of file + compServer res' + = case getHeader (C8.pack "Server") res' of + Nothing -> return $ setHeader (C8.pack "Server") (cnfServerSoftware conf) res' + Just _ -> return res' + + compDate res' + = case getHeader (C8.pack "Date") res' of + Nothing -> do date <- getCurrentDate + return $ setHeader (C8.pack "Date") date res' + Just _ -> return res' + + +cache :: IORef (UTCTime, Strict.ByteString) +cache = unsafePerformIO $ + newIORef (UTCTime (ModifiedJulianDay 0) 0, undefined) +{-# NOINLINE cache #-} + +getCurrentDate :: IO Strict.ByteString +getCurrentDate = do now <- getCurrentTime + (cachedTime, cachedStr) <- readIORef cache + + if now `mostlyEq` cachedTime then + return cachedStr + else + do let dateStr = C8.pack $ HTTP.format now + writeIORef cache (now, dateStr) + return dateStr + where + mostlyEq :: UTCTime -> UTCTime -> Bool + mostlyEq a b + = (utctDay a == utctDay b) + && + (fromEnum (utctDayTime a) == fromEnum (utctDayTime b))