--- #prune
+{-# OPTIONS_HADDOCK prune #-}
-- |This is the Resource Monad; monadic actions to define the behavior
-- of each resources. The 'Resource' Monad is a kind of 'Prelude.IO'
module Network.HTTP.Lucu.Resource
(
- -- * Monad
- Resource
+ -- * Types
+ Resource
+ , FormData(..)
, runRes -- private
-- * Actions
, getConfig
, getRemoteAddr
, getRemoteAddr'
+ , getRemoteHost
+ , getRemoteCertificate
, getRequest
, getMethod
, getRequestURI
, getAcceptEncoding
, isEncodingAcceptable
, getContentType
+ , getAuthorization
-- ** Finding an entity
, setContentType
, setLocation
, setContentEncoding
+ , setWWWAuthenticate
-- ** Writing a response body
import Control.Concurrent.STM
import Control.Monad.Reader
-import Data.Bits
-import Data.ByteString.Base (LazyByteString)
-import qualified Data.ByteString.Lazy.Char8 as B
+import qualified Data.ByteString as Strict (ByteString)
+import qualified Data.ByteString.Lazy as Lazy (ByteString)
+import qualified Data.ByteString.Char8 as C8 hiding (ByteString)
+import qualified Data.ByteString.Lazy.Char8 as L8 hiding (ByteString)
+import Data.Char
import Data.List
import Data.Maybe
+import Data.Time
+import qualified Data.Time.HTTP as HTTP
import Network.HTTP.Lucu.Abortion
+import Network.HTTP.Lucu.Authorization
import Network.HTTP.Lucu.Config
import Network.HTTP.Lucu.ContentCoding
import Network.HTTP.Lucu.DefaultPage
import qualified Network.HTTP.Lucu.Headers as H
import Network.HTTP.Lucu.HttpVersion
import Network.HTTP.Lucu.Interaction
+import Network.HTTP.Lucu.MultipartForm
import Network.HTTP.Lucu.Parser
import Network.HTTP.Lucu.Postprocess
-import Network.HTTP.Lucu.RFC1123DateTime
import Network.HTTP.Lucu.Request
import Network.HTTP.Lucu.Response
import Network.HTTP.Lucu.MIMEType
import Network.HTTP.Lucu.Utils
-import Network.Socket
-import Network.URI
-import System.Time
+import Network.Socket hiding (accept)
+import Network.URI hiding (path)
+import OpenSSL.X509
-- |The 'Resource' monad. This monad implements
-- 'Control.Monad.Trans.MonadIO' so it can do any 'Prelude.IO'
-- you want a 'Network.Socket.SockAddr' instead of 'Prelude.String',
-- use 'getRemoteAddr'.
getRemoteAddr' :: Resource String
-getRemoteAddr' = do addr <- getRemoteAddr
- case addr of
- -- Network.Socket は IPv6 を考慮してゐないやうだ…
- (SockAddrInet _ v4addr)
- -> let b1 = (v4addr `shiftR` 24) .&. 0xFF
- b2 = (v4addr `shiftR` 16) .&. 0xFF
- b3 = (v4addr `shiftR` 8) .&. 0xFF
- b4 = v4addr .&. 0xFF
- in
- return $ concat $ intersperse "." $ map show [b1, b2, b3, b4]
- (SockAddrUnix path)
- -> return path
+getRemoteAddr' = do addr <- getRemoteAddr
+ (Just str, _) <- liftIO $! getNameInfo [NI_NUMERICHOST] True False addr
+ return str
+-- |Resolve an address to the remote host.
+getRemoteHost :: Resource String
+getRemoteHost = do addr <- getRemoteAddr
+ (Just str, _) <- liftIO $! getNameInfo [] True False addr
+ return str
+
+-- | Return the X.509 certificate of the client, or 'Nothing' if:
+--
+-- * This request didn't came through an SSL stream.
+--
+-- * The client didn't send us its certificate.
+--
+-- * The 'OpenSSL.Session.VerificationMode' of
+-- 'OpenSSL.Session.SSLContext' in
+-- 'Network.HTTP.Lucu.Config.SSLConfig' has not been set to
+-- 'OpenSSL.Session.VerifyPeer'.
+getRemoteCertificate :: Resource (Maybe X509)
+getRemoteCertificate = do itr <- getInteraction
+ return $! itrRemoteCert itr
-- |Get the 'Network.HTTP.Lucu.Request.Request' value which represents
-- the request header. In general you don't have to use this action.
return $! fromJust $! itrResourcePath itr
--- |This is an analogy of CGI PATH_INFO. Its result is always @[]@ if
--- the 'Network.HTTP.Lucu.Resource.Tree.ResourceDef' is not
--- greedy. See 'getResourcePath'.
+-- |This is an analogy of CGI PATH_INFO. The result is
+-- URI-unescaped. It is always @[]@ if the
+-- 'Network.HTTP.Lucu.Resource.Tree.ResourceDef' is not greedy. See
+-- 'getResourcePath'.
getPathInfo :: Resource [String]
getPathInfo = do rsrcPath <- getResourcePath
- reqURI <- getRequestURI
- let reqPathStr = uriPath reqURI
- reqPath = [x | x <- splitBy (== '/') reqPathStr, x /= ""]
+ uri <- getRequestURI
+ let reqPathStr = uriPath uri
+ reqPath = [unEscapeString x | x <- splitBy (== '/') reqPathStr, x /= ""]
-- rsrcPath と reqPath の共通する先頭部分を reqPath か
-- ら全部取り除くと、それは PATH_INFO のやうなものにな
-- る。rsrcPath は全部一致してゐるに決まってゐる(でな
-- rsrcPath の長さの分だけ削除すれば良い。
return $! drop (length rsrcPath) reqPath
--- | Assume the query part of request URI as
--- application\/x-www-form-urlencoded, and parse it. This action
--- doesn't parse the request body. See 'inputForm'.
-getQueryForm :: Resource [(String, String)]
-getQueryForm = do reqURI <- getRequestURI
- return $! parseWWWFormURLEncoded $ uriQuery reqURI
+-- |Assume the query part of request URI as
+-- application\/x-www-form-urlencoded, and parse it to pairs of
+-- @(name, formData)@. This action doesn't parse the request body. See
+-- 'inputForm'.
+getQueryForm :: Resource [(String, FormData)]
+getQueryForm = liftM parse' getRequestURI
+ where
+ parse' = map toPairWithFormData .
+ parseWWWFormURLEncoded .
+ snd .
+ splitAt 1 .
+ uriQuery
+
+toPairWithFormData :: (String, String) -> (String, FormData)
+toPairWithFormData (name, value)
+ = let fd = FormData {
+ fdFileName = Nothing
+ , fdContent = L8.pack value
+ }
+ in (name, fd)
-- |Get a value of given request header. Comparison of header name is
-- case-insensitive. Note that this action is not intended to be used
-- so frequently: there should be actions like 'getContentType' for
-- every common headers.
-getHeader :: String -> Resource (Maybe String)
+getHeader :: Strict.ByteString -> Resource (Maybe Strict.ByteString)
getHeader name = name `seq`
do req <- getRequest
return $! H.getHeader name req
-- |Get a list of 'Network.HTTP.Lucu.MIMEType.MIMEType' enumerated on
-- header \"Accept\".
getAccept :: Resource [MIMEType]
-getAccept = do acceptM <- getHeader "Accept"
+getAccept = do acceptM <- getHeader (C8.pack "Accept")
case acceptM of
Nothing
-> return []
Just accept
- -> case parseStr mimeTypeListP accept of
+ -> case parse mimeTypeListP (L8.fromChunks [accept]) of
(# Success xs, _ #) -> return xs
(# _ , _ #) -> abort BadRequest []
- (Just $ "Unparsable Accept: " ++ accept)
+ (Just $ "Unparsable Accept: " ++ C8.unpack accept)
-- |Get a list of @(contentCoding, qvalue)@ enumerated on header
-- \"Accept-Encoding\". The list is sorted in descending order by
-- qvalue.
getAcceptEncoding :: Resource [(String, Maybe Double)]
getAcceptEncoding
- = do accEncM <- getHeader "Accept-Encoding"
+ = do accEncM <- getHeader (C8.pack "Accept-Encoding")
case accEncM of
Nothing
-- HTTP/1.0 には Accept-Encoding が無い場合の規定が無い
case ver of
HttpVersion 1 0 -> return [("identity", Nothing)]
HttpVersion 1 1 -> return [("*" , Nothing)]
- Just ""
- -- identity のみが許される。
- -> return [("identity", Nothing)]
- Just accEnc
- -> case parseStr acceptEncodingListP accEnc of
- (# Success x, _ #) -> return $ reverse $ sortBy orderAcceptEncodings x
- (# _ , _ #) -> abort BadRequest []
- (Just $ "Unparsable Accept-Encoding: " ++ accEnc)
+ _ -> undefined
+ Just value
+ -> if C8.null value then
+ -- identity のみが許される。
+ return [("identity", Nothing)]
+ else
+ case parse acceptEncodingListP (L8.fromChunks [value]) of
+ (# Success x, _ #) -> return $ reverse $ sortBy orderAcceptEncodings x
+ (# _ , _ #) -> abort BadRequest []
+ (Just $ "Unparsable Accept-Encoding: " ++ C8.unpack value)
-- |Check whether a given content-coding is acceptable.
isEncodingAcceptable :: String -> Resource Bool
isEncodingAcceptable coding
= do accList <- getAcceptEncoding
return (flip any accList $ \ (c, q) ->
- (c == "*" || c `noCaseEq` coding) && q /= Just 0)
+ (c == "*" || C8.pack c `H.noCaseEq` C8.pack coding) && q /= Just 0)
-- |Get the header \"Content-Type\" as
-- 'Network.HTTP.Lucu.MIMEType.MIMEType'.
getContentType :: Resource (Maybe MIMEType)
getContentType
- = do cTypeM <- getHeader "Content-Type"
+ = do cTypeM <- getHeader (C8.pack "Content-Type")
case cTypeM of
Nothing
-> return Nothing
Just cType
- -> case parseStr mimeTypeP cType of
+ -> case parse mimeTypeP (L8.fromChunks [cType]) of
(# Success t, _ #) -> return $ Just t
(# _ , _ #) -> abort BadRequest []
- (Just $ "Unparsable Content-Type: " ++ cType)
+ (Just $ "Unparsable Content-Type: " ++ C8.unpack cType)
+
+
+-- |Get the header \"Authorization\" as
+-- 'Network.HTTP.Lucu.Authorization.AuthCredential'.
+getAuthorization :: Resource (Maybe AuthCredential)
+getAuthorization
+ = do authM <- getHeader (C8.pack "Authorization")
+ case authM of
+ Nothing
+ -> return Nothing
+ Just auth
+ -> case parse authCredentialP (L8.fromChunks [auth]) of
+ (# Success a, _ #) -> return $ Just a
+ (# _ , _ #) -> return Nothing
{- ExaminingRequest 時に使用するアクション群 -}
--
-- If this is a GET or HEAD request, 'foundEntity' automatically puts
-- \"ETag\" and \"Last-Modified\" headers into the response.
-foundEntity :: ETag -> ClockTime -> Resource ()
+foundEntity :: ETag -> UTCTime -> Resource ()
foundEntity tag timeStamp
= tag `seq` timeStamp `seq`
do driftTo ExaminingRequest
method <- getMethod
when (method == GET || method == HEAD)
- $ setHeader' "Last-Modified" $! formatHTTPDateTime timeStamp
+ $ setHeader' (C8.pack "Last-Modified") (C8.pack $ HTTP.format timeStamp)
when (method == POST)
$ abort InternalServerError []
(Just "Illegal computation of foundEntity for POST request.")
method <- getMethod
when (method == GET || method == HEAD)
- $ setHeader' "ETag" $! show tag
+ $ setHeader' (C8.pack "ETag") (C8.pack $ show tag)
when (method == POST)
$ abort InternalServerError []
(Just "Illegal computation of foundETag for POST request.")
-- If-Match があればそれを見る。
- ifMatch <- getHeader "If-Match"
+ ifMatch <- getHeader (C8.pack "If-Match")
case ifMatch of
- Nothing -> return ()
- Just "*" -> return ()
- Just list -> case parseStr eTagListP list of
- (# Success tags, _ #)
- -- tags の中に一致するものが無ければ
- -- PreconditionFailed で終了。
- -> when (not $ any (== tag) tags)
- $ abort PreconditionFailed []
- $! Just ("The entity tag doesn't match: " ++ list)
- (# _, _ #)
- -> abort BadRequest [] $! Just ("Unparsable If-Match: " ++ fromJust ifMatch)
+ Nothing -> return ()
+ Just value -> if value == C8.pack "*" then
+ return ()
+ else
+ case parse eTagListP (L8.fromChunks [value]) of
+ (# Success tags, _ #)
+ -- tags の中に一致するものが無ければ
+ -- PreconditionFailed で終了。
+ -> when (not $ any (== tag) tags)
+ $ abort PreconditionFailed []
+ $! Just ("The entity tag doesn't match: " ++ C8.unpack value)
+ (# _, _ #)
+ -> abort BadRequest [] $! Just ("Unparsable If-Match: " ++ C8.unpack value)
let statusForNoneMatch = if method == GET || method == HEAD then
NotModified
PreconditionFailed
-- If-None-Match があればそれを見る。
- ifNoneMatch <- getHeader "If-None-Match"
+ ifNoneMatch <- getHeader (C8.pack "If-None-Match")
case ifNoneMatch of
- Nothing -> return ()
- Just "*" -> abort statusForNoneMatch [] $! Just ("The entity tag matches: *")
- Just list -> case parseStr eTagListP list of
- (# Success tags, _ #)
- -> when (any (== tag) tags)
- $ abort statusForNoneMatch [] $! Just ("The entity tag matches: " ++ list)
- (# _, _ #)
- -> abort BadRequest [] $! Just ("Unparsable If-None-Match: " ++ list)
+ Nothing -> return ()
+ Just value -> if value == C8.pack "*" then
+ abort statusForNoneMatch [] $! Just ("The entity tag matches: *")
+ else
+ case parse eTagListP (L8.fromChunks [value]) of
+ (# Success tags, _ #)
+ -> when (any (== tag) tags)
+ $ abort statusForNoneMatch [] $! Just ("The entity tag matches: " ++ C8.unpack value)
+ (# _, _ #)
+ -> abort BadRequest [] $! Just ("Unparsable If-None-Match: " ++ C8.unpack value)
driftTo GettingBody
--
-- This action is not preferred. You should use 'foundEntity' whenever
-- possible.
-foundTimeStamp :: ClockTime -> Resource ()
+foundTimeStamp :: UTCTime -> Resource ()
foundTimeStamp timeStamp
= timeStamp `seq`
do driftTo ExaminingRequest
method <- getMethod
when (method == GET || method == HEAD)
- $ setHeader' "Last-Modified" $! formatHTTPDateTime timeStamp
+ $ setHeader' (C8.pack "Last-Modified") (C8.pack $ HTTP.format timeStamp)
when (method == POST)
$ abort InternalServerError []
(Just "Illegal computation of foundTimeStamp for POST request.")
PreconditionFailed
-- If-Modified-Since があればそれを見る。
- ifModSince <- getHeader "If-Modified-Since"
+ ifModSince <- getHeader (C8.pack "If-Modified-Since")
case ifModSince of
- Just str -> case parseHTTPDateTime str of
+ Just str -> case HTTP.parse (C8.unpack str) of
Just lastTime
-> when (timeStamp <= lastTime)
$ abort statusForIfModSince []
- $! Just ("The entity has not been modified since " ++ str)
+ $! Just ("The entity has not been modified since " ++ C8.unpack str)
Nothing
-> return () -- 不正な時刻は無視
Nothing -> return ()
-- If-Unmodified-Since があればそれを見る。
- ifUnmodSince <- getHeader "If-Unmodified-Since"
+ ifUnmodSince <- getHeader (C8.pack "If-Unmodified-Since")
case ifUnmodSince of
- Just str -> case parseHTTPDateTime str of
+ Just str -> case HTTP.parse (C8.unpack str) of
Just lastTime
-> when (timeStamp > lastTime)
$ abort PreconditionFailed []
- $! Just ("The entity has not been modified since " ++ str)
+ $! Just ("The entity has not been modified since " ++ C8.unpack str)
Nothing
-> return () -- 不正な時刻は無視
Nothing -> return ()
-- エンティティが存在しないと云ふ事は、"*" も含めたどのやうな
-- If-Match: 條件も滿たさない。
- ifMatch <- getHeader "If-Match"
+ ifMatch <- getHeader (C8.pack "If-Match")
when (ifMatch /= Nothing)
$ abort PreconditionFailed [] msgM
-- use it whenever possible.
input :: Int -> Resource String
input limit = limit `seq`
- inputLBS limit >>= return . B.unpack
+ inputLBS limit >>= return . L8.unpack
-- | This is mostly the same as 'input' but is more
--- efficient. 'inputLBS' returns a
--- 'Data.ByteString.Base.LazyByteString' but it's not really lazy:
--- reading from the socket just happens at the computation of
--- 'inputLBS', not at the evaluation of the
--- 'Data.ByteString.Base.LazyByteString'. The same goes for
+-- efficient. 'inputLBS' returns a 'Data.ByteString.Lazy.ByteString'
+-- but it's not really lazy: reading from the socket just happens at
+-- the computation of 'inputLBS', not at the evaluation of the
+-- 'Data.ByteString.Lazy.ByteString'. The same goes for
-- 'inputChunkLBS'.
-inputLBS :: Int -> Resource LazyByteString
+inputLBS :: Int -> Resource Lazy.ByteString
inputLBS limit
= limit `seq`
do driftTo GettingBody
askForInput itr
else
do driftTo DecidingHeader
- return B.empty
+ return L8.empty
return chunk
where
- askForInput :: Interaction -> Resource LazyByteString
+ askForInput :: Interaction -> Resource Lazy.ByteString
askForInput itr
= itr `seq`
- do let defaultLimit = cnfMaxEntityLength $ itrConfig itr
- actualLimit = if limit <= 0 then
- defaultLimit
- else
- limit
+ do let confLimit = cnfMaxEntityLength $ itrConfig itr
+ actualLimit = if limit <= 0 then
+ confLimit
+ else
+ limit
when (actualLimit <= 0)
$ fail ("inputLBS: limit must be positive: " ++ show actualLimit)
-- Reader にリクエスト
chunk <- liftIO $! atomically
$! do chunk <- readItr itr itrReceivedBody id
chunkIsOver <- readItr itr itrReqChunkIsOver id
- if B.length chunk < fromIntegral actualLimit then
+ if L8.length chunk < fromIntegral actualLimit then
-- 要求された量に滿たなくて、まだ殘り
-- があるなら再試行。
unless chunkIsOver
$ tooLarge actualLimit
-- 成功。itr 内にチャンクを置いたままにす
-- るとメモリの無駄になるので除去。
- writeItr itr itrReceivedBody B.empty
+ writeItr itr itrReceivedBody L8.empty
return chunk
driftTo DecidingHeader
return chunk
-- should use it whenever possible.
inputChunk :: Int -> Resource String
inputChunk limit = limit `seq`
- inputChunkLBS limit >>= return . B.unpack
+ inputChunkLBS limit >>= return . L8.unpack
-- | This is mostly the same as 'inputChunk' but is more
-- efficient. See 'inputLBS'.
-inputChunkLBS :: Int -> Resource LazyByteString
+inputChunkLBS :: Int -> Resource Lazy.ByteString
inputChunkLBS limit
= limit `seq`
do driftTo GettingBody
askForInput itr
else
do driftTo DecidingHeader
- return B.empty
+ return L8.empty
return chunk
where
- askForInput :: Interaction -> Resource LazyByteString
+ askForInput :: Interaction -> Resource Lazy.ByteString
askForInput itr
= itr `seq`
- do let defaultLimit = cnfMaxEntityLength $! itrConfig itr
- actualLimit = if limit < 0 then
- defaultLimit
+ do let confLimit = cnfMaxEntityLength $! itrConfig itr
+ actualLimit = if limit < 0 then
+ confLimit
else
limit
when (actualLimit <= 0)
$ do chunk <- readItr itr itrReceivedBody id
-- 要求された量に滿たなくて、まだ殘りがあ
-- るなら再試行。
- when (B.length chunk < fromIntegral actualLimit)
+ when (L8.length chunk < fromIntegral actualLimit)
$ do chunkIsOver <- readItr itr itrReqChunkIsOver id
unless chunkIsOver
$ retry
-- 成功
- writeItr itr itrReceivedBody B.empty
+ writeItr itr itrReceivedBody L8.empty
return chunk
- when (B.null chunk)
+ when (L8.null chunk)
$ driftTo DecidingHeader
return chunk
-- | Computation of @'inputForm' limit@ attempts to read the request
-- body with 'input' and parse it as
--- application\/x-www-form-urlencoded. If the request header
--- \"Content-Type\" is not application\/x-www-form-urlencoded,
--- 'inputForm' makes 'Resource' abort with status \"415 Unsupported
--- Media Type\". If the request has no \"Content-Type\", it aborts
--- with \"400 Bad Request\".
---
--- This action should also support multipart\/form-data somehow, but
--- it is not (yet) done.
-inputForm :: Int -> Resource [(String, String)]
+-- application\/x-www-form-urlencoded or multipart\/form-data. If the
+-- request header \"Content-Type\" is neither of them, 'inputForm'
+-- makes 'Resource' abort with status \"415 Unsupported Media
+-- Type\". If the request has no \"Content-Type\", it aborts with
+-- \"400 Bad Request\".
+inputForm :: Int -> Resource [(String, FormData)]
inputForm limit
= limit `seq`
do cTypeM <- getContentType
-> abort BadRequest [] (Just "Missing Content-Type")
Just (MIMEType "application" "x-www-form-urlencoded" _)
-> readWWWFormURLEncoded
- Just (MIMEType "multipart" "form-data" _)
- -> readMultipartFormData
+ Just (MIMEType "multipart" "form-data" params)
+ -> readMultipartFormData params
Just cType
-> abort UnsupportedMediaType [] (Just $! "Unsupported media type: "
++ show cType)
where
readWWWFormURLEncoded
- = do src <- input limit
- return $ parseWWWFormURLEncoded src
-
- readMultipartFormData -- FIXME: 未對應
- = abort UnsupportedMediaType []
- (Just $! "Sorry, inputForm does not currently support multipart/form-data.")
+ = liftM (map toPairWithFormData . parseWWWFormURLEncoded) (input limit)
+
+ readMultipartFormData params
+ = do case find ((== "boundary") . map toLower . fst) params of
+ Nothing
+ -> abort BadRequest [] (Just "Missing boundary of multipart/form-data")
+ Just (_, boundary)
+ -> do src <- inputLBS limit
+ case parse (multipartFormP boundary) src of
+ (# Success formList, _ #)
+ -> return formList
+ (# _, _ #)
+ -> abort BadRequest [] (Just "Unparsable multipart/form-data")
-- | This is just a constant @-1@. It's better to say @'input'
-- 'defaultLimit'@ than to say @'input' (-1)@ but these are exactly
-- 20 bytes long. In this case the client shall only accept the first
-- 10 bytes of response body and thinks that the residual 10 bytes is
-- a part of header of the next response.
-setHeader :: String -> String -> Resource ()
+setHeader :: Strict.ByteString -> Strict.ByteString -> Resource ()
setHeader name value
= name `seq` value `seq`
driftTo DecidingHeader >> setHeader' name value
-setHeader' :: String -> String -> Resource ()
+setHeader' :: Strict.ByteString -> Strict.ByteString -> Resource ()
setHeader' name value
= name `seq` value `seq`
do itr <- getInteraction
-- \"Content-Type\" to @mType@.
setContentType :: MIMEType -> Resource ()
setContentType mType
- = setHeader "Content-Type" $! show mType
+ = setHeader (C8.pack "Content-Type") (C8.pack $ show mType)
-- | Computation of @'setLocation' uri@ sets the response header
-- \"Location\" to @uri@.
setLocation :: URI -> Resource ()
setLocation uri
- = setHeader "Location" $ uriToString id uri $ ""
+ = setHeader (C8.pack "Location") (C8.pack $ uriToString id uri $ "")
-- |Computation of @'setContentEncoding' codings@ sets the response
-- header \"Content-Encoding\" to @codings@.
let tr = case ver of
HttpVersion 1 0 -> unnormalizeCoding
HttpVersion 1 1 -> id
- setHeader "Content-Encoding" $ joinWith ", " $ map tr codings
+ _ -> undefined
+ setHeader (C8.pack "Content-Encoding") (C8.pack $ joinWith ", " $ map tr codings)
+
+-- |Computation of @'setWWWAuthenticate' challenge@ sets the response
+-- header \"WWW-Authenticate\" to @challenge@.
+setWWWAuthenticate :: AuthChallenge -> Resource ()
+setWWWAuthenticate challenge
+ = setHeader (C8.pack "WWW-Authenticate") (C8.pack $ show challenge)
{- DecidingBody 時に使用するアクション群 -}
-- Note that 'outputLBS' is more efficient than 'output' so you should
-- use it whenever possible.
output :: String -> Resource ()
-output str = outputLBS $! B.pack str
+output str = outputLBS $! L8.pack str
{-# INLINE output #-}
-- | This is mostly the same as 'output' but is more efficient.
-outputLBS :: LazyByteString -> Resource ()
+outputLBS :: Lazy.ByteString -> Resource ()
outputLBS str = do outputChunkLBS str
driftTo Done
{-# INLINE outputLBS #-}
-- Note that 'outputChunkLBS' is more efficient than 'outputChunk' so
-- you should use it whenever possible.
outputChunk :: String -> Resource ()
-outputChunk str = outputChunkLBS $! B.pack str
+outputChunk str = outputChunkLBS $! L8.pack str
{-# INLINE outputChunk #-}
-- | This is mostly the same as 'outputChunk' but is more efficient.
-outputChunkLBS :: LazyByteString -> Resource ()
-outputChunkLBS str
- = str `seq`
+outputChunkLBS :: Lazy.ByteString -> Resource ()
+outputChunkLBS wholeChunk
+ = wholeChunk `seq`
do driftTo DecidingBody
itr <- getInteraction
readItr itr itrWillDiscardBody id
unless (discardBody)
- $ sendChunks str limit
+ $ sendChunks wholeChunk limit
- unless (B.null str)
+ unless (L8.null wholeChunk)
$ liftIO $ atomically $
writeItr itr itrBodyIsNull False
where
-- チャンクの大きさは Config で制限されてゐる。もし例へば
- -- "/dev/zero" を B.readFile して作った LazyByteString をそのまま
+ -- "/dev/zero" を L8.readFile して作った Lazy.ByteString をそのまま
-- ResponseWriter に渡したりすると大變な事が起こる。何故なら
-- ResponseWriter は Transfer-Encoding: chunked の時、ヘッダを書
-- く爲にチャンクの大きさを測る。
- sendChunks :: LazyByteString -> Int -> Resource ()
+ sendChunks :: Lazy.ByteString -> Int -> Resource ()
sendChunks str limit
- | B.null str = return ()
- | otherwise = do let (chunk, remaining) = B.splitAt (fromIntegral limit) str
- itr <- getInteraction
- liftIO $ atomically $
- do buf <- readItr itr itrBodyToSend id
- if B.null buf then
- -- バッファが消化された
- writeItr itr itrBodyToSend chunk
- else
- -- 消化されるのを待つ
- retry
- -- 殘りのチャンクについて繰り返す
- sendChunks remaining limit
+ | L8.null str = return ()
+ | otherwise = do let (chunk, remaining) = L8.splitAt (fromIntegral limit) str
+ itr <- getInteraction
+ liftIO $ atomically $
+ do buf <- readItr itr itrBodyToSend id
+ if L8.null buf then
+ -- バッファが消化された
+ writeItr itr itrBodyToSend chunk
+ else
+ -- 消化されるのを待つ
+ retry
+ -- 殘りのチャンクについて繰り返す
+ sendChunks remaining limit
{-