----------------------------------------------------------------------------- -- | -- Module : Network.XmlRpc.Server -- Copyright : (c) Bjorn Bringert 2003 -- License : BSD-style -- -- Maintainer : bjorn@bringert.net -- Stability : experimental -- Portability : non-portable (requires extensions and non-portable libraries) -- -- This module contains the server functionality of XML-RPC. -- The XML-RPC specifcation is available at . -- -- A simple CGI-based XML-RPC server application: -- -- > import Network.XmlRpc.Server -- > -- > add :: Int -> Int -> IO Int -- > add x y = return (x + y) -- > -- > main = cgiXmlRpcServer [("examples.add", fun add)] ----------------------------------------------------------------------------- module Network.XmlRpc.Server ( XmlRpcMethod, ServerResult, fun, handleCall, methods, cgiXmlRpcServer, ) where import Network.XmlRpc.Internals import Data.ByteString.Lazy.Char8 (ByteString) import qualified Data.ByteString.Lazy.Char8 as B import Data.Maybe import Control.Monad.Error import Control.Exception import qualified Codec.Binary.UTF8.String as U import System.IO serverName = "Haskell XmlRpcServer/0.1" -- -- API -- type ServerResult = Err IO MethodResponse type Signature = ([Type], Type) -- | The type of XML-RPC methods on the server. type XmlRpcMethod = (MethodCall -> ServerResult, Signature) showException :: SomeException -> String showException = show handleIO :: IO a -> Err IO a handleIO io = lift (try io) >>= either (fail . showException) return -- -- Converting Haskell functions to XML-RPC methods -- -- | Turns any function -- @(XmlRpcType t1, ..., XmlRpcType tn, XmlRpcType r) => -- t1 -> ... -> tn -> IO r@ -- into an 'XmlRpcMethod' fun :: XmlRpcFun a => a -> XmlRpcMethod fun f = (toFun f, sig f) class XmlRpcFun a where toFun :: a -> MethodCall -> ServerResult sig :: a -> Signature instance XmlRpcType a => XmlRpcFun (IO a) where toFun x (MethodCall _ []) = do v <- handleIO x return (Return (toValue v)) toFun _ _ = fail "Too many arguments" sig x = ([], getType (mType x)) instance (XmlRpcType a, XmlRpcFun b) => XmlRpcFun (a -> b) where toFun f (MethodCall n (x:xs)) = do v <- fromValue x toFun (f v) (MethodCall n xs) toFun _ _ = fail "Too few arguments" sig f = let (a,b) = funType f (as, r) = sig b in (getType a : as, r) mType :: m a -> a mType _ = undefined funType :: (a -> b) -> (a, b) funType _ = (undefined, undefined) -- FIXME: always returns error code 0 errorToResponse :: ServerResult -> IO MethodResponse errorToResponse = handleError (return . Fault 0) -- | Reads a method call from a string, uses the supplied method -- to generate a response and returns that response as a string handleCall :: (MethodCall -> ServerResult) -> String -> IO ByteString handleCall f str = do resp <- errorToResponse (parseCall str >>= f) return (renderResponse resp) -- | An XmlRpcMethod that looks up the method name in a table -- and uses that method to handle the call. methods :: [(String,XmlRpcMethod)] -> MethodCall -> ServerResult methods t c@(MethodCall name _) = do (method,_) <- maybeToM ("Unknown method: " ++ name) (lookup name t) method c -- | A server with introspection support server :: [(String,XmlRpcMethod)] -> String -> IO ByteString server t = handleCall (methods (addIntrospection t)) -- -- Introspection -- addIntrospection :: [(String,XmlRpcMethod)] -> [(String,XmlRpcMethod)] addIntrospection t = t' where t' = ("system.listMethods", fun (listMethods t')) : ("system.methodSignature", fun (methodSignature t')) : ("system.methodHelp", fun (methodHelp t')) : t listMethods :: [(String,XmlRpcMethod)] -> IO [String] listMethods t = return (fst (unzip t)) methodSignature :: [(String,XmlRpcMethod)] -> String -> IO [[String]] methodSignature t name = do (_,(as,r)) <- maybeToM ("Unknown method: " ++ name) (lookup name t) return [map show (r:as)] methodHelp :: [(String,XmlRpcMethod)] -> String -> IO String methodHelp t name = do method <- maybeToM ("Unknown method: " ++ name) (lookup name t) return (help method) -- FIXME: implement help :: XmlRpcMethod -> String help m = "" -- -- CGI server -- -- | A CGI-based XML-RPC server. Reads a request from standard input -- and writes some HTTP headers (Content-Type and Content-Length), -- followed by the response to standard output. Supports -- introspection. cgiXmlRpcServer :: [(String,XmlRpcMethod)] -> IO () cgiXmlRpcServer ms = do hSetBinaryMode stdin True hSetBinaryMode stdout True input <- U.decodeString `fmap` getContents --output <- U.encodeString `fmap` server ms input output <- server ms input putStr ("Server: " ++ serverName ++ crlf) putStr ("Content-Type: text/xml" ++ crlf) putStr ("Content-Length: " ++ show (B.length output) ++ crlf) putStr crlf B.putStr output where crlf = "\r\n"