1 module Network.HTTP.Lucu.Postprocess
3 , completeUnconditionalHeaders
7 import Control.Concurrent.STM
9 import qualified Data.ByteString as Strict (ByteString)
10 import qualified Data.ByteString.Char8 as C8 hiding (ByteString)
14 import GHC.Conc (unsafeIOToSTM)
15 import Network.HTTP.Lucu.Abortion
16 import Network.HTTP.Lucu.Config
17 import Network.HTTP.Lucu.Headers
18 import Network.HTTP.Lucu.HttpVersion
19 import Network.HTTP.Lucu.Interaction
20 import Network.HTTP.Lucu.RFC1123DateTime
21 import Network.HTTP.Lucu.Request
22 import Network.HTTP.Lucu.Response
23 import System.IO.Unsafe
27 * Response が未設定なら、200 OK にする。
29 * ステータスが 2xx, 3xx, 4xx, 5xx のいずれでもなければ 500 にする。
31 * 405 Method Not Allowed なのに Allow ヘッダが無ければ 500 にする。
33 * 304 Not Modified 以外の 3xx なのに Location ヘッダが無ければ 500 に
36 * Content-Length があれば、それを削除する。Transfer-Encoding があって
39 * HTTP/1.1 であり、body を持つ事が出來る時、Transfer-Encoding を
42 * body を持つ事が出來る時、Content-Type が無ければデフォルト値にする。
43 出來ない時、HEAD でなければContent-Type, Etag, Last-Modified を削除
46 * body を持つ事が出來ない時、body 破棄フラグを立てる。
48 * Connection: close が設定されてゐる時、切斷フラグを立てる。
50 * 切斷フラグが立ってゐる時、Connection: close を設定する。
58 postprocess :: Interaction -> STM ()
61 do reqM <- readItr itr itrRequest id
62 res <- readItr itr itrResponse id
63 let sc = resStatus res
65 when (not $ any (\ p -> p sc) [isSuccessful, isRedirection, isError])
66 $ abortSTM InternalServerError []
67 $ Just ("The status code is not good for a final status: "
70 when (sc == MethodNotAllowed && getHeader (C8.pack "Allow") res == Nothing)
71 $ abortSTM InternalServerError []
72 $ Just ("The status was " ++ show sc ++ " but no Allow header.")
74 when (sc /= NotModified && isRedirection sc && getHeader (C8.pack "Location") res == Nothing)
75 $ abortSTM InternalServerError []
76 $ Just ("The status code was " ++ show sc ++ " but no Location header.")
78 when (reqM /= Nothing) relyOnRequest
80 -- itrResponse の内容は relyOnRequest によって變へられてゐる可
82 do oldRes <- readItr itr itrResponse id
83 newRes <- unsafeIOToSTM
84 $ completeUnconditionalHeaders (itrConfig itr) oldRes
85 writeItr itr itrResponse newRes
87 relyOnRequest :: STM ()
89 = do status <- readItr itr itrResponse resStatus
90 req <- readItr itr itrRequest fromJust
92 let reqVer = reqVersion req
93 canHaveBody = if reqMethod req == HEAD then
96 not (isInformational status ||
97 status == NoContent ||
98 status == ResetContent ||
99 status == NotModified )
101 updateRes $! deleteHeader (C8.pack "Content-Length")
102 updateRes $! deleteHeader (C8.pack "Transfer-Encoding")
104 cType <- readHeader (C8.pack "Content-Type")
105 when (cType == Nothing)
106 $ updateRes $ setHeader (C8.pack "Content-Type") defaultPageContentType
109 when (reqVer == HttpVersion 1 1)
110 $ do updateRes $! setHeader (C8.pack "Transfer-Encoding") (C8.pack "chunked")
111 writeItr itr itrWillChunkBody True
113 -- body 関連のヘッダを削除。但し HEAD なら Content-* は殘す
114 when (reqMethod req /= HEAD)
115 $ do updateRes $! deleteHeader (C8.pack "Content-Type")
116 updateRes $! deleteHeader (C8.pack "Etag")
117 updateRes $! deleteHeader (C8.pack "Last-Modified")
119 conn <- readHeader (C8.pack "Connection")
122 Just value -> if value `noCaseEq` C8.pack "close" then
123 writeItr itr itrWillClose True
127 willClose <- readItr itr itrWillClose id
129 $ updateRes $! setHeader (C8.pack "Connection") (C8.pack "close")
131 when (reqMethod req == HEAD || not canHaveBody)
132 $ writeTVar (itrWillDiscardBody itr) True
134 readHeader :: Strict.ByteString -> STM (Maybe Strict.ByteString)
137 readItr itr itrResponse $ getHeader name
139 updateRes :: (Response -> Response) -> STM ()
142 updateItr itr itrResponse updator
145 completeUnconditionalHeaders :: Config -> Response -> IO Response
146 completeUnconditionalHeaders conf res
147 = conf `seq` res `seq`
148 return res >>= compServer >>= compDate >>= return
151 = case getHeader (C8.pack "Server") res' of
152 Nothing -> return $ setHeader (C8.pack "Server") (cnfServerSoftware conf) res'
153 Just _ -> return res'
156 = case getHeader (C8.pack "Date") res' of
157 Nothing -> do date <- getCurrentDate
158 return $ setHeader (C8.pack "Date") date res'
159 Just _ -> return res'
162 cache :: IORef (UTCTime, Strict.ByteString)
163 cache = unsafePerformIO $
164 newIORef (UTCTime (ModifiedJulianDay 0) 0, undefined)
165 {-# NOINLINE cache #-}
167 getCurrentDate :: IO Strict.ByteString
168 getCurrentDate = do now <- getCurrentTime
169 (cachedTime, cachedStr) <- readIORef cache
171 if now `mostlyEq` cachedTime then
174 do let dateStr = C8.pack $ formatHTTPDateTime now
175 writeIORef cache (now, dateStr)
178 mostlyEq :: UTCTime -> UTCTime -> Bool
180 = if utctDay a == utctDay b then
181 fromEnum (utctDayTime a) == fromEnum (utctDayTime b)