3 -- |This is the Resource Monad; monadic actions to define the behavior
4 -- of each resources. The 'Resource' Monad is a kind of 'Prelude.IO'
5 -- Monad thus it implements 'Control.Monad.Trans.MonadIO' class. It is
6 -- also a state machine.
8 -- Request Processing Flow:
10 -- 1. A client issues an HTTP request.
12 -- 2. If the URI of it matches to any resource, the corresponding
13 -- 'Resource' Monad starts running on a newly spawned thread.
15 -- 3. The 'Resource' Monad looks at the request header, find (or not
16 -- find) an entity, receive the request body (if any), decide the
17 -- response header, and decide the response body. This process
18 -- will be discussed later.
20 -- 4. The 'Resource' Monad and its thread stops running. The client
21 -- may or may not be sending us the next request at this point.
23 -- 'Resource' Monad takes the following states. The initial state is
24 -- /Examining Request/ and the final state is /Done/.
26 -- [/Examining Request/] In this state, a 'Resource' looks at the
27 -- request header and thinks about an entity for it. If there is a
28 -- suitable entity, the 'Resource' tells the system an entity tag
29 -- and its last modification time ('foundEntity'). If it found no
30 -- entity, it tells the system so ('foundNoEntity'). In case it is
31 -- impossible to decide the existence of entity, which is a typical
32 -- case for POST requests, 'Resource' does nothing in this state.
34 -- [/Getting Body/] A 'Resource' asks the system to receive a
35 -- request body from client. Before actually reading from the
36 -- socket, the system sends \"100 Continue\" to the client if need
37 -- be. When a 'Resource' transits to the next state without
38 -- receiving all or part of request body, the system still reads it
39 -- and just throws it away.
41 -- [/Deciding Header/] A 'Resource' makes a decision of status code
42 -- and response header. When it transits to the next state, the
43 -- system checks the validness of response header and then write
44 -- them to the socket.
46 -- [/Deciding Body/] In this state, a 'Resource' asks the system to
47 -- write some response body to the socket. When it transits to the
48 -- next state without writing any response body, the system
49 -- completes it depending on the status code.
51 -- [/Done/] Everything is over. A 'Resource' can do nothing for the
52 -- HTTP interaction anymore.
54 -- Note that the state transition is one-way: for instance, it is an
55 -- error to try to read a request body after writing some
56 -- response. This limitation is for efficiency. We don't want to read
57 -- the entire request before starting 'Resource', nor we don't want to
58 -- postpone writing the entire response till the end of 'Resource'
61 module Network.HTTP.Lucu.Resource
69 -- ** Getting request header
71 -- |These actions can be computed regardless of the current state,
72 -- and they don't change the state.
86 , isEncodingAcceptable
89 -- ** Finding an entity
91 -- |These actions can be computed only in the /Examining Request/
92 -- state. After the computation, the 'Resource' transits to
93 -- /Getting Body/ state.
99 -- ** Getting a request body
101 -- |Computation of these actions changes the state to /Getting
110 -- ** Setting response headers
112 -- |Computation of these actions changes the state to /Deciding
121 -- ** Writing a response body
123 -- |Computation of these actions changes the state to /Deciding
134 import Control.Concurrent.STM
135 import Control.Monad.Reader
137 import Data.ByteString.Base (ByteString, LazyByteString(..))
138 import qualified Data.ByteString.Char8 as C8
139 import qualified Data.ByteString.Lazy.Char8 as L8
143 import Network.HTTP.Lucu.Abortion
144 import Network.HTTP.Lucu.Config
145 import Network.HTTP.Lucu.ContentCoding
146 import Network.HTTP.Lucu.DefaultPage
147 import Network.HTTP.Lucu.ETag
148 import qualified Network.HTTP.Lucu.Headers as H
149 import Network.HTTP.Lucu.HttpVersion
150 import Network.HTTP.Lucu.Interaction
151 import Network.HTTP.Lucu.MultipartForm
152 import Network.HTTP.Lucu.Parser
153 import Network.HTTP.Lucu.Postprocess
154 import Network.HTTP.Lucu.RFC1123DateTime
155 import Network.HTTP.Lucu.Request
156 import Network.HTTP.Lucu.Response
157 import Network.HTTP.Lucu.MIMEType
158 import Network.HTTP.Lucu.Utils
159 import Network.Socket
163 -- |The 'Resource' monad. This monad implements
164 -- 'Control.Monad.Trans.MonadIO' so it can do any 'Prelude.IO'
166 newtype Resource a = Resource { unRes :: ReaderT Interaction IO a }
168 instance Functor Resource where
169 fmap f c = Resource (fmap f (unRes c))
171 instance Monad Resource where
172 c >>= f = Resource (unRes c >>= unRes . f)
173 return = Resource . return
174 fail = Resource . fail
176 instance MonadIO Resource where
177 liftIO = Resource . liftIO
180 runRes :: Resource a -> Interaction -> IO a
182 = runReaderT (unRes r) itr
185 getInteraction :: Resource Interaction
186 getInteraction = Resource ask
189 -- |Get the 'Network.HTTP.Lucu.Config.Config' value which is used for
191 getConfig :: Resource Config
192 getConfig = do itr <- getInteraction
193 return $! itrConfig itr
196 -- |Get the 'Network.Socket.SockAddr' of the remote host. If you want
197 -- a string representation instead of 'Network.Socket.SockAddr', use
199 getRemoteAddr :: Resource SockAddr
200 getRemoteAddr = do itr <- getInteraction
201 return $! itrRemoteAddr itr
204 -- |Get the string representation of the address of remote host. If
205 -- you want a 'Network.Socket.SockAddr' instead of 'Prelude.String',
206 -- use 'getRemoteAddr'.
207 getRemoteAddr' :: Resource String
208 getRemoteAddr' = do addr <- getRemoteAddr
210 -- Network.Socket は IPv6 を考慮してゐないやうだ…
211 (SockAddrInet _ v4addr)
212 -> let b1 = (v4addr `shiftR` 24) .&. 0xFF
213 b2 = (v4addr `shiftR` 16) .&. 0xFF
214 b3 = (v4addr `shiftR` 8) .&. 0xFF
217 return $ concat $ intersperse "." $ map show [b1, b2, b3, b4]
222 -- |Get the 'Network.HTTP.Lucu.Request.Request' value which represents
223 -- the request header. In general you don't have to use this action.
224 getRequest :: Resource Request
225 getRequest = do itr <- getInteraction
226 req <- liftIO $! atomically $! readItr itr itrRequest fromJust
229 -- |Get the 'Network.HTTP.Lucu.Request.Method' value of the request.
230 getMethod :: Resource Method
231 getMethod = do req <- getRequest
232 return $! reqMethod req
234 -- |Get the URI of the request.
235 getRequestURI :: Resource URI
236 getRequestURI = do req <- getRequest
239 -- |Get the HTTP version of the request.
240 getRequestVersion :: Resource HttpVersion
241 getRequestVersion = do req <- getRequest
242 return $! reqVersion req
244 -- |Get the path of this 'Resource' (to be exact,
245 -- 'Network.HTTP.Lucu.Resource.Tree.ResourceDef') in the
246 -- 'Network.HTTP.Lucu.Resource.Tree.ResTree'. The result of this
247 -- action is the exact path in the tree even if the
248 -- 'Network.HTTP.Lucu.Resource.Tree.ResourceDef' is greedy.
252 -- > main = let tree = mkResTree [ (["foo"], resFoo) ]
253 -- > in runHttpd defaultConfig tree
255 -- > resFoo = ResourceDef {
256 -- > resIsGreedy = True
257 -- > , resGet = Just $ do requestURI <- getRequestURI
258 -- > resourcePath <- getResourcePath
259 -- > pathInfo <- getPathInfo
260 -- > -- uriPath requestURI == "/foo/bar/baz"
261 -- > -- resourcePath == ["foo"]
262 -- > -- pathInfo == ["bar", "baz"]
266 getResourcePath :: Resource [String]
267 getResourcePath = do itr <- getInteraction
268 return $! fromJust $! itrResourcePath itr
271 -- |This is an analogy of CGI PATH_INFO. Its result is always @[]@ if
272 -- the 'Network.HTTP.Lucu.Resource.Tree.ResourceDef' is not
273 -- greedy. See 'getResourcePath'.
274 getPathInfo :: Resource [String]
275 getPathInfo = do rsrcPath <- getResourcePath
276 reqURI <- getRequestURI
277 let reqPathStr = uriPath reqURI
278 reqPath = [x | x <- splitBy (== '/') reqPathStr, x /= ""]
279 -- rsrcPath と reqPath の共通する先頭部分を reqPath か
280 -- ら全部取り除くと、それは PATH_INFO のやうなものにな
281 -- る。rsrcPath は全部一致してゐるに決まってゐる(でな
282 -- ければこの Resource が撰ばれた筈が無い)ので、
283 -- rsrcPath の長さの分だけ削除すれば良い。
284 return $! drop (length rsrcPath) reqPath
286 -- | Assume the query part of request URI as
287 -- application\/x-www-form-urlencoded, and parse it. This action
288 -- doesn't parse the request body. See 'inputForm'.
289 getQueryForm :: Resource [(String, String)]
290 getQueryForm = do reqURI <- getRequestURI
291 return $! parseWWWFormURLEncoded $ uriQuery reqURI
293 -- |Get a value of given request header. Comparison of header name is
294 -- case-insensitive. Note that this action is not intended to be used
295 -- so frequently: there should be actions like 'getContentType' for
296 -- every common headers.
297 getHeader :: ByteString -> Resource (Maybe ByteString)
298 getHeader name = name `seq`
300 return $! H.getHeader name req
302 -- |Get a list of 'Network.HTTP.Lucu.MIMEType.MIMEType' enumerated on
303 -- header \"Accept\".
304 getAccept :: Resource [MIMEType]
305 getAccept = do acceptM <- getHeader (C8.pack "Accept")
310 -> case parse mimeTypeListP (LPS [accept]) of
311 (# Success xs, _ #) -> return xs
312 (# _ , _ #) -> abort BadRequest []
313 (Just $ "Unparsable Accept: " ++ C8.unpack accept)
315 -- |Get a list of @(contentCoding, qvalue)@ enumerated on header
316 -- \"Accept-Encoding\". The list is sorted in descending order by
318 getAcceptEncoding :: Resource [(String, Maybe Double)]
320 = do accEncM <- getHeader (C8.pack "Accept-Encoding")
323 -- HTTP/1.0 には Accept-Encoding が無い場合の規定が無い
324 -- ので安全の爲 identity が指定された事にする。HTTP/1.1
325 -- の場合は何でも受け入れて良い事になってゐるので "*" が
327 -> do ver <- getRequestVersion
329 HttpVersion 1 0 -> return [("identity", Nothing)]
330 HttpVersion 1 1 -> return [("*" , Nothing)]
332 -> if C8.null value then
334 return [("identity", Nothing)]
336 case parse acceptEncodingListP (LPS [value]) of
337 (# Success x, _ #) -> return $ reverse $ sortBy orderAcceptEncodings x
338 (# _ , _ #) -> abort BadRequest []
339 (Just $ "Unparsable Accept-Encoding: " ++ C8.unpack value)
341 -- |Check whether a given content-coding is acceptable.
342 isEncodingAcceptable :: String -> Resource Bool
343 isEncodingAcceptable coding
344 = do accList <- getAcceptEncoding
345 return (flip any accList $ \ (c, q) ->
346 (c == "*" || C8.pack c `H.noCaseEq` C8.pack coding) && q /= Just 0)
349 -- |Get the header \"Content-Type\" as
350 -- 'Network.HTTP.Lucu.MIMEType.MIMEType'.
351 getContentType :: Resource (Maybe MIMEType)
353 = do cTypeM <- getHeader (C8.pack "Content-Type")
358 -> case parse mimeTypeP (LPS [cType]) of
359 (# Success t, _ #) -> return $ Just t
360 (# _ , _ #) -> abort BadRequest []
361 (Just $ "Unparsable Content-Type: " ++ C8.unpack cType)
364 {- ExaminingRequest 時に使用するアクション群 -}
366 -- |Tell the system that the 'Resource' found an entity for the
367 -- request URI. If this is a GET or HEAD request, a found entity means
368 -- a datum to be replied. If this is a PUT or DELETE request, it means
369 -- a datum which was stored for the URI up to now. It is an error to
370 -- compute 'foundEntity' if this is a POST request.
372 -- Computation of 'foundEntity' performs \"If-Match\" test or
373 -- \"If-None-Match\" test if possible. When those tests fail, the
374 -- computation of 'Resource' immediately aborts with status \"412
375 -- Precondition Failed\" or \"304 Not Modified\" depending on the
378 -- If this is a GET or HEAD request, 'foundEntity' automatically puts
379 -- \"ETag\" and \"Last-Modified\" headers into the response.
380 foundEntity :: ETag -> ClockTime -> Resource ()
381 foundEntity tag timeStamp
382 = tag `seq` timeStamp `seq`
383 do driftTo ExaminingRequest
386 when (method == GET || method == HEAD)
387 $ setHeader' (C8.pack "Last-Modified") (C8.pack $ formatHTTPDateTime timeStamp)
388 when (method == POST)
389 $ abort InternalServerError []
390 (Just "Illegal computation of foundEntity for POST request.")
395 -- |Tell the system that the 'Resource' found an entity for the
396 -- request URI. The only difference from 'foundEntity' is that
397 -- 'foundETag' doesn't (and can't) put \"Last-Modified\" header into
400 -- This action is not preferred. You should use 'foundEntity' whenever
402 foundETag :: ETag -> Resource ()
405 do driftTo ExaminingRequest
408 when (method == GET || method == HEAD)
409 $ setHeader' (C8.pack "ETag") (C8.pack $ show tag)
410 when (method == POST)
411 $ abort InternalServerError []
412 (Just "Illegal computation of foundETag for POST request.")
414 -- If-Match があればそれを見る。
415 ifMatch <- getHeader (C8.pack "If-Match")
418 Just value -> if value == C8.pack "*" then
421 case parse eTagListP (LPS [value]) of
422 (# Success tags, _ #)
423 -- tags の中に一致するものが無ければ
424 -- PreconditionFailed で終了。
425 -> when (not $ any (== tag) tags)
426 $ abort PreconditionFailed []
427 $! Just ("The entity tag doesn't match: " ++ C8.unpack value)
429 -> abort BadRequest [] $! Just ("Unparsable If-Match: " ++ C8.unpack value)
431 let statusForNoneMatch = if method == GET || method == HEAD then
436 -- If-None-Match があればそれを見る。
437 ifNoneMatch <- getHeader (C8.pack "If-None-Match")
440 Just value -> if value == C8.pack "*" then
441 abort statusForNoneMatch [] $! Just ("The entity tag matches: *")
443 case parse eTagListP (LPS [value]) of
444 (# Success tags, _ #)
445 -> when (any (== tag) tags)
446 $ abort statusForNoneMatch [] $! Just ("The entity tag matches: " ++ C8.unpack value)
448 -> abort BadRequest [] $! Just ("Unparsable If-None-Match: " ++ C8.unpack value)
452 -- |Tell the system that the 'Resource' found an entity for the
453 -- request URI. The only difference from 'foundEntity' is that
454 -- 'foundTimeStamp' performs \"If-Modified-Since\" test or
455 -- \"If-Unmodified-Since\" test instead of \"If-Match\" test or
456 -- \"If-None-Match\" test. Be aware that any tests based on last
457 -- modification time are unsafe because it is possible to mess up such
458 -- tests by modifying the entity twice in a second.
460 -- This action is not preferred. You should use 'foundEntity' whenever
462 foundTimeStamp :: ClockTime -> Resource ()
463 foundTimeStamp timeStamp
465 do driftTo ExaminingRequest
468 when (method == GET || method == HEAD)
469 $ setHeader' (C8.pack "Last-Modified") (C8.pack $ formatHTTPDateTime timeStamp)
470 when (method == POST)
471 $ abort InternalServerError []
472 (Just "Illegal computation of foundTimeStamp for POST request.")
474 let statusForIfModSince = if method == GET || method == HEAD then
479 -- If-Modified-Since があればそれを見る。
480 ifModSince <- getHeader (C8.pack "If-Modified-Since")
482 Just str -> case parseHTTPDateTime (LPS [str]) of
484 -> when (timeStamp <= lastTime)
485 $ abort statusForIfModSince []
486 $! Just ("The entity has not been modified since " ++ C8.unpack str)
488 -> return () -- 不正な時刻は無視
491 -- If-Unmodified-Since があればそれを見る。
492 ifUnmodSince <- getHeader (C8.pack "If-Unmodified-Since")
494 Just str -> case parseHTTPDateTime (LPS [str]) of
496 -> when (timeStamp > lastTime)
497 $ abort PreconditionFailed []
498 $! Just ("The entity has not been modified since " ++ C8.unpack str)
500 -> return () -- 不正な時刻は無視
505 -- | Computation of @'foundNoEntity' mStr@ tells the system that the
506 -- 'Resource' found no entity for the request URI. @mStr@ is an
507 -- optional error message to be replied to the client.
509 -- If this is a PUT request, 'foundNoEntity' performs \"If-Match\"
510 -- test and aborts with status \"412 Precondition Failed\" when it
511 -- failed. If this is a GET, HEAD, POST or DELETE request,
512 -- 'foundNoEntity' always aborts with status \"404 Not Found\".
513 foundNoEntity :: Maybe String -> Resource ()
516 do driftTo ExaminingRequest
520 $ abort NotFound [] msgM
522 -- エンティティが存在しないと云ふ事は、"*" も含めたどのやうな
523 -- If-Match: 條件も滿たさない。
524 ifMatch <- getHeader (C8.pack "If-Match")
525 when (ifMatch /= Nothing)
526 $ abort PreconditionFailed [] msgM
531 {- GettingBody 時に使用するアクション群 -}
533 -- | Computation of @'input' limit@ attempts to read the request body
534 -- up to @limit@ bytes, and then make the 'Resource' transit to
535 -- /Deciding Header/ state. When the actual size of body is larger
536 -- than @limit@ bytes, computation of 'Resource' immediately aborts
537 -- with status \"413 Request Entity Too Large\". When the request has
538 -- no body, 'input' returns an empty string.
540 -- @limit@ may be less than or equal to zero. In this case, the
541 -- default limitation value
542 -- ('Network.HTTP.Lucu.Config.cnfMaxEntityLength') is used. See
545 -- Note that 'inputLBS' is more efficient than 'input' so you should
546 -- use it whenever possible.
547 input :: Int -> Resource String
548 input limit = limit `seq`
549 inputLBS limit >>= return . L8.unpack
552 -- | This is mostly the same as 'input' but is more
553 -- efficient. 'inputLBS' returns a
554 -- 'Data.ByteString.Base.LazyByteString' but it's not really lazy:
555 -- reading from the socket just happens at the computation of
556 -- 'inputLBS', not at the evaluation of the
557 -- 'Data.ByteString.Base.LazyByteString'. The same goes for
559 inputLBS :: Int -> Resource LazyByteString
562 do driftTo GettingBody
563 itr <- getInteraction
564 hasBody <- liftIO $! atomically $! readItr itr itrRequestHasBody id
565 chunk <- if hasBody then
568 do driftTo DecidingHeader
572 askForInput :: Interaction -> Resource LazyByteString
575 do let defaultLimit = cnfMaxEntityLength $ itrConfig itr
576 actualLimit = if limit <= 0 then
580 when (actualLimit <= 0)
581 $ fail ("inputLBS: limit must be positive: " ++ show actualLimit)
584 $! do chunkLen <- readItr itr itrReqChunkLength id
585 writeItr itr itrWillReceiveBody True
586 if fmap (> actualLimit) chunkLen == Just True then
590 writeItr itr itrReqBodyWanted $ Just actualLimit
591 -- 應答を待つ。トランザクションを分けなければ當然デッドロック。
592 chunk <- liftIO $! atomically
593 $! do chunk <- readItr itr itrReceivedBody id
594 chunkIsOver <- readItr itr itrReqChunkIsOver id
595 if L8.length chunk < fromIntegral actualLimit then
605 $ tooLarge actualLimit
606 -- 成功。itr 内にチャンクを置いたままにす
608 writeItr itr itrReceivedBody L8.empty
610 driftTo DecidingHeader
613 tooLarge :: Int -> STM ()
614 tooLarge lim = lim `seq`
615 abortSTM RequestEntityTooLarge []
616 $! Just ("Request body must be smaller than "
617 ++ show lim ++ " bytes.")
619 -- | Computation of @'inputChunk' limit@ attempts to read a part of
620 -- request body up to @limit@ bytes. You can read any large request by
621 -- repeating computation of this action. When you've read all the
622 -- request body, 'inputChunk' returns an empty string and then make
623 -- the 'Resource' transit to /Deciding Header/ state.
625 -- @limit@ may be less than or equal to zero. In this case, the
626 -- default limitation value
627 -- ('Network.HTTP.Lucu.Config.cnfMaxEntityLength') is used. See
630 -- Note that 'inputChunkLBS' is more efficient than 'inputChunk' so you
631 -- should use it whenever possible.
632 inputChunk :: Int -> Resource String
633 inputChunk limit = limit `seq`
634 inputChunkLBS limit >>= return . L8.unpack
637 -- | This is mostly the same as 'inputChunk' but is more
638 -- efficient. See 'inputLBS'.
639 inputChunkLBS :: Int -> Resource LazyByteString
642 do driftTo GettingBody
643 itr <- getInteraction
644 hasBody <- liftIO $ atomically $ readItr itr itrRequestHasBody id
645 chunk <- if hasBody then
648 do driftTo DecidingHeader
652 askForInput :: Interaction -> Resource LazyByteString
655 do let defaultLimit = cnfMaxEntityLength $! itrConfig itr
656 actualLimit = if limit < 0 then
660 when (actualLimit <= 0)
661 $ fail ("inputChunkLBS: limit must be positive: " ++ show actualLimit)
664 $! do writeItr itr itrReqBodyWanted $! Just actualLimit
665 writeItr itr itrWillReceiveBody True
666 -- 應答を待つ。トランザクションを分けなければ當然デッドロック。
667 chunk <- liftIO $! atomically
668 $ do chunk <- readItr itr itrReceivedBody id
669 -- 要求された量に滿たなくて、まだ殘りがあ
671 when (L8.length chunk < fromIntegral actualLimit)
672 $ do chunkIsOver <- readItr itr itrReqChunkIsOver id
676 writeItr itr itrReceivedBody L8.empty
679 $ driftTo DecidingHeader
682 -- | Computation of @'inputForm' limit@ attempts to read the request
683 -- body with 'input' and parse it as
684 -- application\/x-www-form-urlencoded or multipart\/form-data. If the
685 -- request header \"Content-Type\" is neither of them, 'inputForm'
686 -- makes 'Resource' abort with status \"415 Unsupported Media
687 -- Type\". If the request has no \"Content-Type\", it aborts with
688 -- \"400 Bad Request\".
689 inputForm :: Int -> Resource [(String, String)]
692 do cTypeM <- getContentType
695 -> abort BadRequest [] (Just "Missing Content-Type")
696 Just (MIMEType "application" "x-www-form-urlencoded" _)
697 -> readWWWFormURLEncoded
698 Just (MIMEType "multipart" "form-data" params)
699 -> readMultipartFormData params
701 -> abort UnsupportedMediaType [] (Just $! "Unsupported media type: "
704 readWWWFormURLEncoded
705 = do src <- input limit
706 return $ parseWWWFormURLEncoded src
708 readMultipartFormData params
709 = do case find ((== "boundary") . map toLower . fst) params of
711 -> abort BadRequest [] (Just "Missing boundary of multipart/form-data")
713 -> do src <- inputLBS limit
714 case parse (multipartFormP boundary) src of
715 (# Success pairs, _ #) -> return pairs
717 -> abort BadRequest [] (Just "Unparsable multipart/form-data")
719 -- | This is just a constant @-1@. It's better to say @'input'
720 -- 'defaultLimit'@ than to say @'input' (-1)@ but these are exactly
727 {- DecidingHeader 時に使用するアクション群 -}
729 -- | Set the response status code. If you omit to compute this action,
730 -- the status code will be defaulted to \"200 OK\".
731 setStatus :: StatusCode -> Resource ()
734 do driftTo DecidingHeader
735 itr <- getInteraction
736 liftIO $! atomically $! updateItr itr itrResponse
741 -- | Set a value of given resource header. Comparison of header name
742 -- is case-insensitive. Note that this action is not intended to be
743 -- used so frequently: there should be actions like 'setContentType'
744 -- for every common headers.
746 -- Some important headers (especially \"Content-Length\" and
747 -- \"Transfer-Encoding\") may be silently dropped or overwritten by
748 -- the system not to corrupt the interaction with client at the
749 -- viewpoint of HTTP protocol layer. For instance, if we are keeping
750 -- the connection alive, without this process it causes a catastrophe
751 -- to send a header \"Content-Length: 10\" and actually send a body of
752 -- 20 bytes long. In this case the client shall only accept the first
753 -- 10 bytes of response body and thinks that the residual 10 bytes is
754 -- a part of header of the next response.
755 setHeader :: ByteString -> ByteString -> Resource ()
757 = name `seq` value `seq`
758 driftTo DecidingHeader >> setHeader' name value
761 setHeader' :: ByteString -> ByteString -> Resource ()
762 setHeader' name value
763 = name `seq` value `seq`
764 do itr <- getInteraction
766 $ updateItr itr itrResponse
767 $ H.setHeader name value
769 -- | Computation of @'redirect' code uri@ sets the response status to
770 -- @code@ and \"Location\" header to @uri@. The @code@ must satisfy
771 -- 'Network.HTTP.Lucu.Response.isRedirection' or it causes an error.
772 redirect :: StatusCode -> URI -> Resource ()
774 = code `seq` uri `seq`
775 do when (code == NotModified || not (isRedirection code))
776 $ abort InternalServerError []
777 $! Just ("Attempted to redirect with status " ++ show code)
780 {-# INLINE redirect #-}
783 -- | Computation of @'setContentType' mType@ sets the response header
784 -- \"Content-Type\" to @mType@.
785 setContentType :: MIMEType -> Resource ()
787 = setHeader (C8.pack "Content-Type") (C8.pack $ show mType)
789 -- | Computation of @'setLocation' uri@ sets the response header
790 -- \"Location\" to @uri@.
791 setLocation :: URI -> Resource ()
793 = setHeader (C8.pack "Location") (C8.pack $ uriToString id uri $ "")
795 -- |Computation of @'setContentEncoding' codings@ sets the response
796 -- header \"Content-Encoding\" to @codings@.
797 setContentEncoding :: [String] -> Resource ()
798 setContentEncoding codings
799 = do ver <- getRequestVersion
801 HttpVersion 1 0 -> unnormalizeCoding
802 HttpVersion 1 1 -> id
803 setHeader (C8.pack "Content-Encoding") (C8.pack $ joinWith ", " $ map tr codings)
806 {- DecidingBody 時に使用するアクション群 -}
808 -- | Computation of @'output' str@ writes @str@ as a response body,
809 -- and then make the 'Resource' transit to /Done/ state. It is safe to
810 -- apply 'output' to an infinite string, such as a lazy stream of
813 -- Note that 'outputLBS' is more efficient than 'output' so you should
814 -- use it whenever possible.
815 output :: String -> Resource ()
816 output str = outputLBS $! L8.pack str
817 {-# INLINE output #-}
819 -- | This is mostly the same as 'output' but is more efficient.
820 outputLBS :: LazyByteString -> Resource ()
821 outputLBS str = do outputChunkLBS str
823 {-# INLINE outputLBS #-}
825 -- | Computation of @'outputChunk' str@ writes @str@ as a part of
826 -- response body. You can compute this action multiple times to write
827 -- a body little at a time. It is safe to apply 'outputChunk' to an
830 -- Note that 'outputChunkLBS' is more efficient than 'outputChunk' so
831 -- you should use it whenever possible.
832 outputChunk :: String -> Resource ()
833 outputChunk str = outputChunkLBS $! L8.pack str
834 {-# INLINE outputChunk #-}
836 -- | This is mostly the same as 'outputChunk' but is more efficient.
837 outputChunkLBS :: LazyByteString -> Resource ()
840 do driftTo DecidingBody
841 itr <- getInteraction
843 let limit = cnfMaxOutputChunkLength $ itrConfig itr
845 $ fail ("cnfMaxOutputChunkLength must be positive: "
848 discardBody <- liftIO $ atomically $
849 readItr itr itrWillDiscardBody id
852 $ sendChunks str limit
855 $ liftIO $ atomically $
856 writeItr itr itrBodyIsNull False
858 -- チャンクの大きさは Config で制限されてゐる。もし例へば
859 -- "/dev/zero" を L8.readFile して作った LazyByteString をそのまま
860 -- ResponseWriter に渡したりすると大變な事が起こる。何故なら
861 -- ResponseWriter は Transfer-Encoding: chunked の時、ヘッダを書
863 sendChunks :: LazyByteString -> Int -> Resource ()
865 | L8.null str = return ()
866 | otherwise = do let (chunk, remaining) = L8.splitAt (fromIntegral limit) str
867 itr <- getInteraction
868 liftIO $ atomically $
869 do buf <- readItr itr itrBodyToSend id
872 writeItr itr itrBodyToSend chunk
877 sendChunks remaining limit
881 [GettingBody からそれ以降の状態に遷移する時]
883 body を讀み終へてゐなければ、殘りの body を讀み捨てる。
886 [DecidingHeader からそれ以降の状態に遷移する時]
893 bodyIsNull が False ならば何もしない。True だった場合は出力補完す
898 driftTo :: InteractionState -> Resource ()
901 do itr <- getInteraction
902 liftIO $ atomically $ do oldState <- readItr itr itrState id
903 if newState < oldState then
904 throwStateError oldState newState
906 do let a = [oldState .. newState]
909 mapM_ (uncurry $ drift itr) c
910 writeItr itr itrState newState
912 throwStateError :: Monad m => InteractionState -> InteractionState -> m a
914 throwStateError Done DecidingBody
915 = fail "It makes no sense to output something after finishing to output."
917 throwStateError old new
918 = fail ("state error: " ++ show old ++ " ==> " ++ show new)
921 drift :: Interaction -> InteractionState -> InteractionState -> STM ()
923 drift itr GettingBody _
924 = writeItr itr itrReqBodyWasteAll True
926 drift itr DecidingHeader _
930 = do bodyIsNull <- readItr itr itrBodyIsNull id
932 $ writeDefaultPage itr