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
142 import Network.HTTP.Lucu.Abortion
143 import Network.HTTP.Lucu.Config
144 import Network.HTTP.Lucu.ContentCoding
145 import Network.HTTP.Lucu.DefaultPage
146 import Network.HTTP.Lucu.ETag
147 import qualified Network.HTTP.Lucu.Headers as H
148 import Network.HTTP.Lucu.HttpVersion
149 import Network.HTTP.Lucu.Interaction
150 import Network.HTTP.Lucu.Parser
151 import Network.HTTP.Lucu.Postprocess
152 import Network.HTTP.Lucu.RFC1123DateTime
153 import Network.HTTP.Lucu.Request
154 import Network.HTTP.Lucu.Response
155 import Network.HTTP.Lucu.MIMEType
156 import Network.HTTP.Lucu.Utils
157 import Network.Socket
161 -- |The 'Resource' monad. This monad implements
162 -- 'Control.Monad.Trans.MonadIO' so it can do any 'Prelude.IO'
164 newtype Resource a = Resource { unRes :: ReaderT Interaction IO a }
166 instance Functor Resource where
167 fmap f c = Resource (fmap f (unRes c))
169 instance Monad Resource where
170 c >>= f = Resource (unRes c >>= unRes . f)
171 return = Resource . return
172 fail = Resource . fail
174 instance MonadIO Resource where
175 liftIO = Resource . liftIO
178 runRes :: Resource a -> Interaction -> IO a
180 = runReaderT (unRes r) itr
183 getInteraction :: Resource Interaction
184 getInteraction = Resource ask
187 -- |Get the 'Network.HTTP.Lucu.Config.Config' value which is used for
189 getConfig :: Resource Config
190 getConfig = do itr <- getInteraction
191 return $! itrConfig itr
194 -- |Get the 'Network.Socket.SockAddr' of the remote host. If you want
195 -- a string representation instead of 'Network.Socket.SockAddr', use
197 getRemoteAddr :: Resource SockAddr
198 getRemoteAddr = do itr <- getInteraction
199 return $! itrRemoteAddr itr
202 -- |Get the string representation of the address of remote host. If
203 -- you want a 'Network.Socket.SockAddr' instead of 'Prelude.String',
204 -- use 'getRemoteAddr'.
205 getRemoteAddr' :: Resource String
206 getRemoteAddr' = do addr <- getRemoteAddr
208 -- Network.Socket は IPv6 を考慮してゐないやうだ…
209 (SockAddrInet _ v4addr)
210 -> let b1 = (v4addr `shiftR` 24) .&. 0xFF
211 b2 = (v4addr `shiftR` 16) .&. 0xFF
212 b3 = (v4addr `shiftR` 8) .&. 0xFF
215 return $ concat $ intersperse "." $ map show [b1, b2, b3, b4]
220 -- |Get the 'Network.HTTP.Lucu.Request.Request' value which represents
221 -- the request header. In general you don't have to use this action.
222 getRequest :: Resource Request
223 getRequest = do itr <- getInteraction
224 req <- liftIO $! atomically $! readItr itr itrRequest fromJust
227 -- |Get the 'Network.HTTP.Lucu.Request.Method' value of the request.
228 getMethod :: Resource Method
229 getMethod = do req <- getRequest
230 return $! reqMethod req
232 -- |Get the URI of the request.
233 getRequestURI :: Resource URI
234 getRequestURI = do req <- getRequest
237 -- |Get the HTTP version of the request.
238 getRequestVersion :: Resource HttpVersion
239 getRequestVersion = do req <- getRequest
240 return $! reqVersion req
242 -- |Get the path of this 'Resource' (to be exact,
243 -- 'Network.HTTP.Lucu.Resource.Tree.ResourceDef') in the
244 -- 'Network.HTTP.Lucu.Resource.Tree.ResTree'. The result of this
245 -- action is the exact path in the tree even if the
246 -- 'Network.HTTP.Lucu.Resource.Tree.ResourceDef' is greedy.
250 -- > main = let tree = mkResTree [ (["foo"], resFoo) ]
251 -- > in runHttpd defaultConfig tree
253 -- > resFoo = ResourceDef {
254 -- > resIsGreedy = True
255 -- > , resGet = Just $ do requestURI <- getRequestURI
256 -- > resourcePath <- getResourcePath
257 -- > pathInfo <- getPathInfo
258 -- > -- uriPath requestURI == "/foo/bar/baz"
259 -- > -- resourcePath == ["foo"]
260 -- > -- pathInfo == ["bar", "baz"]
264 getResourcePath :: Resource [String]
265 getResourcePath = do itr <- getInteraction
266 return $! fromJust $! itrResourcePath itr
269 -- |This is an analogy of CGI PATH_INFO. Its result is always @[]@ if
270 -- the 'Network.HTTP.Lucu.Resource.Tree.ResourceDef' is not
271 -- greedy. See 'getResourcePath'.
272 getPathInfo :: Resource [String]
273 getPathInfo = do rsrcPath <- getResourcePath
274 reqURI <- getRequestURI
275 let reqPathStr = uriPath reqURI
276 reqPath = [x | x <- splitBy (== '/') reqPathStr, x /= ""]
277 -- rsrcPath と reqPath の共通する先頭部分を reqPath か
278 -- ら全部取り除くと、それは PATH_INFO のやうなものにな
279 -- る。rsrcPath は全部一致してゐるに決まってゐる(でな
280 -- ければこの Resource が撰ばれた筈が無い)ので、
281 -- rsrcPath の長さの分だけ削除すれば良い。
282 return $! drop (length rsrcPath) reqPath
284 -- | Assume the query part of request URI as
285 -- application\/x-www-form-urlencoded, and parse it. This action
286 -- doesn't parse the request body. See 'inputForm'.
287 getQueryForm :: Resource [(String, String)]
288 getQueryForm = do reqURI <- getRequestURI
289 return $! parseWWWFormURLEncoded $ uriQuery reqURI
291 -- |Get a value of given request header. Comparison of header name is
292 -- case-insensitive. Note that this action is not intended to be used
293 -- so frequently: there should be actions like 'getContentType' for
294 -- every common headers.
295 getHeader :: ByteString -> Resource (Maybe ByteString)
296 getHeader name = name `seq`
298 return $! H.getHeader name req
300 -- |Get a list of 'Network.HTTP.Lucu.MIMEType.MIMEType' enumerated on
301 -- header \"Accept\".
302 getAccept :: Resource [MIMEType]
303 getAccept = do acceptM <- getHeader (C8.pack "Accept")
308 -> case parse mimeTypeListP (LPS [accept]) of
309 (# Success xs, _ #) -> return xs
310 (# _ , _ #) -> abort BadRequest []
311 (Just $ "Unparsable Accept: " ++ C8.unpack accept)
313 -- |Get a list of @(contentCoding, qvalue)@ enumerated on header
314 -- \"Accept-Encoding\". The list is sorted in descending order by
316 getAcceptEncoding :: Resource [(String, Maybe Double)]
318 = do accEncM <- getHeader (C8.pack "Accept-Encoding")
321 -- HTTP/1.0 には Accept-Encoding が無い場合の規定が無い
322 -- ので安全の爲 identity が指定された事にする。HTTP/1.1
323 -- の場合は何でも受け入れて良い事になってゐるので "*" が
325 -> do ver <- getRequestVersion
327 HttpVersion 1 0 -> return [("identity", Nothing)]
328 HttpVersion 1 1 -> return [("*" , Nothing)]
330 -> if C8.null value then
332 return [("identity", Nothing)]
334 case parse acceptEncodingListP (LPS [value]) of
335 (# Success x, _ #) -> return $ reverse $ sortBy orderAcceptEncodings x
336 (# _ , _ #) -> abort BadRequest []
337 (Just $ "Unparsable Accept-Encoding: " ++ C8.unpack value)
339 -- |Check whether a given content-coding is acceptable.
340 isEncodingAcceptable :: String -> Resource Bool
341 isEncodingAcceptable coding
342 = do accList <- getAcceptEncoding
343 return (flip any accList $ \ (c, q) ->
344 (c == "*" || C8.pack c `H.noCaseEq` C8.pack coding) && q /= Just 0)
347 -- |Get the header \"Content-Type\" as
348 -- 'Network.HTTP.Lucu.MIMEType.MIMEType'.
349 getContentType :: Resource (Maybe MIMEType)
351 = do cTypeM <- getHeader (C8.pack "Content-Type")
356 -> case parse mimeTypeP (LPS [cType]) of
357 (# Success t, _ #) -> return $ Just t
358 (# _ , _ #) -> abort BadRequest []
359 (Just $ "Unparsable Content-Type: " ++ C8.unpack cType)
362 {- ExaminingRequest 時に使用するアクション群 -}
364 -- |Tell the system that the 'Resource' found an entity for the
365 -- request URI. If this is a GET or HEAD request, a found entity means
366 -- a datum to be replied. If this is a PUT or DELETE request, it means
367 -- a datum which was stored for the URI up to now. It is an error to
368 -- compute 'foundEntity' if this is a POST request.
370 -- Computation of 'foundEntity' performs \"If-Match\" test or
371 -- \"If-None-Match\" test if possible. When those tests fail, the
372 -- computation of 'Resource' immediately aborts with status \"412
373 -- Precondition Failed\" or \"304 Not Modified\" depending on the
376 -- If this is a GET or HEAD request, 'foundEntity' automatically puts
377 -- \"ETag\" and \"Last-Modified\" headers into the response.
378 foundEntity :: ETag -> ClockTime -> Resource ()
379 foundEntity tag timeStamp
380 = tag `seq` timeStamp `seq`
381 do driftTo ExaminingRequest
384 when (method == GET || method == HEAD)
385 $ setHeader' (C8.pack "Last-Modified") (C8.pack $ formatHTTPDateTime timeStamp)
386 when (method == POST)
387 $ abort InternalServerError []
388 (Just "Illegal computation of foundEntity for POST request.")
393 -- |Tell the system that the 'Resource' found an entity for the
394 -- request URI. The only difference from 'foundEntity' is that
395 -- 'foundETag' doesn't (and can't) put \"Last-Modified\" header into
398 -- This action is not preferred. You should use 'foundEntity' whenever
400 foundETag :: ETag -> Resource ()
403 do driftTo ExaminingRequest
406 when (method == GET || method == HEAD)
407 $ setHeader' (C8.pack "ETag") (C8.pack $ show tag)
408 when (method == POST)
409 $ abort InternalServerError []
410 (Just "Illegal computation of foundETag for POST request.")
412 -- If-Match があればそれを見る。
413 ifMatch <- getHeader (C8.pack "If-Match")
416 Just value -> if value == C8.pack "*" then
419 case parse eTagListP (LPS [value]) of
420 (# Success tags, _ #)
421 -- tags の中に一致するものが無ければ
422 -- PreconditionFailed で終了。
423 -> when (not $ any (== tag) tags)
424 $ abort PreconditionFailed []
425 $! Just ("The entity tag doesn't match: " ++ C8.unpack value)
427 -> abort BadRequest [] $! Just ("Unparsable If-Match: " ++ C8.unpack value)
429 let statusForNoneMatch = if method == GET || method == HEAD then
434 -- If-None-Match があればそれを見る。
435 ifNoneMatch <- getHeader (C8.pack "If-None-Match")
438 Just value -> if value == C8.pack "*" then
439 abort statusForNoneMatch [] $! Just ("The entity tag matches: *")
441 case parse eTagListP (LPS [value]) of
442 (# Success tags, _ #)
443 -> when (any (== tag) tags)
444 $ abort statusForNoneMatch [] $! Just ("The entity tag matches: " ++ C8.unpack value)
446 -> abort BadRequest [] $! Just ("Unparsable If-None-Match: " ++ C8.unpack value)
450 -- |Tell the system that the 'Resource' found an entity for the
451 -- request URI. The only difference from 'foundEntity' is that
452 -- 'foundTimeStamp' performs \"If-Modified-Since\" test or
453 -- \"If-Unmodified-Since\" test instead of \"If-Match\" test or
454 -- \"If-None-Match\" test. Be aware that any tests based on last
455 -- modification time are unsafe because it is possible to mess up such
456 -- tests by modifying the entity twice in a second.
458 -- This action is not preferred. You should use 'foundEntity' whenever
460 foundTimeStamp :: ClockTime -> Resource ()
461 foundTimeStamp timeStamp
463 do driftTo ExaminingRequest
466 when (method == GET || method == HEAD)
467 $ setHeader' (C8.pack "Last-Modified") (C8.pack $ formatHTTPDateTime timeStamp)
468 when (method == POST)
469 $ abort InternalServerError []
470 (Just "Illegal computation of foundTimeStamp for POST request.")
472 let statusForIfModSince = if method == GET || method == HEAD then
477 -- If-Modified-Since があればそれを見る。
478 ifModSince <- getHeader (C8.pack "If-Modified-Since")
480 Just str -> case parseHTTPDateTime (LPS [str]) of
482 -> when (timeStamp <= lastTime)
483 $ abort statusForIfModSince []
484 $! Just ("The entity has not been modified since " ++ C8.unpack str)
486 -> return () -- 不正な時刻は無視
489 -- If-Unmodified-Since があればそれを見る。
490 ifUnmodSince <- getHeader (C8.pack "If-Unmodified-Since")
492 Just str -> case parseHTTPDateTime (LPS [str]) of
494 -> when (timeStamp > lastTime)
495 $ abort PreconditionFailed []
496 $! Just ("The entity has not been modified since " ++ C8.unpack str)
498 -> return () -- 不正な時刻は無視
503 -- | Computation of @'foundNoEntity' mStr@ tells the system that the
504 -- 'Resource' found no entity for the request URI. @mStr@ is an
505 -- optional error message to be replied to the client.
507 -- If this is a PUT request, 'foundNoEntity' performs \"If-Match\"
508 -- test and aborts with status \"412 Precondition Failed\" when it
509 -- failed. If this is a GET, HEAD, POST or DELETE request,
510 -- 'foundNoEntity' always aborts with status \"404 Not Found\".
511 foundNoEntity :: Maybe String -> Resource ()
514 do driftTo ExaminingRequest
518 $ abort NotFound [] msgM
520 -- エンティティが存在しないと云ふ事は、"*" も含めたどのやうな
521 -- If-Match: 條件も滿たさない。
522 ifMatch <- getHeader (C8.pack "If-Match")
523 when (ifMatch /= Nothing)
524 $ abort PreconditionFailed [] msgM
529 {- GettingBody 時に使用するアクション群 -}
531 -- | Computation of @'input' limit@ attempts to read the request body
532 -- up to @limit@ bytes, and then make the 'Resource' transit to
533 -- /Deciding Header/ state. When the actual size of body is larger
534 -- than @limit@ bytes, computation of 'Resource' immediately aborts
535 -- with status \"413 Request Entity Too Large\". When the request has
536 -- no body, 'input' returns an empty string.
538 -- @limit@ may be less than or equal to zero. In this case, the
539 -- default limitation value
540 -- ('Network.HTTP.Lucu.Config.cnfMaxEntityLength') is used. See
543 -- Note that 'inputLBS' is more efficient than 'input' so you should
544 -- use it whenever possible.
545 input :: Int -> Resource String
546 input limit = limit `seq`
547 inputLBS limit >>= return . L8.unpack
550 -- | This is mostly the same as 'input' but is more
551 -- efficient. 'inputLBS' returns a
552 -- 'Data.ByteString.Base.LazyByteString' but it's not really lazy:
553 -- reading from the socket just happens at the computation of
554 -- 'inputLBS', not at the evaluation of the
555 -- 'Data.ByteString.Base.LazyByteString'. The same goes for
557 inputLBS :: Int -> Resource LazyByteString
560 do driftTo GettingBody
561 itr <- getInteraction
562 hasBody <- liftIO $! atomically $! readItr itr itrRequestHasBody id
563 chunk <- if hasBody then
566 do driftTo DecidingHeader
570 askForInput :: Interaction -> Resource LazyByteString
573 do let defaultLimit = cnfMaxEntityLength $ itrConfig itr
574 actualLimit = if limit <= 0 then
578 when (actualLimit <= 0)
579 $ fail ("inputLBS: limit must be positive: " ++ show actualLimit)
582 $! do chunkLen <- readItr itr itrReqChunkLength id
583 writeItr itr itrWillReceiveBody True
584 if fmap (> actualLimit) chunkLen == Just True then
588 writeItr itr itrReqBodyWanted $ Just actualLimit
589 -- 應答を待つ。トランザクションを分けなければ當然デッドロック。
590 chunk <- liftIO $! atomically
591 $! do chunk <- readItr itr itrReceivedBody id
592 chunkIsOver <- readItr itr itrReqChunkIsOver id
593 if L8.length chunk < fromIntegral actualLimit then
603 $ tooLarge actualLimit
604 -- 成功。itr 内にチャンクを置いたままにす
606 writeItr itr itrReceivedBody L8.empty
608 driftTo DecidingHeader
611 tooLarge :: Int -> STM ()
612 tooLarge lim = lim `seq`
613 abortSTM RequestEntityTooLarge []
614 $! Just ("Request body must be smaller than "
615 ++ show lim ++ " bytes.")
617 -- | Computation of @'inputChunk' limit@ attempts to read a part of
618 -- request body up to @limit@ bytes. You can read any large request by
619 -- repeating computation of this action. When you've read all the
620 -- request body, 'inputChunk' returns an empty string and then make
621 -- the 'Resource' transit to /Deciding Header/ state.
623 -- @limit@ may be less than or equal to zero. In this case, the
624 -- default limitation value
625 -- ('Network.HTTP.Lucu.Config.cnfMaxEntityLength') is used. See
628 -- Note that 'inputChunkLBS' is more efficient than 'inputChunk' so you
629 -- should use it whenever possible.
630 inputChunk :: Int -> Resource String
631 inputChunk limit = limit `seq`
632 inputChunkLBS limit >>= return . L8.unpack
635 -- | This is mostly the same as 'inputChunk' but is more
636 -- efficient. See 'inputLBS'.
637 inputChunkLBS :: Int -> Resource LazyByteString
640 do driftTo GettingBody
641 itr <- getInteraction
642 hasBody <- liftIO $ atomically $ readItr itr itrRequestHasBody id
643 chunk <- if hasBody then
646 do driftTo DecidingHeader
650 askForInput :: Interaction -> Resource LazyByteString
653 do let defaultLimit = cnfMaxEntityLength $! itrConfig itr
654 actualLimit = if limit < 0 then
658 when (actualLimit <= 0)
659 $ fail ("inputChunkLBS: limit must be positive: " ++ show actualLimit)
662 $! do writeItr itr itrReqBodyWanted $! Just actualLimit
663 writeItr itr itrWillReceiveBody True
664 -- 應答を待つ。トランザクションを分けなければ當然デッドロック。
665 chunk <- liftIO $! atomically
666 $ do chunk <- readItr itr itrReceivedBody id
667 -- 要求された量に滿たなくて、まだ殘りがあ
669 when (L8.length chunk < fromIntegral actualLimit)
670 $ do chunkIsOver <- readItr itr itrReqChunkIsOver id
674 writeItr itr itrReceivedBody L8.empty
677 $ driftTo DecidingHeader
680 -- | Computation of @'inputForm' limit@ attempts to read the request
681 -- body with 'input' and parse it as
682 -- application\/x-www-form-urlencoded. If the request header
683 -- \"Content-Type\" is not application\/x-www-form-urlencoded,
684 -- 'inputForm' makes 'Resource' abort with status \"415 Unsupported
685 -- Media Type\". If the request has no \"Content-Type\", it aborts
686 -- with \"400 Bad Request\".
688 -- This action should also support multipart\/form-data somehow, but
689 -- it is not (yet) done.
690 inputForm :: Int -> Resource [(String, String)]
693 do cTypeM <- getContentType
696 -> abort BadRequest [] (Just "Missing Content-Type")
697 Just (MIMEType "application" "x-www-form-urlencoded" _)
698 -> readWWWFormURLEncoded
699 Just (MIMEType "multipart" "form-data" _)
700 -> readMultipartFormData
702 -> abort UnsupportedMediaType [] (Just $! "Unsupported media type: "
705 readWWWFormURLEncoded
706 = do src <- input limit
707 return $ parseWWWFormURLEncoded src
709 readMultipartFormData -- FIXME: 未對應
710 = abort UnsupportedMediaType []
711 (Just $! "Sorry, inputForm does not currently support multipart/form-data.")
713 -- | This is just a constant @-1@. It's better to say @'input'
714 -- 'defaultLimit'@ than to say @'input' (-1)@ but these are exactly
721 {- DecidingHeader 時に使用するアクション群 -}
723 -- | Set the response status code. If you omit to compute this action,
724 -- the status code will be defaulted to \"200 OK\".
725 setStatus :: StatusCode -> Resource ()
728 do driftTo DecidingHeader
729 itr <- getInteraction
730 liftIO $! atomically $! updateItr itr itrResponse
735 -- | Set a value of given resource header. Comparison of header name
736 -- is case-insensitive. Note that this action is not intended to be
737 -- used so frequently: there should be actions like 'setContentType'
738 -- for every common headers.
740 -- Some important headers (especially \"Content-Length\" and
741 -- \"Transfer-Encoding\") may be silently dropped or overwritten by
742 -- the system not to corrupt the interaction with client at the
743 -- viewpoint of HTTP protocol layer. For instance, if we are keeping
744 -- the connection alive, without this process it causes a catastrophe
745 -- to send a header \"Content-Length: 10\" and actually send a body of
746 -- 20 bytes long. In this case the client shall only accept the first
747 -- 10 bytes of response body and thinks that the residual 10 bytes is
748 -- a part of header of the next response.
749 setHeader :: ByteString -> ByteString -> Resource ()
751 = name `seq` value `seq`
752 driftTo DecidingHeader >> setHeader' name value
755 setHeader' :: ByteString -> ByteString -> Resource ()
756 setHeader' name value
757 = name `seq` value `seq`
758 do itr <- getInteraction
760 $ updateItr itr itrResponse
761 $ H.setHeader name value
763 -- | Computation of @'redirect' code uri@ sets the response status to
764 -- @code@ and \"Location\" header to @uri@. The @code@ must satisfy
765 -- 'Network.HTTP.Lucu.Response.isRedirection' or it causes an error.
766 redirect :: StatusCode -> URI -> Resource ()
768 = code `seq` uri `seq`
769 do when (code == NotModified || not (isRedirection code))
770 $ abort InternalServerError []
771 $! Just ("Attempted to redirect with status " ++ show code)
774 {-# INLINE redirect #-}
777 -- | Computation of @'setContentType' mType@ sets the response header
778 -- \"Content-Type\" to @mType@.
779 setContentType :: MIMEType -> Resource ()
781 = setHeader (C8.pack "Content-Type") (C8.pack $ show mType)
783 -- | Computation of @'setLocation' uri@ sets the response header
784 -- \"Location\" to @uri@.
785 setLocation :: URI -> Resource ()
787 = setHeader (C8.pack "Location") (C8.pack $ uriToString id uri $ "")
789 -- |Computation of @'setContentEncoding' codings@ sets the response
790 -- header \"Content-Encoding\" to @codings@.
791 setContentEncoding :: [String] -> Resource ()
792 setContentEncoding codings
793 = do ver <- getRequestVersion
795 HttpVersion 1 0 -> unnormalizeCoding
796 HttpVersion 1 1 -> id
797 setHeader (C8.pack "Content-Encoding") (C8.pack $ joinWith ", " $ map tr codings)
800 {- DecidingBody 時に使用するアクション群 -}
802 -- | Computation of @'output' str@ writes @str@ as a response body,
803 -- and then make the 'Resource' transit to /Done/ state. It is safe to
804 -- apply 'output' to an infinite string, such as a lazy stream of
807 -- Note that 'outputLBS' is more efficient than 'output' so you should
808 -- use it whenever possible.
809 output :: String -> Resource ()
810 output str = outputLBS $! L8.pack str
811 {-# INLINE output #-}
813 -- | This is mostly the same as 'output' but is more efficient.
814 outputLBS :: LazyByteString -> Resource ()
815 outputLBS str = do outputChunkLBS str
817 {-# INLINE outputLBS #-}
819 -- | Computation of @'outputChunk' str@ writes @str@ as a part of
820 -- response body. You can compute this action multiple times to write
821 -- a body little at a time. It is safe to apply 'outputChunk' to an
824 -- Note that 'outputChunkLBS' is more efficient than 'outputChunk' so
825 -- you should use it whenever possible.
826 outputChunk :: String -> Resource ()
827 outputChunk str = outputChunkLBS $! L8.pack str
828 {-# INLINE outputChunk #-}
830 -- | This is mostly the same as 'outputChunk' but is more efficient.
831 outputChunkLBS :: LazyByteString -> Resource ()
834 do driftTo DecidingBody
835 itr <- getInteraction
837 let limit = cnfMaxOutputChunkLength $ itrConfig itr
839 $ fail ("cnfMaxOutputChunkLength must be positive: "
842 discardBody <- liftIO $ atomically $
843 readItr itr itrWillDiscardBody id
846 $ sendChunks str limit
849 $ liftIO $ atomically $
850 writeItr itr itrBodyIsNull False
852 -- チャンクの大きさは Config で制限されてゐる。もし例へば
853 -- "/dev/zero" を L8.readFile して作った LazyByteString をそのまま
854 -- ResponseWriter に渡したりすると大變な事が起こる。何故なら
855 -- ResponseWriter は Transfer-Encoding: chunked の時、ヘッダを書
857 sendChunks :: LazyByteString -> Int -> Resource ()
859 | L8.null str = return ()
860 | otherwise = do let (chunk, remaining) = L8.splitAt (fromIntegral limit) str
861 itr <- getInteraction
862 liftIO $ atomically $
863 do buf <- readItr itr itrBodyToSend id
866 writeItr itr itrBodyToSend chunk
871 sendChunks remaining limit
875 [GettingBody からそれ以降の状態に遷移する時]
877 body を讀み終へてゐなければ、殘りの body を讀み捨てる。
880 [DecidingHeader からそれ以降の状態に遷移する時]
887 bodyIsNull が False ならば何もしない。True だった場合は出力補完す
892 driftTo :: InteractionState -> Resource ()
895 do itr <- getInteraction
896 liftIO $ atomically $ do oldState <- readItr itr itrState id
897 if newState < oldState then
898 throwStateError oldState newState
900 do let a = [oldState .. newState]
903 mapM_ (uncurry $ drift itr) c
904 writeItr itr itrState newState
906 throwStateError :: Monad m => InteractionState -> InteractionState -> m a
908 throwStateError Done DecidingBody
909 = fail "It makes no sense to output something after finishing to output."
911 throwStateError old new
912 = fail ("state error: " ++ show old ++ " ==> " ++ show new)
915 drift :: Interaction -> InteractionState -> InteractionState -> STM ()
917 drift itr GettingBody _
918 = writeItr itr itrReqBodyWasteAll True
920 drift itr DecidingHeader _
924 = do bodyIsNull <- readItr itr itrBodyIsNull id
926 $ writeDefaultPage itr