X-Git-Url: http://git.cielonegro.org/gitweb.cgi?p=Lucu.git;a=blobdiff_plain;f=Network%2FHTTP%2FLucu%2FPostprocess.hs;h=29c3c5167cb375accbce646038b110a4a0b0b601;hp=49c95e809be046489bed306c83db6f77eab12baf;hb=51eda5b;hpb=e34910f85f459f049b9e6e6b79db9ef95dfccc13 diff --git a/Network/HTTP/Lucu/Postprocess.hs b/Network/HTTP/Lucu/Postprocess.hs index 49c95e8..29c3c51 100644 --- a/Network/HTTP/Lucu/Postprocess.hs +++ b/Network/HTTP/Lucu/Postprocess.hs @@ -1,172 +1,146 @@ {-# LANGUAGE - BangPatterns - , DoAndIfThenElse + DoAndIfThenElse , OverloadedStrings + , RecordWildCards , UnicodeSyntax #-} module Network.HTTP.Lucu.Postprocess ( postprocess - , completeUnconditionalHeaders ) where import Control.Applicative import Control.Concurrent.STM import Control.Monad import Control.Monad.Unicode -import Data.Ascii (Ascii, CIAscii) +import Data.Ascii (Ascii, CIAscii, AsciiBuilder) import qualified Data.Ascii as A -import qualified Data.ByteString as Strict (ByteString) -import qualified Data.ByteString.Char8 as C8 hiding (ByteString) -import Data.IORef import Data.Maybe 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 -import System.IO.Unsafe -{- - - * Response が未設定なら、200 OK にする。 +postprocess ∷ NormalInteraction → STM () +postprocess ni@(NI {..}) + = do void $ tryPutTMVar niSendContinue False + abortOnCertainConditions ni + postprocessWithRequest ni + completeUnconditionalHeaders ni - * ステータスが 2xx, 3xx, 4xx, 5xx のいずれでもなければ 500 にする。 - - * 405 Method Not Allowed なのに Allow ヘッダが無ければ 500 にする。 - - * 304 Not Modified 以外の 3xx なのに Location ヘッダが無ければ 500 に - する。 - - * Content-Length があれば、それを削除する。Transfer-Encoding があって - も削除する。 - - * HTTP/1.1 であり、body を持つ事が出來る時、Transfer-Encoding を - chunked に設定する。 - - * body を持つ事が出來る時、Content-Type が無ければデフォルト値にする。 - 出來ない時、HEAD でなければContent-Type, Etag, Last-Modified を削除 - する。 - - * body を持つ事が出來ない時、body 破棄フラグを立てる。 - - * Connection: close が設定されてゐる時、切斷フラグを立てる。 - - * 切斷フラグが立ってゐる時、Connection: close を設定する。 - - * Server が無ければ設定。 - - * Date が無ければ設定。 - --} - -postprocess ∷ Interaction → STM () -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 - $ A.toText ( "The status code is not good for a final status of a response: " - ⊕ printStatusCode sc ) - - when (sc ≡ MethodNotAllowed ∧ getHeader "Allow" res ≡ Nothing) - $ abortSTM InternalServerError [] - $ Just - $ A.toText ( "The status was " - ⊕ printStatusCode sc - ⊕ " but no Allow header." ) - - when (sc /= NotModified ∧ isRedirection sc ∧ getHeader "Location" res ≡ Nothing) - $ abortSTM InternalServerError [] - $ Just - $ A.toText ( "The status code was " - ⊕ printStatusCode 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 +abortOnCertainConditions ∷ NormalInteraction → STM () +abortOnCertainConditions (NI {..}) + = readTVar niResponse ≫= go where - relyOnRequest ∷ STM () - relyOnRequest - = do status ← readItr itr itrResponse resStatus - req ← readItr itr itrRequest fromJust - - let reqVer = reqVersion req - canHaveBody = if reqMethod req ≡ HEAD then - False - else - not (isInformational status ∨ - status ≡ NoContent ∨ - status ≡ ResetContent ∨ - status ≡ NotModified ) - - updateRes $ deleteHeader "Content-Length" - updateRes $ deleteHeader "Transfer-Encoding" - - cType ← readHeader "Content-Type" - when (cType ≡ Nothing) - $ updateRes $ setHeader "Content-Type" defaultPageContentType - - if canHaveBody then - when (reqVer ≡ HttpVersion 1 1) - $ do updateRes $ setHeader "Transfer-Encoding" "chunked" - writeItr itr itrWillChunkBody True - else - -- body 関連のヘッダを削除。但し HEAD なら Content-* は殘す - when (reqMethod req /= HEAD) - $ do updateRes $ deleteHeader "Content-Type" - updateRes $ deleteHeader "Etag" - updateRes $ deleteHeader "Last-Modified" - - conn ← readHeader "Connection" - case conn of - Nothing → return () - Just value → when (A.toCIAscii value ≡ "close") - $ writeItr itr itrWillClose True - - willClose ← readItr itr itrWillClose id - when willClose - $ updateRes $ setHeader "Connection" "close" - - when (reqMethod req ≡ HEAD ∨ not canHaveBody) - $ writeTVar (itrWillDiscardBody itr) True - - readHeader ∷ CIAscii → STM (Maybe Ascii) - {-# INLINE readHeader #-} - readHeader = readItr itr itrResponse ∘ getHeader - - updateRes ∷ (Response → Response) → STM () - {-# INLINE updateRes #-} - updateRes = updateItr itr itrResponse - -completeUnconditionalHeaders ∷ Config → Response → IO Response -completeUnconditionalHeaders conf = (compDate =≪) ∘ compServer - where - 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.format <$> getCurrentTime + 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 ( toStatusCode resStatus ≡ Just MethodNotAllowed ∧ + hasHeader "Allow" res ) + $ abort' + $ A.toAsciiBuilder "The status was " + ⊕ printStatusCode resStatus + ⊕ A.toAsciiBuilder " but no \"Allow\" header." + + when ( toStatusCode resStatus ≢ Just 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' = throwSTM + ∘ mkAbortion' InternalServerError + ∘ A.toText + ∘ A.fromAsciiBuilder + +postprocessWithRequest ∷ NormalInteraction → STM () +postprocessWithRequest ni@(NI {..}) + = do willDiscardBody ← readTVar niWillDiscardBody + canHaveBody ← if willDiscardBody then + return False + else + resCanHaveBody <$> readTVar niResponse + + updateRes ni + $ deleteHeader "Content-Length" + ∘ deleteHeader "Transfer-Encoding" + + if canHaveBody then + do when niWillChunkBody $ + writeHeader ni "Transfer-Encoding" (Just "chunked") + writeDefaultPageIfNeeded ni + else + do writeTVar niWillDiscardBody True + -- 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 = getDefaultPage 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