1 module Network.HTTP.Lucu.Resource.Tree
5 , mkResTree -- [ ([String], ResourceDef) ] -> ResTree
7 , findResource -- ResTree -> URI -> Maybe ([String], 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.MIMEType
24 import Network.HTTP.Lucu.Request
25 import Network.HTTP.Lucu.Resource
26 import Network.HTTP.Lucu.Response
27 import Network.HTTP.Lucu.Interaction
28 import Network.HTTP.Lucu.Utils
31 import System.IO.Error hiding (catch)
32 import Prelude hiding (catch)
35 {- /aaa/bbb/ccc にアクセスされた時、もし /aaa/bbb に貪欲なリソースがあ
36 れば、假に /aaa/bbb/ccc に何らかのリソースがあったとしても必ず
37 /aaa/bbb が撰ばれる。/aaa/bbb のリソースが貪欲でなければ、それは無視
39 data ResourceDef = ResourceDef {
40 resUsesNativeThread :: Bool
42 , resGet :: Maybe (Resource ())
43 , resHead :: Maybe (Resource ())
44 , resPost :: Maybe (Resource ())
45 , resPut :: Maybe (Resource ())
46 , resDelete :: Maybe (Resource ())
48 type ResTree = ResNode -- root だから Map ではない
49 type ResSubtree = Map String ResNode
50 data ResNode = ResNode (Maybe ResourceDef) ResSubtree
53 mkResTree :: [ ([String], ResourceDef) ] -> ResTree
54 mkResTree list = processRoot list
56 processRoot :: [ ([String], ResourceDef) ] -> ResTree
58 = let (roots, nonRoots) = partition (\ (path, _) -> path == []) list
59 children = processNonRoot nonRoots
62 -- / にリソースが定義されない。/foo とかにはあるかも。
63 ResNode Nothing children
66 let (_, def) = last roots
68 ResNode (Just def) children
70 processNonRoot :: [ ([String], ResourceDef) ] -> ResSubtree
72 = let subtree = M.fromList [(name, node name)
74 childNames = [name | (name:_, _) <- list]
75 node name = let defs = [def | (path, def) <- list, path == [name]]
80 ResNode Nothing children
83 ResNode (Just $ last defs) children
84 children = processNonRoot [(path, def)
85 | (_:path, def) <- list, not (null path)]
90 findResource :: ResTree -> URI -> Maybe ([String], ResourceDef)
91 findResource (ResNode rootDefM subtree) uri
92 = let pathStr = uriPath uri
93 path = [x | x <- splitBy (== '/') pathStr, x /= ""]
99 walkTree subtree path []
101 walkTree :: ResSubtree -> [String] -> [String] -> Maybe ([String], ResourceDef)
103 walkTree subtree (name:[]) soFar
104 = case M.lookup name subtree of
106 Just (ResNode defM _) -> do def <- defM
107 return (soFar ++ [name], def)
109 walkTree subtree (x:xs) soFar
110 = case M.lookup x subtree of
112 Just (ResNode defM children) -> case defM of
113 Just (ResourceDef { resIsGreedy = True })
115 return (soFar ++ [x], def)
116 _ -> walkTree children xs (soFar ++ [x])
119 runResource :: ResourceDef -> Interaction -> IO ThreadId
122 $ catch ( runReaderT ( do fromMaybe notAllowed rsrc
126 $ \ exc -> processException exc
128 fork :: IO () -> IO ThreadId
129 fork = if (resUsesNativeThread def)
133 rsrc :: Maybe (Resource ())
134 rsrc = case reqMethod $ fromJust $ itrRequest itr of
136 HEAD -> case resHead def of
138 Nothing -> resGet def
141 DELETE -> resDelete def
143 notAllowed :: Resource ()
144 notAllowed = do setStatus MethodNotAllowed
145 setHeader "Allow" $ joinWith ", " allowedMethods
147 allowedMethods :: [String]
148 allowedMethods = nub $ foldr (++) [] [ methods resGet ["GET"]
149 , methods resHead ["GET", "HEAD"]
150 , methods resPost ["POST"]
151 , methods resPut ["PUT"]
152 , methods resDelete ["DELETE"]
155 methods :: (ResourceDef -> Maybe a) -> [String] -> [String]
156 methods f xs = case f def of
160 processException :: Exception -> IO ()
162 = do let abo = case exc of
163 ErrorCall msg -> Abortion InternalServerError [] $ Just msg
164 IOException ioE -> Abortion InternalServerError [] $ Just $ formatIOE ioE
165 DynException dynE -> case fromDynamic dynE of
166 Just (abo :: Abortion) -> abo
168 -> Abortion InternalServerError []
170 _ -> Abortion InternalServerError [] $ Just $ show exc
172 reqM = itrRequest itr
173 -- まだ DecidingHeader 以前の状態だったら、この途中終了
174 -- を應答に反映させる餘地がある。さうでなければ stderr
176 state <- atomically $ readItr itr itrState id
177 resM <- atomically $ readItr itr itrResponse id
178 if state <= DecidingHeader then
180 $ do setStatus $ aboStatus abo
181 -- FIXME: 同じ名前で複數の値があった時は、こ
183 mapM_ (\ (name, value) -> setHeader name value) $ aboHeaders abo
184 setContentType ("application" +/+ "xhtml+xml")
185 output $ abortPage conf reqM resM abo
187 hPutStrLn stderr $ show abo
189 flip runReaderT itr $ driftTo Done
191 formatIOE :: IOError -> String
192 formatIOE ioE = if isUserError ioE then
193 ioeGetErrorString ioE