1 module Network.HTTP.Lucu.Resource.Tree
5 , mkResTree -- [ ([String], ResourceDef) ] -> ResTree
7 , findResource -- ResTree -> URI -> Maybe ResourceDef
8 , runResource -- ResourceDef -> Interaction -> IO ThreadId
12 import Control.Concurrent
13 import Control.Concurrent.STM
14 import Control.Exception
15 import Control.Monad.Reader
18 import qualified Data.Map as M
21 import Network.HTTP.Lucu.Abortion
22 import Network.HTTP.Lucu.Config
23 import Network.HTTP.Lucu.Request
24 import Network.HTTP.Lucu.Resource
25 import Network.HTTP.Lucu.Response
26 import Network.HTTP.Lucu.Interaction
27 import Network.HTTP.Lucu.Utils
30 import System.IO.Error hiding (catch)
31 import Prelude hiding (catch)
34 {- /aaa/bbb/ccc にアクセスされた時、もし /aaa/bbb に貪欲なリソースがあ
35 れば、假に /aaa/bbb/ccc に何らかのリソースがあったとしても必ず
36 /aaa/bbb が撰ばれる。/aaa/bbb のリソースが貪欲でなければ、それは無視
38 data ResourceDef = ResourceDef {
39 resUsesNativeThread :: Bool
41 , resGet :: Maybe (Resource ())
42 , resHead :: Maybe (Resource ())
43 , resPost :: Maybe (Resource ())
44 , resPut :: Maybe (Resource ())
45 , resDelete :: Maybe (Resource ())
47 type ResTree = ResNode -- root だから Map ではない
48 type ResSubtree = Map String ResNode
49 data ResNode = ResNode (Maybe ResourceDef) ResSubtree
52 mkResTree :: [ ([String], ResourceDef) ] -> ResTree
53 mkResTree list = processRoot list
55 processRoot :: [ ([String], ResourceDef) ] -> ResTree
57 = let (roots, nonRoots) = partition (\ (path, _) -> path == []) list
58 children = processNonRoot nonRoots
61 -- / にリソースが定義されない。/foo とかにはあるかも。
62 ResNode Nothing children
65 let (_, def) = last roots
67 ResNode (Just def) children
69 processNonRoot :: [ ([String], ResourceDef) ] -> ResSubtree
71 = let subtree = M.fromList [(name, node name)
73 childNames = [name | (name:_, _) <- list]
74 node name = let defs = [def | (path, def) <- list, path == [name]]
79 ResNode Nothing children
82 ResNode (Just $ last defs) children
83 children = processNonRoot [(path, def)
84 | (_:path, def) <- list, not (null path)]
89 findResource :: ResTree -> URI -> Maybe ResourceDef
90 findResource (ResNode rootDefM subtree) uri
91 = let pathStr = uriPath uri
92 path = [x | x <- splitBy (== '/') pathStr, x /= ""]
99 walkTree :: ResSubtree -> [String] -> Maybe ResourceDef
101 walkTree subtree (name:[])
102 = case M.lookup name subtree of
104 Just (ResNode defM _) -> defM
106 walkTree subtree (x:xs)
107 = case M.lookup x subtree of
109 Just (ResNode defM children) -> case defM of
110 Just (ResourceDef { resIsGreedy = True })
112 _ -> walkTree children xs
115 runResource :: ResourceDef -> Interaction -> IO ThreadId
118 $ catch ( runReaderT ( do fromMaybe notAllowed rsrc
122 $ \ exc -> processException exc
124 fork :: IO () -> IO ThreadId
125 fork = if (resUsesNativeThread def)
129 rsrc :: Maybe (Resource ())
130 rsrc = case reqMethod $ fromJust $ itrRequest itr of
132 HEAD -> case resHead def of
134 Nothing -> resGet def
137 DELETE -> resDelete def
139 notAllowed :: Resource ()
140 notAllowed = do setStatus MethodNotAllowed
141 setHeader "Allow" $ joinWith ", " allowedMethods
143 allowedMethods :: [String]
144 allowedMethods = nub $ foldr (++) [] [ methods resGet ["GET"]
145 , methods resHead ["GET", "HEAD"]
146 , methods resPost ["POST"]
147 , methods resPut ["PUT"]
148 , methods resDelete ["DELETE"]
151 methods :: (ResourceDef -> Maybe a) -> [String] -> [String]
152 methods f xs = case f def of
156 processException :: Exception -> IO ()
158 = do let abo = case exc of
159 ErrorCall msg -> Abortion InternalServerError [] $ Just msg
160 IOException ioE -> Abortion InternalServerError [] $ Just $ formatIOE ioE
161 DynException dynE -> case fromDynamic dynE of
162 Just (abo :: Abortion) -> abo
164 -> Abortion InternalServerError []
166 _ -> Abortion InternalServerError [] $ Just $ show exc
168 reqM = itrRequest itr
169 -- まだ DecidingHeader 以前の状態だったら、この途中終了
170 -- を應答に反映させる餘地がある。さうでなければ stderr
172 state <- atomically $ readItr itr itrState id
173 resM <- atomically $ readItr itr itrResponse id
174 if state <= DecidingHeader then
176 $ do setStatus $ aboStatus abo
177 -- FIXME: 同じ名前で複數の値があった時は、こ
179 mapM_ (\ (name, value) -> setHeader name value) $ aboHeaders abo
180 setHeader "Content-Type" "application/xhtml+xml"
181 output $ abortPage conf reqM resM abo
183 hPutStrLn stderr $ show abo
185 flip runReaderT itr $ driftTo Done
187 formatIOE :: IOError -> String
188 formatIOE ioE = if isUserError ioE then
189 ioeGetErrorString ioE