]> gitweb @ CieloNegro.org - Lucu.git/blobdiff - Network/HTTP/Lucu/Dispatcher/Internal.hs
It (at least) builds now...
[Lucu.git] / Network / HTTP / Lucu / Dispatcher / Internal.hs
diff --git a/Network/HTTP/Lucu/Dispatcher/Internal.hs b/Network/HTTP/Lucu/Dispatcher/Internal.hs
new file mode 100644 (file)
index 0000000..f8f3b12
--- /dev/null
@@ -0,0 +1,361 @@
+{-# LANGUAGE
+    DoAndIfThenElse
+  , ExistentialQuantification
+  , FlexibleInstances
+  , OverlappingInstances
+  , MultiParamTypeClasses
+  , RecordWildCards
+  , ScopedTypeVariables
+  , UndecidableInstances
+  , UnicodeSyntax
+  #-}
+module Network.HTTP.Lucu.Dispatcher.Internal
+    ( SchemeMapper(..)
+    , SchemeMap
+    , HostMapper(..)
+    , HostMap
+    , ResourceMapper(..)
+    , ResourceMap
+    , ResourceTree
+    , ResourceNode
+
+    , dispatch
+    )
+    where
+import Control.Applicative hiding (empty)
+import Control.Monad.Trans.Maybe
+import Control.Monad.Unicode
+import Data.Collections
+import qualified Data.Map as M
+import Data.Monoid
+import Data.Monoid.Unicode
+import Network.HTTP.Lucu.Resource.Internal
+import Network.HTTP.Lucu.Utils
+import Network.URI hiding (path)
+import Prelude hiding (filter, lookup, null)
+import Prelude.Unicode
+
+-- |FIXME: docs
+--
+-- Minimal complete definition: 'findHostMap'
+class SchemeMapper α where
+    findHostMap ∷ Scheme → α → MaybeT IO HostMap
+    schemeMap   ∷ α → SchemeMap
+    {-# INLINE schemeMap #-}
+    schemeMap   = SMap
+
+-- |Container type for the 'SchemeMapper' type class.
+data SchemeMap = ∀α. SchemeMapper α ⇒ SMap α
+
+-- |FIXME: docs
+--
+-- Minimal complete definition: 'findResourceMap'
+class HostMapper α where
+    findResourceMap ∷ Host → α → MaybeT IO ResourceMap
+    hostMap ∷ α → HostMap
+    {-# INLINE hostMap #-}
+    hostMap = HMap
+
+-- |Container type for the 'HostMapper' type class.
+data HostMap = ∀α. HostMapper α ⇒ HMap α
+
+-- |FIXME: docs
+--
+-- Minimal complete definition: 'findResource'
+class ResourceMapper α where
+    findResource ∷ PathSegments → α → MaybeT IO (PathSegments, Resource)
+    resourceMap  ∷ α → ResourceMap
+    {-# INLINE resourceMap #-}
+    resourceMap = RMap
+
+-- |Container type for the 'ResourceMapper' type class.
+data ResourceMap = ∀α. ResourceMapper α ⇒ RMap α
+
+-- FIXME: doc
+newtype ResourceTree = Root ResourceNode
+
+-- FIXME: docs
+data ResourceNode
+    = Greedy    !Resource
+    | NonGreedy !Resource !ResourceTree
+    | Branch              !ResourceTree
+
+-- Instances of SchemeMapper --------------------------------------------------
+instance SchemeMapper SchemeMap where
+    {-# INLINE findHostMap #-}
+    findHostMap s (SMap α) = findHostMap s α
+    {-# INLINE schemeMap #-}
+    schemeMap = id
+
+-- |'HostMap's are also 'SchemeMapper's too, which matches to any
+-- schemes.
+instance SchemeMapper HostMap where
+    {-# INLINE findHostMap #-}
+    findHostMap = const return
+
+-- |'ResourceMap's are also 'SchemeMapper's too, which matches to any
+-- schemes and hosts.
+instance SchemeMapper ResourceMap where
+    {-# INLINE findHostMap #-}
+    findHostMap _ r = return $ hostMap f
+        where
+          f ∷ Host → Maybe ResourceMap
+          {-# INLINE f #-}
+          f = const $ Just r
+
+-- |@'insert' a b@ first tries @a@, and if it fails, tries @b@ next.
+instance SchemeMapper α ⇒ Unfoldable SchemeMap α where
+    {-# INLINE insert #-}
+    insert a (SMap b) = schemeMap c
+        where
+          c ∷ Scheme → MaybeT IO HostMap
+          {-# INLINEABLE c #-}
+          c s = findHostMap s a <|> findHostMap s b
+    {-# INLINE empty #-}
+    empty = (∅)
+    {-# INLINE singleton #-}
+    singleton = schemeMap
+
+-- |@a `'mappend'` b@ first tries @a@, and if it fails, tries @b@ next.
+instance Monoid SchemeMap where
+    {-# INLINE mempty #-}
+    mempty = schemeMap e
+        where
+          e ∷ Scheme → MaybeT IO HostMap
+          {-# INLINE e #-}
+          e = const (fail (⊥))
+    {-# INLINE mappend #-}
+    mappend = insert
+
+instance Map α Scheme HostMap ⇒ SchemeMapper α where
+    {-# INLINE findHostMap #-}
+    findHostMap = (maybe (fail (⊥)) return ∘) ∘ lookup
+
+-- |An IO-based scheme mapper.
+instance SchemeMapper (Scheme → MaybeT IO HostMap) where
+    {-# INLINE findHostMap #-}
+    findHostMap = flip id
+
+-- |A pure scheme mapper.
+instance SchemeMapper (Scheme → Maybe HostMap) where
+    {-# INLINE findHostMap #-}
+    findHostMap = (maybe (fail (⊥)) return ∘) ∘ flip id
+
+
+-- Instances of HostMapper ----------------------------------------------------
+instance HostMapper HostMap where
+    {-# INLINE findResourceMap #-}
+    findResourceMap h (HMap α) = findResourceMap h α
+    {-# INLINE hostMap #-}
+    hostMap = id
+
+-- |'ResourceMap's are also 'HostMapper's too, which matches to any
+-- hosts.
+instance HostMapper ResourceMap where
+    {-# INLINE findResourceMap #-}
+    findResourceMap = const return
+
+-- |@'insert' a b@ first tries @a@, and if it fails, tries @b@ next.
+instance HostMapper α ⇒ Unfoldable HostMap α where
+    {-# INLINE insert #-}
+    insert a (HMap b) = hostMap c
+        where
+          c ∷ Host → MaybeT IO ResourceMap
+          {-# INLINEABLE c #-}
+          c h = findResourceMap h a <|> findResourceMap h b
+    {-# INLINE empty #-}
+    empty = (∅)
+    {-# INLINE singleton #-}
+    singleton = hostMap
+
+-- |@a `'mappend'` b@ first tries @a@, and if it fails, tries @b@ next.
+instance Monoid HostMap where
+    {-# INLINE mempty #-}
+    mempty = hostMap e
+        where
+          e ∷ Host → MaybeT IO ResourceMap
+          {-# INLINE e #-}
+          e = const (fail (⊥))
+    {-# INLINE mappend #-}
+    mappend = insert
+
+instance Map α Host ResourceMap ⇒ HostMapper α where
+    {-# INLINE findResourceMap #-}
+    findResourceMap = (maybe (fail (⊥)) return ∘) ∘ lookup
+
+-- |An IO-based host mapper.
+instance HostMapper (Host → MaybeT IO ResourceMap) where
+    {-# INLINE findResourceMap #-}
+    findResourceMap = flip id
+
+-- |A pure host mapper.
+instance HostMapper (Host → Maybe ResourceMap) where
+    {-# INLINE findResourceMap #-}
+    findResourceMap = (maybe (fail (⊥)) return ∘) ∘ flip id
+
+-- Instances of ResourceMapper ------------------------------------------------
+instance ResourceMapper ResourceMap where
+    {-# INLINE findResource #-}
+    findResource s (RMap α) = findResource s α
+    {-# INLINE resourceMap #-}
+    resourceMap = id
+
+-- |@'insert' a b@ first tries @a@, and if it fails, tries @b@ next.
+instance ResourceMapper α ⇒ Unfoldable ResourceMap α where
+    {-# INLINE insert #-}
+    insert a (RMap b) = resourceMap c
+        where
+          c ∷ PathSegments → MaybeT IO (PathSegments, Resource)
+          {-# INLINEABLE c #-}
+          c s = findResource s a <|> findResource s b
+    {-# INLINE empty #-}
+    empty = (∅)
+    {-# INLINE singleton #-}
+    singleton = resourceMap
+
+-- |@a `'mappend'` b@ first tries @a@, and if it fails, tries @b@ next.
+instance Monoid ResourceMap where
+    {-# INLINE mempty #-}
+    mempty = resourceMap e
+        where
+          e ∷ PathSegments → MaybeT IO (PathSegments, Resource)
+          {-# INLINE e #-}
+          e = const (fail (⊥))
+    {-# INLINE mappend #-}
+    mappend = insert
+
+instance Map α PathSegments (PathSegments, Resource) ⇒ ResourceMapper α where
+    {-# INLINE findResource #-}
+    findResource = (maybe (fail (⊥)) return ∘) ∘ lookup
+
+-- |An IO-based resource mapper.
+instance ResourceMapper (PathSegments → MaybeT IO (PathSegments, Resource)) where
+    {-# INLINE findResource #-}
+    findResource = flip id
+
+-- |A pure resource mapper.
+instance ResourceMapper (PathSegments → Maybe (PathSegments, Resource)) where
+    {-# INLINE findResource #-}
+    findResource = (maybe (fail (⊥)) return ∘) ∘ flip id
+
+-- Instances of ResourceTree --------------------------------------------------
+{-
+instance (Functor m, MonadIO m)
+       ⇒ Unfoldable (ResourceTree m) ([PathSegment], ResourceNode m) where
+    {-# INLINE insert #-}
+    insert e (Root root) = Root $ insert e root
+    {-# INLINE empty #-}
+    empty = (∅)
+
+instance (Functor m, MonadIO m) ⇒ Monoid (ResourceTree m) where
+    {-# INLINE mempty #-}
+    mempty = Root (∅)
+    {-# INLINE mappend #-}
+    mappend (Root a) (Root b)
+        = Root (a ⊕ b)
+-}
+
+-- Instances of ResourceNode --------------------------------------------------
+{-
+instance (Functor m, MonadIO m)
+       ⇒ Unfoldable (ResourceNode m) ([PathSegment], ResourceNode m) where
+    {-# INLINEABLE insert #-}
+    insert (p, a) b = insertNodeAt (canonPath p) a b
+    {-# INLINE empty #-}
+    empty = Branch (∅)
+
+canonPath ∷ (Collection c f, Foldable f e) ⇒ c → c
+{-# INLINEABLE canonPath #-}
+canonPath = filter ((¬) ∘ null)
+
+insertNodeAt ∷ (Functor m, MonadIO m)
+             ⇒ [PathSegment]
+             → ResourceNode m
+             → ResourceNode m
+             → ResourceNode m
+{-# INLINEABLE insertNodeAt #-}
+insertNodeAt []     a b = a ⊕ b
+insertNodeAt (x:[]) a b = Branch (singleton (x, a)) ⊕ b
+insertNodeAt (x:xs) a b = insertNodeAt xs a (∅) ⊕ b
+
+instance (Functor m, MonadIO m) ⇒ Monoid (ResourceNode m) where
+    {-# INLINE mempty #-}
+    mempty = Branch (∅)
+    {-# INLINEABLE mappend #-}
+    mappend _               (Greedy    r  ) = Greedy    r
+    mappend (Greedy    _  ) (NonGreedy r n) = NonGreedy r      n
+    mappend (NonGreedy _ m) (NonGreedy r n) = NonGreedy r (m ⊕ n)
+    mappend (Branch      m) (NonGreedy r n) = NonGreedy r (m ⊕ n)
+    mappend (Greedy    r  ) (Branch      _) = Greedy    r
+    mappend (NonGreedy r m) (Branch      n) = NonGreedy r (m ⊕ n)
+    mappend (Branch      m) (Branch      n) = Branch      (m ⊕ n)
+-}
+
+-- dispatch -------------------------------------------------------------------
+dispatch ∷ SchemeMapper α ⇒ URI → α → MaybeT IO (PathSegments, Resource)
+dispatch uri
+    = (findResource (uriPathSegments uri) =≪)
+      ∘ (findResourceMap (uriHost uri) =≪)
+      ∘ findHostMap (uriCIScheme uri)
+
+{-
+-- |'ResTree' is an opaque structure which is a map from resource path
+-- to 'Resource'.
+newtype ResTree = ResTree ResNode -- root だから Map ではない
+type ResSubtree = Map ByteString ResNode
+data ResNode    = ResNode (Maybe Resource) ResSubtree
+
+-- |'mkResTree' converts a list of @(path, def)@ to a 'ResTree' e.g.
+--
+-- @
+--   mkResTree [ ([]        , 'Network.HTTP.Lucu.StaticFile.staticFile' \"\/usr\/include\/stdio.h\" ) -- \/
+--             , ([\"unistd\"], 'Network.HTTP.Lucu.StaticFile.staticFile' \"\/usr\/include\/unistd.h\") -- \/unistd
+--             ]
+-- @
+--
+-- Note that path components are always represented as octet streams
+-- in this system. Lucu automatically decodes percent-encoded URIs but
+-- has no involvement in character encodings such as UTF-8, since RFC
+-- 2616 (HTTP/1.1) says nothing about character encodings to be used
+-- in \"http\" and \"https\" URI schemas.
+mkResTree ∷ [ ([ByteString], Resource) ] → ResTree
+mkResTree = processRoot ∘ map (first canonicalisePath)
+    where
+      canonicalisePath ∷ [ByteString] → [ByteString]
+      canonicalisePath = filter ((¬) ∘ BS.null)
+
+      processRoot ∷ [ ([ByteString], Resource) ] → ResTree
+      processRoot list
+          = let (roots, nonRoots) = partition (\(path, _) → null path) list
+                children = processNonRoot nonRoots
+            in
+              if null roots then
+                  -- 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)
+
+      processNonRoot ∷ [ ([ByteString], Resource) ] → ResSubtree
+      processNonRoot list
+          = let subtree    = M.fromList [(name, node name)
+                                             | name ← childNames]
+                childNames = [name | (name:_, _) ← list]
+                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]
+            in
+              subtree
+-}
\ No newline at end of file