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 qualified Data.ByteString as Strict (ByteString)
138 import qualified Data.ByteString.Lazy as Lazy (ByteString)
139 import qualified Data.ByteString.Char8 as C8 hiding (ByteString)
140 import qualified Data.ByteString.Lazy.Char8 as L8 hiding (ByteString)
145 import Network.HTTP.Lucu.Abortion
146 import Network.HTTP.Lucu.Config
147 import Network.HTTP.Lucu.ContentCoding
148 import Network.HTTP.Lucu.DefaultPage
149 import Network.HTTP.Lucu.ETag
150 import qualified Network.HTTP.Lucu.Headers as H
151 import Network.HTTP.Lucu.HttpVersion
152 import Network.HTTP.Lucu.Interaction
153 import Network.HTTP.Lucu.MultipartForm
154 import Network.HTTP.Lucu.Parser
155 import Network.HTTP.Lucu.Postprocess
156 import Network.HTTP.Lucu.RFC1123DateTime
157 import Network.HTTP.Lucu.Request
158 import Network.HTTP.Lucu.Response
159 import Network.HTTP.Lucu.MIMEType
160 import Network.HTTP.Lucu.Utils
161 import Network.Socket hiding (accept)
162 import Network.URI hiding (path)
164 -- |The 'Resource' monad. This monad implements
165 -- 'Control.Monad.Trans.MonadIO' so it can do any 'Prelude.IO'
167 newtype Resource a = Resource { unRes :: ReaderT Interaction IO a }
169 instance Functor Resource where
170 fmap f c = Resource (fmap f (unRes c))
172 instance Monad Resource where
173 c >>= f = Resource (unRes c >>= unRes . f)
174 return = Resource . return
175 fail = Resource . fail
177 instance MonadIO Resource where
178 liftIO = Resource . liftIO
181 runRes :: Resource a -> Interaction -> IO a
183 = runReaderT (unRes r) itr
186 getInteraction :: Resource Interaction
187 getInteraction = Resource ask
190 -- |Get the 'Network.HTTP.Lucu.Config.Config' value which is used for
192 getConfig :: Resource Config
193 getConfig = do itr <- getInteraction
194 return $! itrConfig itr
197 -- |Get the 'Network.Socket.SockAddr' of the remote host. If you want
198 -- a string representation instead of 'Network.Socket.SockAddr', use
200 getRemoteAddr :: Resource SockAddr
201 getRemoteAddr = do itr <- getInteraction
202 return $! itrRemoteAddr itr
205 -- |Get the string representation of the address of remote host. If
206 -- you want a 'Network.Socket.SockAddr' instead of 'Prelude.String',
207 -- use 'getRemoteAddr'.
208 getRemoteAddr' :: Resource String
209 getRemoteAddr' = do addr <- getRemoteAddr
211 -- Network.Socket は IPv6 を考慮してゐないやうだ…
212 SockAddrInet _ v4addr
213 -> let b1 = (v4addr `shiftR` 24) .&. 0xFF
214 b2 = (v4addr `shiftR` 16) .&. 0xFF
215 b3 = (v4addr `shiftR` 8) .&. 0xFF
218 return $ concat $ intersperse "." $ map show [b1, b2, b3, b4]
225 -- |Get the 'Network.HTTP.Lucu.Request.Request' value which represents
226 -- the request header. In general you don't have to use this action.
227 getRequest :: Resource Request
228 getRequest = do itr <- getInteraction
229 req <- liftIO $! atomically $! readItr itr itrRequest fromJust
232 -- |Get the 'Network.HTTP.Lucu.Request.Method' value of the request.
233 getMethod :: Resource Method
234 getMethod = do req <- getRequest
235 return $! reqMethod req
237 -- |Get the URI of the request.
238 getRequestURI :: Resource URI
239 getRequestURI = do req <- getRequest
242 -- |Get the HTTP version of the request.
243 getRequestVersion :: Resource HttpVersion
244 getRequestVersion = do req <- getRequest
245 return $! reqVersion req
247 -- |Get the path of this 'Resource' (to be exact,
248 -- 'Network.HTTP.Lucu.Resource.Tree.ResourceDef') in the
249 -- 'Network.HTTP.Lucu.Resource.Tree.ResTree'. The result of this
250 -- action is the exact path in the tree even if the
251 -- 'Network.HTTP.Lucu.Resource.Tree.ResourceDef' is greedy.
255 -- > main = let tree = mkResTree [ (["foo"], resFoo) ]
256 -- > in runHttpd defaultConfig tree
258 -- > resFoo = ResourceDef {
259 -- > resIsGreedy = True
260 -- > , resGet = Just $ do requestURI <- getRequestURI
261 -- > resourcePath <- getResourcePath
262 -- > pathInfo <- getPathInfo
263 -- > -- uriPath requestURI == "/foo/bar/baz"
264 -- > -- resourcePath == ["foo"]
265 -- > -- pathInfo == ["bar", "baz"]
269 getResourcePath :: Resource [String]
270 getResourcePath = do itr <- getInteraction
271 return $! fromJust $! itrResourcePath itr
274 -- |This is an analogy of CGI PATH_INFO. Its result is always @[]@ if
275 -- the 'Network.HTTP.Lucu.Resource.Tree.ResourceDef' is not
276 -- greedy. See 'getResourcePath'.
277 getPathInfo :: Resource [String]
278 getPathInfo = do rsrcPath <- getResourcePath
280 let reqPathStr = uriPath uri
281 reqPath = [x | x <- splitBy (== '/') reqPathStr, x /= ""]
282 -- rsrcPath と reqPath の共通する先頭部分を reqPath か
283 -- ら全部取り除くと、それは PATH_INFO のやうなものにな
284 -- る。rsrcPath は全部一致してゐるに決まってゐる(でな
285 -- ければこの Resource が撰ばれた筈が無い)ので、
286 -- rsrcPath の長さの分だけ削除すれば良い。
287 return $! drop (length rsrcPath) reqPath
289 -- | Assume the query part of request URI as
290 -- application\/x-www-form-urlencoded, and parse it. This action
291 -- doesn't parse the request body. See 'inputForm'.
292 getQueryForm :: Resource [(String, String)]
293 getQueryForm = do uri <- getRequestURI
294 return $! parseWWWFormURLEncoded $ uriQuery uri
296 -- |Get a value of given request header. Comparison of header name is
297 -- case-insensitive. Note that this action is not intended to be used
298 -- so frequently: there should be actions like 'getContentType' for
299 -- every common headers.
300 getHeader :: Strict.ByteString -> Resource (Maybe Strict.ByteString)
301 getHeader name = name `seq`
303 return $! H.getHeader name req
305 -- |Get a list of 'Network.HTTP.Lucu.MIMEType.MIMEType' enumerated on
306 -- header \"Accept\".
307 getAccept :: Resource [MIMEType]
308 getAccept = do acceptM <- getHeader (C8.pack "Accept")
313 -> case parse mimeTypeListP (L8.fromChunks [accept]) of
314 (# Success xs, _ #) -> return xs
315 (# _ , _ #) -> abort BadRequest []
316 (Just $ "Unparsable Accept: " ++ C8.unpack accept)
318 -- |Get a list of @(contentCoding, qvalue)@ enumerated on header
319 -- \"Accept-Encoding\". The list is sorted in descending order by
321 getAcceptEncoding :: Resource [(String, Maybe Double)]
323 = do accEncM <- getHeader (C8.pack "Accept-Encoding")
326 -- HTTP/1.0 には Accept-Encoding が無い場合の規定が無い
327 -- ので安全の爲 identity が指定された事にする。HTTP/1.1
328 -- の場合は何でも受け入れて良い事になってゐるので "*" が
330 -> do ver <- getRequestVersion
332 HttpVersion 1 0 -> return [("identity", Nothing)]
333 HttpVersion 1 1 -> return [("*" , Nothing)]
336 -> if C8.null value then
338 return [("identity", Nothing)]
340 case parse acceptEncodingListP (L8.fromChunks [value]) of
341 (# Success x, _ #) -> return $ reverse $ sortBy orderAcceptEncodings x
342 (# _ , _ #) -> abort BadRequest []
343 (Just $ "Unparsable Accept-Encoding: " ++ C8.unpack value)
345 -- |Check whether a given content-coding is acceptable.
346 isEncodingAcceptable :: String -> Resource Bool
347 isEncodingAcceptable coding
348 = do accList <- getAcceptEncoding
349 return (flip any accList $ \ (c, q) ->
350 (c == "*" || C8.pack c `H.noCaseEq` C8.pack coding) && q /= Just 0)
353 -- |Get the header \"Content-Type\" as
354 -- 'Network.HTTP.Lucu.MIMEType.MIMEType'.
355 getContentType :: Resource (Maybe MIMEType)
357 = do cTypeM <- getHeader (C8.pack "Content-Type")
362 -> case parse mimeTypeP (L8.fromChunks [cType]) of
363 (# Success t, _ #) -> return $ Just t
364 (# _ , _ #) -> abort BadRequest []
365 (Just $ "Unparsable Content-Type: " ++ C8.unpack cType)
368 {- ExaminingRequest 時に使用するアクション群 -}
370 -- |Tell the system that the 'Resource' found an entity for the
371 -- request URI. If this is a GET or HEAD request, a found entity means
372 -- a datum to be replied. If this is a PUT or DELETE request, it means
373 -- a datum which was stored for the URI up to now. It is an error to
374 -- compute 'foundEntity' if this is a POST request.
376 -- Computation of 'foundEntity' performs \"If-Match\" test or
377 -- \"If-None-Match\" test if possible. When those tests fail, the
378 -- computation of 'Resource' immediately aborts with status \"412
379 -- Precondition Failed\" or \"304 Not Modified\" depending on the
382 -- If this is a GET or HEAD request, 'foundEntity' automatically puts
383 -- \"ETag\" and \"Last-Modified\" headers into the response.
384 foundEntity :: ETag -> UTCTime -> Resource ()
385 foundEntity tag timeStamp
386 = tag `seq` timeStamp `seq`
387 do driftTo ExaminingRequest
390 when (method == GET || method == HEAD)
391 $ setHeader' (C8.pack "Last-Modified") (C8.pack $ formatHTTPDateTime timeStamp)
392 when (method == POST)
393 $ abort InternalServerError []
394 (Just "Illegal computation of foundEntity for POST request.")
399 -- |Tell the system that the 'Resource' found an entity for the
400 -- request URI. The only difference from 'foundEntity' is that
401 -- 'foundETag' doesn't (and can't) put \"Last-Modified\" header into
404 -- This action is not preferred. You should use 'foundEntity' whenever
406 foundETag :: ETag -> Resource ()
409 do driftTo ExaminingRequest
412 when (method == GET || method == HEAD)
413 $ setHeader' (C8.pack "ETag") (C8.pack $ show tag)
414 when (method == POST)
415 $ abort InternalServerError []
416 (Just "Illegal computation of foundETag for POST request.")
418 -- If-Match があればそれを見る。
419 ifMatch <- getHeader (C8.pack "If-Match")
422 Just value -> if value == C8.pack "*" then
425 case parse eTagListP (L8.fromChunks [value]) of
426 (# Success tags, _ #)
427 -- tags の中に一致するものが無ければ
428 -- PreconditionFailed で終了。
429 -> when (not $ any (== tag) tags)
430 $ abort PreconditionFailed []
431 $! Just ("The entity tag doesn't match: " ++ C8.unpack value)
433 -> abort BadRequest [] $! Just ("Unparsable If-Match: " ++ C8.unpack value)
435 let statusForNoneMatch = if method == GET || method == HEAD then
440 -- If-None-Match があればそれを見る。
441 ifNoneMatch <- getHeader (C8.pack "If-None-Match")
444 Just value -> if value == C8.pack "*" then
445 abort statusForNoneMatch [] $! Just ("The entity tag matches: *")
447 case parse eTagListP (L8.fromChunks [value]) of
448 (# Success tags, _ #)
449 -> when (any (== tag) tags)
450 $ abort statusForNoneMatch [] $! Just ("The entity tag matches: " ++ C8.unpack value)
452 -> abort BadRequest [] $! Just ("Unparsable If-None-Match: " ++ C8.unpack value)
456 -- |Tell the system that the 'Resource' found an entity for the
457 -- request URI. The only difference from 'foundEntity' is that
458 -- 'foundTimeStamp' performs \"If-Modified-Since\" test or
459 -- \"If-Unmodified-Since\" test instead of \"If-Match\" test or
460 -- \"If-None-Match\" test. Be aware that any tests based on last
461 -- modification time are unsafe because it is possible to mess up such
462 -- tests by modifying the entity twice in a second.
464 -- This action is not preferred. You should use 'foundEntity' whenever
466 foundTimeStamp :: UTCTime -> Resource ()
467 foundTimeStamp timeStamp
469 do driftTo ExaminingRequest
472 when (method == GET || method == HEAD)
473 $ setHeader' (C8.pack "Last-Modified") (C8.pack $ formatHTTPDateTime timeStamp)
474 when (method == POST)
475 $ abort InternalServerError []
476 (Just "Illegal computation of foundTimeStamp for POST request.")
478 let statusForIfModSince = if method == GET || method == HEAD then
483 -- If-Modified-Since があればそれを見る。
484 ifModSince <- getHeader (C8.pack "If-Modified-Since")
486 Just str -> case parseHTTPDateTime (L8.fromChunks [str]) of
488 -> when (timeStamp <= lastTime)
489 $ abort statusForIfModSince []
490 $! Just ("The entity has not been modified since " ++ C8.unpack str)
492 -> return () -- 不正な時刻は無視
495 -- If-Unmodified-Since があればそれを見る。
496 ifUnmodSince <- getHeader (C8.pack "If-Unmodified-Since")
498 Just str -> case parseHTTPDateTime (L8.fromChunks [str]) of
500 -> when (timeStamp > lastTime)
501 $ abort PreconditionFailed []
502 $! Just ("The entity has not been modified since " ++ C8.unpack str)
504 -> return () -- 不正な時刻は無視
509 -- | Computation of @'foundNoEntity' mStr@ tells the system that the
510 -- 'Resource' found no entity for the request URI. @mStr@ is an
511 -- optional error message to be replied to the client.
513 -- If this is a PUT request, 'foundNoEntity' performs \"If-Match\"
514 -- test and aborts with status \"412 Precondition Failed\" when it
515 -- failed. If this is a GET, HEAD, POST or DELETE request,
516 -- 'foundNoEntity' always aborts with status \"404 Not Found\".
517 foundNoEntity :: Maybe String -> Resource ()
520 do driftTo ExaminingRequest
524 $ abort NotFound [] msgM
526 -- エンティティが存在しないと云ふ事は、"*" も含めたどのやうな
527 -- If-Match: 條件も滿たさない。
528 ifMatch <- getHeader (C8.pack "If-Match")
529 when (ifMatch /= Nothing)
530 $ abort PreconditionFailed [] msgM
535 {- GettingBody 時に使用するアクション群 -}
537 -- | Computation of @'input' limit@ attempts to read the request body
538 -- up to @limit@ bytes, and then make the 'Resource' transit to
539 -- /Deciding Header/ state. When the actual size of body is larger
540 -- than @limit@ bytes, computation of 'Resource' immediately aborts
541 -- with status \"413 Request Entity Too Large\". When the request has
542 -- no body, 'input' returns an empty string.
544 -- @limit@ may be less than or equal to zero. In this case, the
545 -- default limitation value
546 -- ('Network.HTTP.Lucu.Config.cnfMaxEntityLength') is used. See
549 -- Note that 'inputLBS' is more efficient than 'input' so you should
550 -- use it whenever possible.
551 input :: Int -> Resource String
552 input limit = limit `seq`
553 inputLBS limit >>= return . L8.unpack
556 -- | This is mostly the same as 'input' but is more
557 -- efficient. 'inputLBS' returns a 'Data.ByteString.Lazy.ByteString'
558 -- but it's not really lazy: reading from the socket just happens at
559 -- the computation of 'inputLBS', not at the evaluation of the
560 -- 'Data.ByteString.Lazy.ByteString'. The same goes for
562 inputLBS :: Int -> Resource Lazy.ByteString
565 do driftTo GettingBody
566 itr <- getInteraction
567 hasBody <- liftIO $! atomically $! readItr itr itrRequestHasBody id
568 chunk <- if hasBody then
571 do driftTo DecidingHeader
575 askForInput :: Interaction -> Resource Lazy.ByteString
578 do let confLimit = cnfMaxEntityLength $ itrConfig itr
579 actualLimit = if limit <= 0 then
583 when (actualLimit <= 0)
584 $ fail ("inputLBS: limit must be positive: " ++ show actualLimit)
587 $! do chunkLen <- readItr itr itrReqChunkLength id
588 writeItr itr itrWillReceiveBody True
589 if fmap (> actualLimit) chunkLen == Just True then
593 writeItr itr itrReqBodyWanted $ Just actualLimit
594 -- 應答を待つ。トランザクションを分けなければ當然デッドロック。
595 chunk <- liftIO $! atomically
596 $! do chunk <- readItr itr itrReceivedBody id
597 chunkIsOver <- readItr itr itrReqChunkIsOver id
598 if L8.length chunk < fromIntegral actualLimit then
608 $ tooLarge actualLimit
609 -- 成功。itr 内にチャンクを置いたままにす
611 writeItr itr itrReceivedBody L8.empty
613 driftTo DecidingHeader
616 tooLarge :: Int -> STM ()
617 tooLarge lim = lim `seq`
618 abortSTM RequestEntityTooLarge []
619 $! Just ("Request body must be smaller than "
620 ++ show lim ++ " bytes.")
622 -- | Computation of @'inputChunk' limit@ attempts to read a part of
623 -- request body up to @limit@ bytes. You can read any large request by
624 -- repeating computation of this action. When you've read all the
625 -- request body, 'inputChunk' returns an empty string and then make
626 -- the 'Resource' transit to /Deciding Header/ state.
628 -- @limit@ may be less than or equal to zero. In this case, the
629 -- default limitation value
630 -- ('Network.HTTP.Lucu.Config.cnfMaxEntityLength') is used. See
633 -- Note that 'inputChunkLBS' is more efficient than 'inputChunk' so you
634 -- should use it whenever possible.
635 inputChunk :: Int -> Resource String
636 inputChunk limit = limit `seq`
637 inputChunkLBS limit >>= return . L8.unpack
640 -- | This is mostly the same as 'inputChunk' but is more
641 -- efficient. See 'inputLBS'.
642 inputChunkLBS :: Int -> Resource Lazy.ByteString
645 do driftTo GettingBody
646 itr <- getInteraction
647 hasBody <- liftIO $ atomically $ readItr itr itrRequestHasBody id
648 chunk <- if hasBody then
651 do driftTo DecidingHeader
655 askForInput :: Interaction -> Resource Lazy.ByteString
658 do let confLimit = cnfMaxEntityLength $! itrConfig itr
659 actualLimit = if limit < 0 then
663 when (actualLimit <= 0)
664 $ fail ("inputChunkLBS: limit must be positive: " ++ show actualLimit)
667 $! do writeItr itr itrReqBodyWanted $! Just actualLimit
668 writeItr itr itrWillReceiveBody True
669 -- 應答を待つ。トランザクションを分けなければ當然デッドロック。
670 chunk <- liftIO $! atomically
671 $ do chunk <- readItr itr itrReceivedBody id
672 -- 要求された量に滿たなくて、まだ殘りがあ
674 when (L8.length chunk < fromIntegral actualLimit)
675 $ do chunkIsOver <- readItr itr itrReqChunkIsOver id
679 writeItr itr itrReceivedBody L8.empty
682 $ driftTo DecidingHeader
685 -- | Computation of @'inputForm' limit@ attempts to read the request
686 -- body with 'input' and parse it as
687 -- application\/x-www-form-urlencoded or multipart\/form-data. If the
688 -- request header \"Content-Type\" is neither of them, 'inputForm'
689 -- makes 'Resource' abort with status \"415 Unsupported Media
690 -- Type\". If the request has no \"Content-Type\", it aborts with
691 -- \"400 Bad Request\".
692 inputForm :: Int -> Resource [(String, String)]
695 do cTypeM <- getContentType
698 -> abort BadRequest [] (Just "Missing Content-Type")
699 Just (MIMEType "application" "x-www-form-urlencoded" _)
700 -> readWWWFormURLEncoded
701 Just (MIMEType "multipart" "form-data" params)
702 -> readMultipartFormData params
704 -> abort UnsupportedMediaType [] (Just $! "Unsupported media type: "
707 readWWWFormURLEncoded
708 = do src <- input limit
709 return $ parseWWWFormURLEncoded src
711 readMultipartFormData params
712 = do case find ((== "boundary") . map toLower . fst) params of
714 -> abort BadRequest [] (Just "Missing boundary of multipart/form-data")
716 -> do src <- inputLBS limit
717 case parse (multipartFormP boundary) src of
718 (# Success pairs, _ #) -> return pairs
720 -> abort BadRequest [] (Just "Unparsable multipart/form-data")
722 -- | This is just a constant @-1@. It's better to say @'input'
723 -- 'defaultLimit'@ than to say @'input' (-1)@ but these are exactly
730 {- DecidingHeader 時に使用するアクション群 -}
732 -- | Set the response status code. If you omit to compute this action,
733 -- the status code will be defaulted to \"200 OK\".
734 setStatus :: StatusCode -> Resource ()
737 do driftTo DecidingHeader
738 itr <- getInteraction
739 liftIO $! atomically $! updateItr itr itrResponse
744 -- | Set a value of given resource header. Comparison of header name
745 -- is case-insensitive. Note that this action is not intended to be
746 -- used so frequently: there should be actions like 'setContentType'
747 -- for every common headers.
749 -- Some important headers (especially \"Content-Length\" and
750 -- \"Transfer-Encoding\") may be silently dropped or overwritten by
751 -- the system not to corrupt the interaction with client at the
752 -- viewpoint of HTTP protocol layer. For instance, if we are keeping
753 -- the connection alive, without this process it causes a catastrophe
754 -- to send a header \"Content-Length: 10\" and actually send a body of
755 -- 20 bytes long. In this case the client shall only accept the first
756 -- 10 bytes of response body and thinks that the residual 10 bytes is
757 -- a part of header of the next response.
758 setHeader :: Strict.ByteString -> Strict.ByteString -> Resource ()
760 = name `seq` value `seq`
761 driftTo DecidingHeader >> setHeader' name value
764 setHeader' :: Strict.ByteString -> Strict.ByteString -> Resource ()
765 setHeader' name value
766 = name `seq` value `seq`
767 do itr <- getInteraction
769 $ updateItr itr itrResponse
770 $ H.setHeader name value
772 -- | Computation of @'redirect' code uri@ sets the response status to
773 -- @code@ and \"Location\" header to @uri@. The @code@ must satisfy
774 -- 'Network.HTTP.Lucu.Response.isRedirection' or it causes an error.
775 redirect :: StatusCode -> URI -> Resource ()
777 = code `seq` uri `seq`
778 do when (code == NotModified || not (isRedirection code))
779 $ abort InternalServerError []
780 $! Just ("Attempted to redirect with status " ++ show code)
783 {-# INLINE redirect #-}
786 -- | Computation of @'setContentType' mType@ sets the response header
787 -- \"Content-Type\" to @mType@.
788 setContentType :: MIMEType -> Resource ()
790 = setHeader (C8.pack "Content-Type") (C8.pack $ show mType)
792 -- | Computation of @'setLocation' uri@ sets the response header
793 -- \"Location\" to @uri@.
794 setLocation :: URI -> Resource ()
796 = setHeader (C8.pack "Location") (C8.pack $ uriToString id uri $ "")
798 -- |Computation of @'setContentEncoding' codings@ sets the response
799 -- header \"Content-Encoding\" to @codings@.
800 setContentEncoding :: [String] -> Resource ()
801 setContentEncoding codings
802 = do ver <- getRequestVersion
804 HttpVersion 1 0 -> unnormalizeCoding
805 HttpVersion 1 1 -> id
807 setHeader (C8.pack "Content-Encoding") (C8.pack $ joinWith ", " $ map tr codings)
810 {- DecidingBody 時に使用するアクション群 -}
812 -- | Computation of @'output' str@ writes @str@ as a response body,
813 -- and then make the 'Resource' transit to /Done/ state. It is safe to
814 -- apply 'output' to an infinite string, such as a lazy stream of
817 -- Note that 'outputLBS' is more efficient than 'output' so you should
818 -- use it whenever possible.
819 output :: String -> Resource ()
820 output str = outputLBS $! L8.pack str
821 {-# INLINE output #-}
823 -- | This is mostly the same as 'output' but is more efficient.
824 outputLBS :: Lazy.ByteString -> Resource ()
825 outputLBS str = do outputChunkLBS str
827 {-# INLINE outputLBS #-}
829 -- | Computation of @'outputChunk' str@ writes @str@ as a part of
830 -- response body. You can compute this action multiple times to write
831 -- a body little at a time. It is safe to apply 'outputChunk' to an
834 -- Note that 'outputChunkLBS' is more efficient than 'outputChunk' so
835 -- you should use it whenever possible.
836 outputChunk :: String -> Resource ()
837 outputChunk str = outputChunkLBS $! L8.pack str
838 {-# INLINE outputChunk #-}
840 -- | This is mostly the same as 'outputChunk' but is more efficient.
841 outputChunkLBS :: Lazy.ByteString -> Resource ()
842 outputChunkLBS wholeChunk
844 do driftTo DecidingBody
845 itr <- getInteraction
847 let limit = cnfMaxOutputChunkLength $ itrConfig itr
849 $ fail ("cnfMaxOutputChunkLength must be positive: "
852 discardBody <- liftIO $ atomically $
853 readItr itr itrWillDiscardBody id
856 $ sendChunks wholeChunk limit
858 unless (L8.null wholeChunk)
859 $ liftIO $ atomically $
860 writeItr itr itrBodyIsNull False
862 -- チャンクの大きさは Config で制限されてゐる。もし例へば
863 -- "/dev/zero" を L8.readFile して作った Lazy.ByteString をそのまま
864 -- ResponseWriter に渡したりすると大變な事が起こる。何故なら
865 -- ResponseWriter は Transfer-Encoding: chunked の時、ヘッダを書
867 sendChunks :: Lazy.ByteString -> Int -> Resource ()
869 | L8.null str = return ()
870 | otherwise = do let (chunk, remaining) = L8.splitAt (fromIntegral limit) str
871 itr <- getInteraction
872 liftIO $ atomically $
873 do buf <- readItr itr itrBodyToSend id
876 writeItr itr itrBodyToSend chunk
881 sendChunks remaining limit
885 [GettingBody からそれ以降の状態に遷移する時]
887 body を讀み終へてゐなければ、殘りの body を讀み捨てる。
890 [DecidingHeader からそれ以降の状態に遷移する時]
897 bodyIsNull が False ならば何もしない。True だった場合は出力補完す
902 driftTo :: InteractionState -> Resource ()
905 do itr <- getInteraction
906 liftIO $ atomically $ do oldState <- readItr itr itrState id
907 if newState < oldState then
908 throwStateError oldState newState
910 do let a = [oldState .. newState]
913 mapM_ (uncurry $ drift itr) c
914 writeItr itr itrState newState
916 throwStateError :: Monad m => InteractionState -> InteractionState -> m a
918 throwStateError Done DecidingBody
919 = fail "It makes no sense to output something after finishing to output."
921 throwStateError old new
922 = fail ("state error: " ++ show old ++ " ==> " ++ show new)
925 drift :: Interaction -> InteractionState -> InteractionState -> STM ()
927 drift itr GettingBody _
928 = writeItr itr itrReqBodyWasteAll True
930 drift itr DecidingHeader _
934 = do bodyIsNull <- readItr itr itrBodyIsNull id
936 $ writeDefaultPage itr