-- | Repository of the resources in httpd.
module Network.HTTP.Lucu.Resource.Tree
( ResourceDef(..)
+ , emptyResource
+
, ResTree
, FallbackHandler
)
where
+import Control.Arrow
import Control.Concurrent
import Control.Concurrent.STM
import Control.Exception
import Network.HTTP.Lucu.Utils
import Network.URI hiding (path)
import System.IO
-import System.IO.Error hiding (catch)
import Prelude hiding (catch)
, resDelete :: !(Maybe (Resource ()))
}
+-- |'emptyResource' is a resource definition with no actual
+-- handlers. You can construct a 'ResourceDef' by selectively
+-- overriding 'emptyResource'. It is defined as follows:
+--
+-- @
+-- emptyResource = ResourceDef {
+-- resUsesNativeThread = False
+-- , resIsGreedy = False
+-- , resGet = Nothing
+-- , resHead = Nothing
+-- , resPost = Nothing
+-- , resPut = Nothing
+-- , resDelete = Nothing
+-- }
+-- @
+emptyResource :: ResourceDef
+emptyResource = ResourceDef {
+ resUsesNativeThread = False
+ , resIsGreedy = False
+ , resGet = Nothing
+ , resHead = Nothing
+ , resPost = Nothing
+ , resPut = Nothing
+ , resDelete = Nothing
+ }
+
-- |'ResTree' is an opaque structure which is a map from resource path
-- to 'ResourceDef'.
newtype ResTree = ResTree ResNode -- root だから Map ではない
type ResSubtree = Map String ResNode
-data ResNode = ResNode !(Maybe ResourceDef) !ResSubtree
+data ResNode = ResNode (Maybe ResourceDef) ResSubtree
-- |'mkResTree' converts a list of @(path, def)@ to a 'ResTree' e.g.
--
-- ]
-- @
mkResTree :: [ ([String], ResourceDef) ] -> ResTree
-mkResTree xs = xs `seq` processRoot xs
+mkResTree = processRoot . map (first canonicalisePath)
where
+ canonicalisePath :: [String] -> [String]
+ canonicalisePath = filter (/= "")
+
processRoot :: [ ([String], ResourceDef) ] -> ResTree
processRoot list
= let (roots, nonRoots) = partition (\ (path, _) -> path == []) list
children = processNonRoot nonRoots
in
if null roots then
- -- "/" にリソースが定義されない。"/foo" とかにはあるかも。
+ -- The root has no resources. Maybe there's one at
+ -- somewhere like "/foo".
ResTree (ResNode Nothing children)
else
- -- "/" がある。
+ -- There is a root resource.
let (_, def) = last roots
in
ResTree (ResNode (Just def) children)
node name = let defs = [def | (path, def) <- list, path == [name]]
in
if null defs then
- -- この位置にリソースが定義されない。
- -- もっと下にはあるかも。
+ -- No resources are defined
+ -- here. Maybe there's one at
+ -- somewhere below this node.
ResNode Nothing children
else
- -- この位置にリソースがある。
+ -- There is a resource here.
ResNode (Just $ last defs) children
children = processNonRoot [(path, def)
- | (_:path, def) <- list, not (null path)]
+ | (_:path, def) <- list]
in
subtree
findResource :: ResTree -> [FallbackHandler] -> URI -> IO (Maybe ([String], ResourceDef))
findResource (ResTree (ResNode rootDefM subtree)) fbs uri
- = do let pathStr = uriPath uri
- path = [x | x <- splitBy (== '/') pathStr, x /= ""]
- foundInTree = if null path then
- do def <- rootDefM
- return (path, def)
- else
- walkTree subtree path []
+ = do let pathStr = uriPath uri
+ path = [unEscapeString x | x <- splitBy (== '/') pathStr, x /= ""]
+ haveGreedyRoot = case rootDefM of
+ Just def -> resIsGreedy def
+ Nothing -> False
+ foundInTree = if haveGreedyRoot || null path then
+ do def <- rootDefM
+ return ([], def)
+ else
+ walkTree subtree path []
if isJust foundInTree then
return foundInTree
else
driftTo Done
) itr
)
- $ \ exc -> processException exc
+ processException
where
fork :: IO () -> IO ThreadId
- fork = if (resUsesNativeThread def)
+ fork = if resUsesNativeThread def
then forkOS
else forkIO
setHeader (C8.pack "Allow") (C8.pack $ joinWith ", " allowedMethods)
allowedMethods :: [String]
- allowedMethods = nub $ foldr (++) [] [ methods resGet ["GET"]
- , methods resHead ["GET", "HEAD"]
- , methods resPost ["POST"]
- , methods resPut ["PUT"]
- , methods resDelete ["DELETE"]
- ]
+ allowedMethods = nub $ concat [ methods resGet ["GET"]
+ , methods resHead ["GET", "HEAD"]
+ , methods resPost ["POST"]
+ , methods resPut ["PUT"]
+ , methods resDelete ["DELETE"]
+ ]
methods :: (ResourceDef -> Maybe a) -> [String] -> [String]
methods f xs = case f def of
if state <= DecidingHeader then
flip runRes itr
$ do setStatus $ aboStatus abo
- mapM_ (\ (name, value) -> setHeader name value) $ fromHeaders $ aboHeaders abo
+ mapM_ (uncurry setHeader) $ fromHeaders $ aboHeaders abo
output $ abortPage conf reqM res abo
else
when (cnfDumpTooLateAbortionToStderr $ itrConfig itr)
$ hPutStrLn stderr $ show abo
flip runRes itr $ driftTo Done
-
- formatIOE :: IOError -> String
- formatIOE ioE = if isUserError ioE then
- ioeGetErrorString ioE
- else
- show ioE