2 ExistentialQuantification
5 , GeneralizedNewtypeDeriving
7 , MultiParamTypeClasses
12 module Network.HTTP.Lucu.Dispatcher.Internal
27 import Control.Applicative hiding (empty)
28 import Control.Monad.Trans.Maybe
29 import Control.Monad.Unicode
30 import Data.Collections
31 import qualified Data.Map as M
33 import Data.Monoid.Unicode
34 import Network.HTTP.Lucu.Resource.Internal
35 import Network.HTTP.Lucu.Utils
36 import Network.URI hiding (path)
37 import Prelude hiding (filter, lookup, null)
38 import Prelude.Unicode
42 -- Minimal complete definition: 'findHostMap'
43 class SchemeMapper α where
44 findHostMap ∷ Scheme → α → MaybeT IO HostMap
45 schemeMap ∷ α → SchemeMap
46 {-# INLINE schemeMap #-}
49 -- |Container type for the 'SchemeMapper' type class.
50 data SchemeMap = ∀α. SchemeMapper α ⇒ SMap α
54 -- Minimal complete definition: 'findResourceMap'
55 class HostMapper α where
56 findResourceMap ∷ Host → α → MaybeT IO ResourceMap
58 {-# INLINE hostMap #-}
61 -- |Container type for the 'HostMapper' type class.
62 data HostMap = ∀α. HostMapper α ⇒ HMap α
66 -- Minimal complete definition: 'findResource'
67 class ResourceMapper α where
68 findResource ∷ PathSegments → α → MaybeT IO (PathSegments, Resource)
69 resourceMap ∷ α → ResourceMap
70 {-# INLINE resourceMap #-}
73 -- |Container type for the 'ResourceMapper' type class.
74 data ResourceMap = ∀α. ResourceMapper α ⇒ RMap α
77 newtype ResourceTree = Tree (M.Map PathSegments ResourceNode)
82 = Greedy { nResource ∷ !Resource }
83 | NonGreedy { nResource ∷ !Resource }
86 greedy ∷ Resource → ResourceNode
87 {-# INLINE CONLIKE greedy #-}
91 nonGreedy ∷ Resource → ResourceNode
92 {-# INLINE CONLIKE nonGreedy #-}
95 -- Instances of SchemeMapper --------------------------------------------------
96 instance SchemeMapper SchemeMap where
97 {-# INLINE findHostMap #-}
98 findHostMap s (SMap α) = findHostMap s α
99 {-# INLINE schemeMap #-}
102 -- |'HostMap's are also 'SchemeMapper's too, which matches to any
104 instance SchemeMapper HostMap where
105 {-# INLINE findHostMap #-}
106 findHostMap = const return
108 -- |'ResourceMap's are also 'SchemeMapper's too, which matches to any
109 -- schemes and hosts.
110 instance SchemeMapper ResourceMap where
111 {-# INLINE findHostMap #-}
112 findHostMap _ r = return $ hostMap f
114 f ∷ Host → Maybe ResourceMap
118 -- |@'insert' a b@ first tries @a@, and if it fails, tries @b@ next.
119 instance SchemeMapper α ⇒ Unfoldable SchemeMap α where
120 {-# INLINE insert #-}
121 insert a (SMap b) = schemeMap c
123 c ∷ Scheme → MaybeT IO HostMap
125 c s = findHostMap s a <|> findHostMap s b
128 {-# INLINE singleton #-}
129 singleton = schemeMap
131 -- |@'mappend' a b@ first tries @a@, and if it fails, tries @b@ next.
132 instance Monoid SchemeMap where
133 {-# INLINE mempty #-}
136 e ∷ Scheme → MaybeT IO HostMap
139 {-# INLINE mappend #-}
142 -- |Any 'Map's from 'Scheme' to 'HostMap' are also 'SchemeMapper's.
143 instance Map α Scheme HostMap ⇒ SchemeMapper α where
144 {-# INLINE findHostMap #-}
145 findHostMap = (maybe (fail (⊥)) return ∘) ∘ lookup
147 -- |An IO-based scheme mapper.
148 instance SchemeMapper (Scheme → MaybeT IO HostMap) where
149 {-# INLINE findHostMap #-}
150 findHostMap = flip id
152 -- |A pure scheme mapper.
153 instance SchemeMapper (Scheme → Maybe HostMap) where
154 {-# INLINE findHostMap #-}
155 findHostMap = (maybe (fail (⊥)) return ∘) ∘ flip id
158 -- Instances of HostMapper ----------------------------------------------------
159 instance HostMapper HostMap where
160 {-# INLINE findResourceMap #-}
161 findResourceMap h (HMap α) = findResourceMap h α
162 {-# INLINE hostMap #-}
165 -- |'ResourceMap's are also 'HostMapper's too, which matches to any
167 instance HostMapper ResourceMap where
168 {-# INLINE findResourceMap #-}
169 findResourceMap = const return
171 -- |@'insert' a b@ first tries @a@, and if it fails, tries @b@ next.
172 instance HostMapper α ⇒ Unfoldable HostMap α where
173 {-# INLINE insert #-}
174 insert a (HMap b) = hostMap c
176 c ∷ Host → MaybeT IO ResourceMap
178 c h = findResourceMap h a <|> findResourceMap h b
181 {-# INLINE singleton #-}
184 -- |@'mappend' a b@ first tries @a@, and if it fails, tries @b@ next.
185 instance Monoid HostMap where
186 {-# INLINE mempty #-}
189 e ∷ Host → MaybeT IO ResourceMap
192 {-# INLINE mappend #-}
195 -- |Any 'Map's from 'Host' to 'ResourceMap' are also 'HostMapper's.
196 instance Map α Host ResourceMap ⇒ HostMapper α where
197 {-# INLINE findResourceMap #-}
198 findResourceMap = (maybe (fail (⊥)) return ∘) ∘ lookup
200 -- |An IO-based host mapper.
201 instance HostMapper (Host → MaybeT IO ResourceMap) where
202 {-# INLINE findResourceMap #-}
203 findResourceMap = flip id
205 -- |A pure host mapper.
206 instance HostMapper (Host → Maybe ResourceMap) where
207 {-# INLINE findResourceMap #-}
208 findResourceMap = (maybe (fail (⊥)) return ∘) ∘ flip id
210 -- Instances of ResourceMapper ------------------------------------------------
211 instance ResourceMapper ResourceMap where
212 {-# INLINE findResource #-}
213 findResource s (RMap α) = findResource s α
214 {-# INLINE resourceMap #-}
217 -- |@'insert' a b@ first tries @a@, and if it fails, tries @b@ next.
218 instance ResourceMapper α ⇒ Unfoldable ResourceMap α where
219 {-# INLINE insert #-}
220 insert a (RMap b) = resourceMap c
222 c ∷ PathSegments → MaybeT IO (PathSegments, Resource)
224 c s = findResource s a <|> findResource s b
227 {-# INLINE singleton #-}
228 singleton = resourceMap
230 -- |@'mappend' a b@ first tries @a@, and if it fails, tries @b@ next.
231 instance Monoid ResourceMap where
232 {-# INLINE mempty #-}
233 mempty = resourceMap e
235 e ∷ PathSegments → MaybeT IO (PathSegments, Resource)
238 {-# INLINE mappend #-}
241 -- |Any 'Map's from 'PathSegments' to @('PathSegments', 'Resource')@
242 -- are also 'ResourceMapper's.
243 instance Map α PathSegments (PathSegments, Resource) ⇒ ResourceMapper α where
244 {-# INLINE findResource #-}
245 findResource = (maybe (fail (⊥)) return ∘) ∘ lookup
247 -- |An IO-based resource mapper.
248 instance ResourceMapper (PathSegments → MaybeT IO (PathSegments, Resource)) where
249 {-# INLINE findResource #-}
250 findResource = flip id
252 -- |A pure resource mapper.
253 instance ResourceMapper (PathSegments → Maybe (PathSegments, Resource)) where
254 {-# INLINE findResource #-}
255 findResource = (maybe (fail (⊥)) return ∘) ∘ flip id
257 -- Instances of ResourceTree --------------------------------------------------
258 instance Unfoldable ResourceTree (PathSegments, ResourceNode) where
259 {-# INLINEABLE insert #-}
260 insert (p, n) (Tree m) = Tree $ insert (canonPath p, n) m
263 {-# INLINE singleton #-}
264 singleton = Tree ∘ singleton
266 canonPath ∷ (Collection c f, Foldable f e) ⇒ c → c
267 {-# INLINEABLE canonPath #-}
268 canonPath = filter ((¬) ∘ null)
270 -- |'findResource' performs the longest prefix match on the tree,
271 -- finding the most specific one.
272 instance ResourceMapper ResourceTree where
273 {-# INLINEABLE findResource #-}
274 findResource p (Tree m)
276 Just n → return (p, nResource n)
277 Nothing → findGreedyResource p m
279 findGreedyResource ∷ (Monad m, Map α PathSegments ResourceNode)
282 → MaybeT m (PathSegments, Resource)
283 findGreedyResource p m
286 Just (p', _) → case lookup p' m of
289 _ → findGreedyResource p' m
291 -- dispatch -------------------------------------------------------------------
292 dispatch ∷ SchemeMapper α ⇒ URI → α → MaybeT IO (PathSegments, Resource)
294 = (findResource (uriPathSegments uri) =≪)
295 ∘ (findResourceMap (uriHost uri) =≪)
296 ∘ findHostMap (uriCIScheme uri)
299 -- |'ResTree' is an opaque structure which is a map from resource path
301 newtype ResTree = ResTree ResNode -- root だから Map ではない
302 type ResSubtree = Map ByteString ResNode
303 data ResNode = ResNode (Maybe Resource) ResSubtree
305 -- |'mkResTree' converts a list of @(path, def)@ to a 'ResTree' e.g.
308 -- mkResTree [ ([] , 'Network.HTTP.Lucu.StaticFile.staticFile' \"\/usr\/include\/stdio.h\" ) -- \/
309 -- , ([\"unistd\"], 'Network.HTTP.Lucu.StaticFile.staticFile' \"\/usr\/include\/unistd.h\") -- \/unistd
313 -- Note that path components are always represented as octet streams
314 -- in this system. Lucu automatically decodes percent-encoded URIs but
315 -- has no involvement in character encodings such as UTF-8, since RFC
316 -- 2616 (HTTP/1.1) says nothing about character encodings to be used
317 -- in \"http\" and \"https\" URI schemas.
318 mkResTree ∷ [ ([ByteString], Resource) ] → ResTree
319 mkResTree = processRoot ∘ map (first canonicalisePath)
321 canonicalisePath ∷ [ByteString] → [ByteString]
322 canonicalisePath = filter ((¬) ∘ BS.null)
324 processRoot ∷ [ ([ByteString], Resource) ] → ResTree
326 = let (roots, nonRoots) = partition (\(path, _) → null path) list
327 children = processNonRoot nonRoots
330 -- The root has no resources. Maybe there's one at
331 -- somewhere like "/foo".
332 ResTree (ResNode Nothing children)
334 -- There is a root resource.
335 let (_, def) = last roots
337 ResTree (ResNode (Just def) children)
339 processNonRoot ∷ [ ([ByteString], Resource) ] → ResSubtree
341 = let subtree = M.fromList [(name, node name)
343 childNames = [name | (name:_, _) ← list]
344 node name = let defs = [def | (path, def) ← list, path ≡ [name]]
347 -- No resources are defined
348 -- here. Maybe there's one at
349 -- somewhere below this node.
350 ResNode Nothing children
352 -- There is a resource here.
353 ResNode (Just $ last defs) children
354 children = processNonRoot [(path, def)
355 | (_:path, def) ← list]