3 -- |This is the Resource Monad; monadic actions to define the behavior
4 -- of each resources. The 'Resource' Monad is a kind of IO Monad thus
5 -- it implements MonadIO class. It is also a state machine.
7 -- Request Processing Flow:
9 -- 1. A client issues an HTTP request.
11 -- 2. If the URI of it matches to any resource, the corresponding
12 -- 'Resource' Monad starts running on a newly spawned thread.
14 -- 3. The 'Resource' Monad looks at the request header, find (or not
15 -- find) an entity, receive the request body (if any), decide the
16 -- response header, and decide the response body. This process
17 -- will be discussed later.
19 -- 4. The 'Resource' Monad and its thread stops running. The client
20 -- may or may not be sending us the next request at this point.
22 -- 'Resource' Monad takes the following states. The initial state is
23 -- /Examining Request/ and the final state is /Done/.
25 -- [/Examining Request/] In this state, a 'Resource' looks at the
26 -- request header and thinks about an entity for it. If there is a
27 -- suitable entity, the 'Resource' tells the system an entity tag
28 -- and its last modification time ('foundEntity'). If it found no
29 -- entity, it tells the system so ('foundNoEntity'). In case it is
30 -- impossible to decide the existence of entity, which is a typical
31 -- case for POST requests, 'Resource' does nothing in this state.
33 -- [/Getting Body/] A 'Resource' asks the system to receive a
34 -- request body from client. Before actually reading from the
35 -- socket, the system sends \"100 Continue\" to the client if need
36 -- be. When a 'Resource' transits to the next state without
37 -- receiving all or part of request body, the system still reads it
38 -- and just throws it away.
40 -- [/Deciding Header/] A 'Resource' makes a decision of status code
41 -- and response header. When it transits to the next state, the
42 -- system checks the validness of response header and then write
43 -- them to the socket.
45 -- [/Deciding Body/] In this state, a 'Resource' asks the system to
46 -- write some response body to the socket. When it transits to the
47 -- next state without writing any response body, the system
48 -- completes it depending on the status code.
50 -- [/Done/] Everything is over. A 'Resource' can do nothing for the
51 -- HTTP interaction anymore.
53 -- Note that the state transition is one-way: for instance, it is an
54 -- error to try to read a request body after writing some
55 -- response. This limitation is for efficiency. We don't want to read
56 -- the entire request before starting 'Resource', nor we don't want to
57 -- postpone writing the entire response till the end of 'Resource'
60 module Network.HTTP.Lucu.Resource
67 -- ** Getting request header
69 -- |These actions can be computed regardless of the current state,
70 -- and they don't change the state.
84 -- ** Finding an entity
86 -- |These actions can be computed only in the /Examining Request/
87 -- state. After the computation, the 'Resource' transits to
88 -- /Getting Body/ state.
94 -- ** Getting a request body
96 -- |Computation of these actions changes the state to /Getting
105 -- ** Setting response headers
107 -- |Computation of these actions changes the state to /Deciding
115 -- ** Writing a response body
117 -- |Computation of these actions changes the state to /Deciding
128 import Control.Concurrent.STM
129 import Control.Monad.Reader
131 import qualified Data.ByteString.Lazy.Char8 as B
132 import Data.ByteString.Lazy.Char8 (ByteString)
135 import GHC.Conc (unsafeIOToSTM)
136 import Network.HTTP.Lucu.Abortion
137 import Network.HTTP.Lucu.Config
138 import Network.HTTP.Lucu.DefaultPage
139 import Network.HTTP.Lucu.ETag
140 import qualified Network.HTTP.Lucu.Headers as H
141 import Network.HTTP.Lucu.HttpVersion
142 import Network.HTTP.Lucu.Interaction
143 import Network.HTTP.Lucu.Parser
144 import Network.HTTP.Lucu.Postprocess
145 import Network.HTTP.Lucu.RFC1123DateTime
146 import Network.HTTP.Lucu.Request
147 import Network.HTTP.Lucu.Response
148 import Network.HTTP.Lucu.MIMEType
149 import Network.HTTP.Lucu.Utils
150 import Network.Socket
154 -- |The 'Resource' monad. /Interaction/ is an internal state thus it
155 -- is not exposed to users. This monad implements 'MonadIO' so it can
156 -- do any IO actions.
157 type Resource a = ReaderT Interaction IO a
159 -- |Get the 'Network.HTTP.Lucu.Config.Config' value which is used for
161 getConfig :: Resource Config
162 getConfig = do itr <- ask
163 return $ itrConfig itr
166 -- |Get the SockAddr of the remote host. If you want a string
167 -- representation instead of SockAddr, use 'getRemoteAddr''.
168 getRemoteAddr :: Resource SockAddr
169 getRemoteAddr = do itr <- ask
170 return $ itrRemoteAddr itr
173 -- |Get the string representation of the address of remote host. If
174 -- you want a SockAddr instead of String, use 'getRemoteAddr'.
175 getRemoteAddr' :: Resource String
176 getRemoteAddr' = do addr <- getRemoteAddr
178 -- Network.Socket は IPv6 を考慮してゐないやうだ…
179 (SockAddrInet _ v4addr)
180 -> let b1 = (v4addr `shiftR` 24) .&. 0xFF
181 b2 = (v4addr `shiftR` 16) .&. 0xFF
182 b3 = (v4addr `shiftR` 8) .&. 0xFF
185 return $ concat $ intersperse "." $ map show [b1, b2, b3, b4]
190 -- |Get the 'Network.HTTP.Lucu.Request.Request' value which represents
191 -- the request header. In general you don't have to use this action.
192 getRequest :: Resource Request
193 getRequest = do itr <- ask
194 req <- liftIO $ atomically $ readItr itr itrRequest fromJust
197 -- |Get the 'Network.HTTP.Lucu.Request.Method' value of the request.
198 getMethod :: Resource Method
199 getMethod = do req <- getRequest
200 return $ reqMethod req
202 -- |Get the URI of the request.
203 getRequestURI :: Resource URI
204 getRequestURI = do req <- getRequest
207 -- |Get the path of this 'Resource' (to be exact,
208 -- 'Network.HTTP.Lucu.Resource.Tree.ResourceDef') in the
209 -- 'Network.HTTP.Lucu.Resource.Tree.ResTree'. The result of this
210 -- action is the exact path in the tree even if the
211 -- 'Network.HTTP.Lucu.Resource.Tree.ResourceDef' is greedy.
215 -- > main = let tree = mkResTree [ (["foo"], resFoo) ]
216 -- > in runHttpd defaultConfig tree
218 -- > resFoo = ResourceDef {
219 -- > resIsGreedy = True
220 -- > , resGet = Just $ do requestURI <- getRequestURI
221 -- > resourcePath <- getResourcePath
222 -- > pathInfo <- getPathInfo
223 -- > -- uriPath requestURI == "/foo/bar/baz"
224 -- > -- resourcePath == ["foo"]
225 -- > -- pathInfo == ["bar", "baz"]
229 getResourcePath :: Resource [String]
230 getResourcePath = do itr <- ask
231 return $ fromJust $ itrResourcePath itr
234 -- |This is an analogy of CGI PATH_INFO. Its result is always @[]@ if
235 -- the 'Network.HTTP.Lucu.Resource.Tree.ResourceDef' is not
236 -- greedy. See 'getResourcePath'.
237 getPathInfo :: Resource [String]
238 getPathInfo = do rsrcPath <- getResourcePath
239 reqURI <- getRequestURI
240 let reqPathStr = uriPath reqURI
241 reqPath = [x | x <- splitBy (== '/') reqPathStr, x /= ""]
242 -- rsrcPath と reqPath の共通する先頭部分を reqPath か
243 -- ら全部取り除くと、それは PATH_INFO のやうなものにな
244 -- る。rsrcPath は全部一致してゐるに決まってゐる(でな
245 -- ければこの Resource が撰ばれた筈が無い)ので、
246 -- rsrcPath の長さの分だけ削除すれば良い。
247 return $ drop (length rsrcPath) reqPath
249 -- | Assume the query part of request URI as
250 -- application\/x-www-form-urlencoded, and parse it. This action
251 -- doesn't parse the request body. See 'inputForm'.
252 getQueryForm :: Resource [(String, String)]
253 getQueryForm = do reqURI <- getRequestURI
254 return $ parseWWWFormURLEncoded $ uriQuery reqURI
256 -- |Get a value of given request header. Comparison of header name is
257 -- case-insensitive. Note that this action is not intended to be used
258 -- so frequently: there should be actions like 'getContentType' for
259 -- every common headers.
260 getHeader :: String -> Resource (Maybe String)
261 getHeader name = do req <- getRequest
262 return $ H.getHeader name req
264 -- |Get a list of 'Network.HTTP.Lucu.MIMEType.MIMEType' enumerated on
265 -- header \"Accept\".
266 getAccept :: Resource [MIMEType]
267 getAccept = do accept <- getHeader "Accept"
268 if accept == Nothing then
271 case parseStr mimeTypeListP $ fromJust accept of
272 (Success xs, _) -> return xs
275 -- |Get the header \"Content-Type\" as
276 -- 'Network.HTTP.Lucu.MIMEType.MIMEType'.
277 getContentType :: Resource (Maybe MIMEType)
278 getContentType = do cType <- getHeader "Content-Type"
279 if cType == Nothing then
282 case parseStr mimeTypeP $ fromJust cType of
283 (Success t, _) -> return $ Just t
288 {- ExaminingRequest 時に使用するアクション群 -}
290 -- |Tell the system that the 'Resource' found an entity for the
291 -- request URI. If this is a GET or HEAD request, a found entity means
292 -- a datum to be replied. If this is a PUT or DELETE request, it means
293 -- a datum which was stored for the URI up to now. It is an error to
294 -- compute 'foundEntity' if this is a POST request.
296 -- Computation of 'foundEntity' performs \"If-Match\" test or
297 -- \"If-None-Match\" test if possible. When those tests fail, the
298 -- computation of 'Resource' immediately aborts with status \"412
299 -- Precondition Failed\" or \"304 Not Modified\" depending on the
302 -- If this is a GET or HEAD request, 'foundEntity' automatically puts
303 -- \"ETag\" and \"Last-Modified\" headers into the response.
304 foundEntity :: ETag -> ClockTime -> Resource ()
305 foundEntity tag timeStamp
306 = do driftTo ExaminingRequest
309 when (method == GET || method == HEAD)
310 $ setHeader' "Last-Modified" $ formatHTTPDateTime timeStamp
311 when (method == POST)
312 $ abort InternalServerError []
313 (Just "Illegal computation of foundEntity for POST request.")
318 -- |Tell the system that the 'Resource' found an entity for the
319 -- request URI. The only difference from 'foundEntity' is that
320 -- 'foundETag' doesn't (and can't) put \"Last-Modified\" header into
323 -- This action is not preferred. You should use 'foundEntity' when
325 foundETag :: ETag -> Resource ()
327 = do driftTo ExaminingRequest
330 when (method == GET || method == HEAD)
331 $ setHeader' "ETag" $ show tag
332 when (method == POST)
333 $ abort InternalServerError []
334 (Just "Illegal computation of foundETag for POST request.")
336 -- If-Match があればそれを見る。
337 ifMatch <- getHeader "If-Match"
340 Just "*" -> return ()
341 Just list -> case parseStr eTagListP list of
343 -- tags の中に一致するものが無ければ
344 -- PreconditionFailed で終了。
345 -> when (not $ any (== tag) tags)
346 $ abort PreconditionFailed []
347 $ Just ("The entity tag doesn't match: " ++ list)
348 _ -> abort BadRequest [] $ Just ("Unparsable If-Match: " ++ fromJust ifMatch)
350 let statusForNoneMatch = if method == GET || method == HEAD then
355 -- If-None-Match があればそれを見る。
356 ifNoneMatch <- getHeader "If-None-Match"
359 Just "*" -> abort statusForNoneMatch [] $ Just ("The entity tag matches: *")
360 Just list -> case parseStr eTagListP list of
362 -> when (any (== tag) tags)
363 $ abort statusForNoneMatch [] $ Just ("The entity tag matches: " ++ list)
364 _ -> abort BadRequest [] $ Just ("Unparsable If-None-Match: " ++ list)
368 -- |Tell the system that the 'Resource' found an entity for the
369 -- request URI. The only difference from 'foundEntity' is that
370 -- 'foundTimeStamp' performs \"If-Modified-Since\" test or
371 -- \"If-Unmodified-Since\" test instead of \"If-Match\" test or
372 -- \"If-None-Match\" test. Be aware that any tests based on last
373 -- modification time are unsafe because it is possible to mess up such
374 -- tests by modifying the entity twice in a second.
376 -- This action is not preferred. You should use 'foundEntity' when
378 foundTimeStamp :: ClockTime -> Resource ()
379 foundTimeStamp timeStamp
380 = do driftTo ExaminingRequest
383 when (method == GET || method == HEAD)
384 $ setHeader' "Last-Modified" $ formatHTTPDateTime timeStamp
385 when (method == POST)
386 $ abort InternalServerError []
387 (Just "Illegal computation of foundTimeStamp for POST request.")
389 let statusForIfModSince = if method == GET || method == HEAD then
394 -- If-Modified-Since があればそれを見る。
395 ifModSince <- getHeader "If-Modified-Since"
397 Just str -> case parseHTTPDateTime str of
399 -> when (timeStamp <= lastTime)
400 $ abort statusForIfModSince []
401 $ Just ("The entity has not been modified since " ++ str)
403 -> return () -- 不正な時刻は無視
406 -- If-Unmodified-Since があればそれを見る。
407 ifUnmodSince <- getHeader "If-Unmodified-Since"
409 Just str -> case parseHTTPDateTime str of
411 -> when (timeStamp > lastTime)
412 $ abort PreconditionFailed []
413 $ Just ("The entity has not been modified since " ++ str)
415 -> return () -- 不正な時刻は無視
420 -- | Computation of @'foundNoEntity' mStr@ tells the system that the
421 -- 'Resource' found no entity for the request URI. @mStr@ is an
422 -- optional error message to be replied to the client.
424 -- If this is a PUT request, 'foundNoEntity' performs \"If-Match\"
425 -- test and aborts with status \"412 Precondition Failed\" when it
426 -- failed. If this is a GET, HEAD, POST or DELETE request,
427 -- 'foundNoEntity' always aborts with status \"404 Not Found\".
428 foundNoEntity :: Maybe String -> Resource ()
430 = do driftTo ExaminingRequest
434 $ abort NotFound [] msgM
436 -- エンティティが存在しないと云ふ事は、"*" も含めたどのやうな
437 -- If-Match: 條件も滿たさない。
438 ifMatch <- getHeader "If-Match"
439 when (ifMatch /= Nothing)
440 $ abort PreconditionFailed [] msgM
445 {- GettingBody 時に使用するアクション群 -}
447 -- | Computation of @'input' limit@ attempts to read the request body
448 -- up to @limit@ bytes, and then make the 'Resource' transit to
449 -- /Deciding Header/ state. When the actual size of body is larger
450 -- than @limit@ bytes, computation of 'Resource' immediately aborts
451 -- with status \"413 Request Entity Too Large\". When the request has
452 -- no body, 'input' returns an empty string.
454 -- @limit@ may be less than or equal to zero. In this case, the
455 -- default limitation value
456 -- ('Network.HTTP.Lucu.Config.cnfMaxEntityLength') is used. See
459 -- Note that 'inputBS' is more efficient than 'input' so you should
460 -- use it whenever possible.
461 input :: Int -> Resource String
462 input limit = inputBS limit >>= return . B.unpack
465 -- | This is mostly the same as 'input' but is more
466 -- efficient. 'inputBS' returns a lazy ByteString but it's not really
467 -- lazy: reading from the socket just happens at the computation of
468 -- 'inputBS', not at the lazy evaluation of the ByteString. The same
469 -- goes for 'inputChunkBS'.
470 inputBS :: Int -> Resource ByteString
472 = do driftTo GettingBody
474 hasBody <- liftIO $ atomically $ readItr itr itrRequestHasBody id
475 chunk <- if hasBody then
478 do driftTo DecidingHeader
482 askForInput :: Interaction -> Resource ByteString
484 = do let defaultLimit = cnfMaxEntityLength $ itrConfig itr
485 actualLimit = if limit <= 0 then
489 when (actualLimit <= 0)
490 $ fail ("inputBS: limit must be positive: " ++ show actualLimit)
493 $ do chunkLen <- readItr itr itrReqChunkLength id
494 writeItr itr itrWillReceiveBody True
495 if fmap (> actualLimit) chunkLen == Just True then
499 writeItr itr itrReqBodyWanted $ Just actualLimit
500 -- 應答を待つ。トランザクションを分けなければ當然デッドロック。
501 chunk <- liftIO $ atomically
502 $ do chunk <- readItr itr itrReceivedBody id
503 chunkIsOver <- readItr itr itrReqChunkIsOver id
504 if B.length chunk < fromIntegral actualLimit then
514 $ tooLarge actualLimit
515 -- 成功。itr 内にチャンクを置いたままにす
517 writeItr itr itrReceivedBody B.empty
519 driftTo DecidingHeader
522 tooLarge :: Int -> STM ()
523 tooLarge lim = abortSTM RequestEntityTooLarge []
524 $ Just ("Request body must be smaller than "
525 ++ show lim ++ " bytes.")
527 -- | Computation of @'inputChunk' limit@ attempts to read a part of
528 -- request body up to @limit@ bytes. You can read any large request by
529 -- repeating computation of this action. When you've read all the
530 -- request body, 'inputChunk' returns an empty string and then make
531 -- the 'Resource' transit to /Deciding Header/ state.
533 -- @limit@ may be less than or equal to zero. In this case, the
534 -- default limitation value
535 -- ('Network.HTTP.Lucu.Config.cnfMaxEntityLength') is used. See
538 -- Note that 'inputChunkBS' is more efficient than 'inputChunk' so you
539 -- should use it whenever possible.
540 inputChunk :: Int -> Resource String
541 inputChunk limit = inputChunkBS limit >>= return . B.unpack
544 -- | This is mostly the same as 'inputChunk' but is more
545 -- efficient. See 'inputBS'.
546 inputChunkBS :: Int -> Resource ByteString
548 = do driftTo GettingBody
550 hasBody <- liftIO $ atomically $ readItr itr itrRequestHasBody id
551 chunk <- if hasBody then
554 do driftTo DecidingHeader
558 askForInput :: Interaction -> Resource ByteString
560 = do let defaultLimit = cnfMaxEntityLength $ itrConfig itr
561 actualLimit = if limit < 0 then
565 when (actualLimit <= 0)
566 $ fail ("inputChunkBS: limit must be positive: " ++ show actualLimit)
569 $ do writeItr itr itrReqBodyWanted $ Just actualLimit
570 writeItr itr itrWillReceiveBody True
571 -- 應答を待つ。トランザクションを分けなければ當然デッドロック。
572 chunk <- liftIO $ atomically
573 $ do chunk <- readItr itr itrReceivedBody id
574 -- 要求された量に滿たなくて、まだ殘りがあ
576 when (B.length chunk < fromIntegral actualLimit)
577 $ do chunkIsOver <- readItr itr itrReqChunkIsOver id
581 writeItr itr itrReceivedBody B.empty
584 $ driftTo DecidingHeader
587 -- | Computation of @'inputForm' limit@ attempts to read the request
588 -- body with 'input' and parse it as
589 -- application\/x-www-form-urlencoded. If the request header
590 -- \"Content-Type\" is not application\/x-www-form-urlencoded,
591 -- 'inputForm' makes 'Resource' abort with status \"415 Unsupported
592 -- Media Type\". If the request has no \"Content-Type\", it aborts
593 -- with \"400 Bad Request\".
595 -- This action should also support multipart\/form-data somehow, but
596 -- it is not (yet) done.
597 inputForm :: Int -> Resource [(String, String)]
599 = do cTypeM <- getContentType
602 -> abort BadRequest [] (Just "Missing Content-Type")
603 Just (MIMEType "application" "x-www-form-urlencoded" _)
604 -> readWWWFormURLEncoded
605 Just (MIMEType "multipart" "form-data" _)
606 -> readMultipartFormData
608 -> abort UnsupportedMediaType [] (Just $ "Unsupported media type: "
611 readWWWFormURLEncoded
612 = do src <- input limit
613 return $ parseWWWFormURLEncoded src
615 readMultipartFormData -- FIXME: 未對應
616 = abort UnsupportedMediaType []
617 (Just $ "Sorry, inputForm does not currently support multipart/form-data.")
619 -- | This is just a constant -1. It's better to say @'input'
620 -- 'defaultLimit'@ than to say @'input' (-1)@ but these are exactly
627 {- DecidingHeader 時に使用するアクション群 -}
629 -- | Set the response status code. If you omit to compute this action,
630 -- the status code will be defaulted to \"200 OK\".
631 setStatus :: StatusCode -> Resource ()
633 = do driftTo DecidingHeader
635 liftIO $ atomically $ updateItr itr itrResponse
640 -- | Set a value of given resource header. Comparison of header name
641 -- is case-insensitive. Note that this action is not intended to be
642 -- used so frequently: there should be actions like 'setContentType'
643 -- for every common headers.
645 -- Some important headers (especially \"Content-Length\" and
646 -- \"Transfer-Encoding\") may be silently dropped or overwritten by
647 -- the system not to corrupt the interaction with client at the
648 -- viewpoint of HTTP protocol layer. For instance, if we are keeping
649 -- the connection alive, without this process it causes a catastrophe
650 -- to send a header \"Content-Length: 10\" and actually send a body of
651 -- 20 bytes long. In this case the client shall only accept the first
652 -- 10 bytes of response body and thinks that the residual 10 bytes is
653 -- a part of header of the next response.
654 setHeader :: String -> String -> Resource ()
656 = driftTo DecidingHeader >> setHeader' name value
659 setHeader' :: String -> String -> Resource()
660 setHeader' name value
663 $ updateItr itr itrResponse
664 $ H.setHeader name value
666 -- | Computation of @'redirect' code uri@ sets the response status to
667 -- @code@ and \"Location\" header to @uri@. @code@ must satisfy
668 -- 'Network.HTTP.Lucu.Response.isRedirection' or it causes an error.
669 redirect :: StatusCode -> URI -> Resource ()
671 = do when (code == NotModified || not (isRedirection code))
672 $ abort InternalServerError []
673 $ Just ("Attempted to redirect with status " ++ show code)
677 -- | Computation of @'setContentType' mType@ sets the response header
678 -- \"Content-Type\" to @mType@.
679 setContentType :: MIMEType -> Resource ()
681 = setHeader "Content-Type" $ show mType
683 -- | Computation of @'setLocation' uri@ sets the response header
684 -- \"Location\" to @uri@.
685 setLocation :: URI -> Resource ()
687 = setHeader "Location" $ uriToString id uri $ ""
690 {- DecidingBody 時に使用するアクション群 -}
692 -- | Computation of @'output' str@ writes @str@ as a response body,
693 -- and then make the 'Resource' transit to /Done/ state. It is safe to
694 -- apply 'output' to an infinite string, such as a lazy stream of
697 -- Note that 'outputBS' is more efficient than 'output' so you should
698 -- use it whenever possible.
699 output :: String -> Resource ()
700 output = outputBS . B.pack
702 -- | This is mostly the same as 'output' but is more efficient.
703 outputBS :: ByteString -> Resource ()
704 outputBS str = do outputChunkBS str
707 -- | Computation of @'outputChunk' str@ writes @str@ as a part of
708 -- response body. You can compute this action multiple times to write
709 -- a body little at a time. It is safe to apply 'outputChunk' to an
712 -- Note that 'outputChunkBS' is more efficient than 'outputChunk' so
713 -- you should use it whenever possible.
714 outputChunk :: String -> Resource ()
715 outputChunk = outputChunkBS . B.pack
717 -- | This is mostly the same as 'outputChunk' but is more efficient.
718 outputChunkBS :: ByteString -> Resource ()
720 = do driftTo DecidingBody
723 let limit = cnfMaxOutputChunkLength $ itrConfig itr
725 $ fail ("cnfMaxOutputChunkLength must be positive: "
728 discardBody <- liftIO $ atomically $
729 readItr itr itrWillDiscardBody id
732 $ sendChunks str limit
735 $ liftIO $ atomically $
736 writeItr itr itrBodyIsNull False
738 {- チャンクの大きさは Config で制限されてゐる。もし例へば
739 /dev/zero を B.readFile して作った ByteString をそのまま
740 ResponseWriter に渡したりすると大變な事が起こる。何故なら
741 ResponseWriter はTransfer-Encoding: chunked の時、ヘッダを書く
742 爲にチャンクの大きさを測るから、その時に起こるであらう事は言ふ
744 sendChunks :: ByteString -> Int -> Resource ()
746 | B.null str = return ()
747 | otherwise = do let (chunk, remaining) = B.splitAt (fromIntegral limit) str
749 liftIO $ atomically $
750 do buf <- readItr itr itrBodyToSend id
753 writeItr itr itrBodyToSend chunk
758 sendChunks remaining limit
762 [GettingBody からそれ以降の状態に遷移する時]
764 body を讀み終へてゐなければ、殘りの body を讀み捨てる。
767 [DecidingHeader からそれ以降の状態に遷移する時]
774 bodyIsNull が False ならば何もしない。True だった場合は出力補完す
779 driftTo :: InteractionState -> Resource ()
782 liftIO $ atomically $ do oldState <- readItr itr itrState id
783 if newState < oldState then
784 throwStateError oldState newState
786 do let a = [oldState .. newState]
789 mapM_ (uncurry $ drift itr) c
790 writeItr itr itrState newState
792 throwStateError :: Monad m => InteractionState -> InteractionState -> m a
794 throwStateError Done DecidingBody
795 = fail "It makes no sense to output something after finishing to output."
797 throwStateError old new
798 = fail ("state error: " ++ show old ++ " ==> " ++ show new)
801 drift :: Interaction -> InteractionState -> InteractionState -> STM ()
803 drift itr GettingBody _
804 = writeItr itr itrReqBodyWasteAll True
806 drift itr DecidingHeader _
810 = do bodyIsNull <- readItr itr itrBodyIsNull id
812 $ writeDefaultPage itr