+setStatus :: StatusCode -> Resource ()
+setStatus code
+ = do driftTo DecidingHeader
+ itr <- ask
+ liftIO $ atomically $ updateItr itr itrResponse
+ $ \ resM -> case resM of
+ Nothing -> Just $ Response {
+ resVersion = HttpVersion 1 1
+ , resStatus = code
+ , resHeaders = []
+ }
+ Just res -> Just $ res {
+ resStatus = code
+ }
+
+
+setHeader :: String -> String -> Resource ()
+setHeader name value
+ = driftTo DecidingHeader >> setHeader' name value
+
+
+setHeader' :: String -> String -> Resource()
+setHeader' name value
+ = do itr <- ask
+ liftIO $ atomically $ updateItr itr itrResponse
+ $ \ resM -> case resM of
+ Nothing -> Just $ Response {
+ resVersion = HttpVersion 1 1
+ , resStatus = Ok
+ , resHeaders = [ (name, value) ]
+ }
+ Just res -> Just $ H.setHeader name value res
+
+
+redirect :: StatusCode -> URI -> Resource ()
+redirect code uri
+ = do when (code == NotModified || not (isRedirection code))
+ $ abort InternalServerError []
+ $ Just ("Attempted to redirect with status " ++ show code)
+ setStatus code
+ setHeader "Location" (uriToString id uri $ "")
+
+
+setETag :: ETag -> Resource ()
+setETag tag
+ = setHeader "ETag" $ show tag
+
+
+setLastModified :: ClockTime -> Resource ()
+setLastModified lastmod
+ = setHeader "Last-Modified" $ formatHTTPDateTime lastmod
+
+
+setContentType :: MIMEType -> Resource ()
+setContentType mType
+ = setHeader "Content-Type" $ show mType
+
+
+{- DecidingBody 時に使用するアクション群 -}
+
+output :: String -> Resource ()
+output = outputBS . B.pack
+
+
+outputBS :: ByteString -> Resource ()
+outputBS str = do outputChunkBS str
+ driftTo Done
+
+
+outputChunk :: String -> Resource ()
+outputChunk = outputChunkBS . B.pack
+
+
+{- チャンクの大きさは Config で制限されてゐる。もし例へば /dev/zero を
+ B.readFile して作った ByteString をそのまま ResponseWriter に渡した
+ りすると大變な事が起こる。何故なら ResponseWriter は
+ Transfer-Encoding: chunked の時、ヘッダを書く爲にチャンクの大きさを
+ 測るから、その時に起こるであらう事は言ふまでも無い。 -}
+
+outputChunkBS :: ByteString -> Resource ()
+outputChunkBS str
+ = do driftTo DecidingBody
+ unless (B.null str)
+ $ do itr <- ask
+
+ let limit = cnfMaxOutputChunkLength $ itrConfig itr
+ when (limit <= 0)
+ $ fail ("cnfMaxOutputChunkLength must be positive: "
+ ++ show limit)
+
+ sendChunks str limit
+
+ liftIO $ atomically $
+ writeItr itr itrBodyIsNull False
+ where
+ sendChunks :: ByteString -> Int -> Resource ()
+ sendChunks str limit
+ | B.null str = return ()
+ | otherwise = do let (chunk, remaining) = B.splitAt (fromIntegral limit) str
+ itr <- ask
+ liftIO $ atomically $
+ do buf <- readItr itr itrBodyToSend id
+ if B.null buf then
+ -- バッファが消化された
+ writeItr itr itrBodyToSend chunk
+ else
+ -- 消化されるのを待つ
+ retry
+ -- 殘りのチャンクについて繰り返す
+ sendChunks remaining limit
+
+{-
+
+ [GettingBody からそれ以降の状態に遷移する時]
+
+ body を讀み終へてゐなければ、殘りの body を讀み捨てる。
+
+
+ [DecidingHeader からそれ以降の状態に遷移する時]
+
+ postprocess する。
+
+
+ [Done に遷移する時]
+
+ bodyIsNull が False ならば何もしない。True だった場合は出力補完す
+ る。Content-Type も變はる。但し(デフォルトのまま)Status が 200 OK
+ だった場合は、補完の代はりに 204 No Content に變へる。
+
+-}
+
+driftTo :: InteractionState -> Resource ()
+driftTo newState
+ = do itr <- ask
+ liftIO $ atomically $ do oldState <- readItr itr itrState id
+ if newState < oldState then
+ throwStateError oldState newState
+ else
+ do let a = [oldState .. newState]
+ b = tail a
+ c = zip a b
+ mapM_ (uncurry $ drift itr) c
+ writeItr itr itrState newState