module Network.HTTP.Lucu.Resource.Tree
( ResourceDef(..)
, ResTree
+ , FallbackHandler
+
, mkResTree -- [ ([String], ResourceDef) ] -> ResTree
, findResource -- ResTree -> URI -> Maybe ([String], ResourceDef)
import Control.Concurrent.STM
import Control.Exception
import Control.Monad
+import qualified Data.ByteString.Char8 as C8
import Data.Dynamic
import Data.List
import qualified Data.Map as M
import Data.Maybe
import Network.HTTP.Lucu.Abortion
import Network.HTTP.Lucu.Config
+import Network.HTTP.Lucu.Headers (emptyHeaders, fromHeaders)
import Network.HTTP.Lucu.Request
import Network.HTTP.Lucu.Resource
import Network.HTTP.Lucu.Response
import Network.HTTP.Lucu.Interaction
import Network.HTTP.Lucu.Utils
-import Network.URI
+import Network.URI hiding (path)
import System.IO
import System.IO.Error hiding (catch)
import Prelude hiding (catch)
+-- |'FallbackHandler' is an extra resource handler for resources which
+-- can't be statically located somewhere in the resource tree. The
+-- Lucu httpd first search for a resource in the tree, and then call
+-- fallback handlers to ask them for a resource. If all of the
+-- handlers returned 'Prelude.Nothing', the httpd responds with 404
+-- Not Found.
+type FallbackHandler = [String] -> IO (Maybe ResourceDef)
+
+
-- "/aaa/bbb/ccc" にアクセスされた時、もし "/aaa/bbb" に貪欲なリソース
-- があれば、假に "/aaa/bbb/ccc" に何らかのリソースがあったとしても必ず
-- "/aaa/bbb" が撰ばれる。"/aaa/bbb" のリソースが貪欲でなければ、それは
-- greedy resource at \/aaa\/bbb, it is always chosen even if
-- there is another resource at \/aaa\/bbb\/ccc. If the resource
-- at \/aaa\/bbb is not greedy, it is just ignored. Greedy
- -- resource is like a CGI script.
+ -- resources are like CGI scripts.
, resIsGreedy :: !Bool
-- | A 'Network.HTTP.Lucu.Resource.Resource' to be run when a GET
-- request comes for the resource path. If 'resGet' is Nothing,
, resDelete :: !(Maybe (Resource ()))
}
--- | 'ResTree' is an opaque structure which is a map from resource
--- path to 'ResourceDef'.
-type ResTree = ResNode -- root だから Map ではない
+-- |'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
--- | 'mkResTree' converts a list of @(path, def)@ to a 'ResTree' e.g.
+-- |'mkResTree' converts a list of @(path, def)@ to a 'ResTree' e.g.
--
-- @
-- mkResTree [ ([] , 'Network.HTTP.Lucu.StaticFile.staticFile' \"\/usr\/include\/stdio.h\" ) -- \/
-- ]
-- @
mkResTree :: [ ([String], ResourceDef) ] -> ResTree
-mkResTree list = list `seq` processRoot list
+mkResTree xs = xs `seq` processRoot xs
where
processRoot :: [ ([String], ResourceDef) ] -> ResTree
processRoot list
in
if null roots then
-- "/" にリソースが定義されない。"/foo" とかにはあるかも。
- ResNode Nothing children
+ ResTree (ResNode Nothing children)
else
-- "/" がある。
let (_, def) = last roots
in
- ResNode (Just def) children
+ ResTree (ResNode (Just def) children)
processNonRoot :: [ ([String], ResourceDef) ] -> ResSubtree
processNonRoot list
subtree
-findResource :: ResTree -> URI -> Maybe ([String], ResourceDef)
-findResource (ResNode rootDefM subtree) uri
- = let pathStr = uriPath uri
- path = [x | x <- splitBy (== '/') pathStr, x /= ""]
- in
- if null path then
- do def <- rootDefM
- return (path, def)
- else
- walkTree subtree path []
+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 []
+ if isJust foundInTree then
+ return foundInTree
+ else
+ fallback path fbs
where
walkTree :: ResSubtree -> [String] -> [String] -> Maybe ([String], ResourceDef)
- walkTree subtree (name:[]) soFar
- = case M.lookup name subtree of
+ walkTree tree (name:[]) soFar
+ = case M.lookup name tree of
Nothing -> Nothing
Just (ResNode defM _) -> do def <- defM
return (soFar ++ [name], def)
- walkTree subtree (x:xs) soFar
- = case M.lookup x subtree of
+ walkTree tree (x:xs) soFar
+ = case M.lookup x tree of
Nothing -> Nothing
Just (ResNode defM children) -> case defM of
Just (ResourceDef { resIsGreedy = True })
return (soFar ++ [x], def)
_ -> walkTree children xs (soFar ++ [x])
+ fallback :: [String] -> [FallbackHandler] -> IO (Maybe ([String], ResourceDef))
+ fallback _ [] = return Nothing
+ fallback path (x:xs) = do m <- x path
+ case m of
+ Just def -> return $! Just ([], def)
+ Nothing -> fallback path xs
+
runResource :: ResourceDef -> Interaction -> IO ThreadId
runResource def itr
POST -> resPost def
PUT -> resPut def
DELETE -> resDelete def
+ _ -> undefined
notAllowed :: Resource ()
notAllowed = do setStatus MethodNotAllowed
- setHeader "Allow" $ joinWith ", " allowedMethods
+ setHeader (C8.pack "Allow") (C8.pack $ joinWith ", " allowedMethods)
allowedMethods :: [String]
allowedMethods = nub $ foldr (++) [] [ methods resGet ["GET"]
processException :: Exception -> IO ()
processException exc
= do let abo = case exc of
- ErrorCall msg -> Abortion InternalServerError [] $ Just msg
- IOException ioE -> Abortion InternalServerError [] $ Just $ formatIOE ioE
+ ErrorCall msg -> Abortion InternalServerError emptyHeaders $ Just msg
+ IOException ioE -> Abortion InternalServerError emptyHeaders $ Just $ formatIOE ioE
DynException dynE -> case fromDynamic dynE of
- Just (abo :: Abortion) -> abo
+ Just a
+ -> a :: Abortion
Nothing
- -> Abortion InternalServerError []
+ -> Abortion InternalServerError emptyHeaders
$ Just $ show exc
- _ -> Abortion InternalServerError [] $ Just $ show exc
+ _ -> Abortion InternalServerError emptyHeaders $ Just $ show exc
conf = itrConfig itr
-- まだ DecidingHeader 以前の状態だったら、この途中終了
-- を應答に反映させる餘地がある。さうでなければ stderr
if state <= DecidingHeader then
flip runRes itr
$ do setStatus $ aboStatus abo
- -- FIXME: 同じ名前で複數の値があった時は、こ
- -- れではまずいと思ふ。
- mapM_ (\ (name, value) -> setHeader name value) $ aboHeaders abo
+ mapM_ (\ (name, value) -> setHeader name value) $ fromHeaders $ aboHeaders abo
output $ abortPage conf reqM res abo
else
when (cnfDumpTooLateAbortionToStderr $ itrConfig itr)