bert (empty) → 1.0
raw patch · 9 files changed
+453/−0 lines, 9 filesdep +basedep +binarydep +bytestringsetup-changed
Dependencies added: base, binary, bytestring, containers, mtl, network, network-bytestring, parsec, time, unix
Files
- Data/BERT.hs +21/−0
- LICENSE +24/−0
- Network/BERT.hs +30/−0
- Network/BERT/Client.hs +51/−0
- Network/BERT/Server.hs +87/−0
- Network/BERT/Transport.hs +140/−0
- Setup.hs +2/−0
- bert.cabal +29/−0
- bert.hs +69/−0
+ Data/BERT.hs view
@@ -0,0 +1,21 @@+-- |+-- Module : Data.BERT+-- Copyright : (c) marius a. eriksen 2009+-- +-- License : BSD3+-- Maintainer : marius@monkey.org+-- Stability : experimental+-- Portability : GHC+-- +-- BERT (Erlang terms) implementation. See <http://bert-rpc.org/> and+-- <http://erlang.org/doc/apps/erts/erl_ext_dist.html> for more+-- details.+module Data.BERT + ( module Data.BERT.Types+ , module Data.BERT.Term+ , module Data.BERT.Packet+ ) where++import Data.BERT.Types+import Data.BERT.Term+import Data.BERT.Packet
+ LICENSE view
@@ -0,0 +1,24 @@+Copyright (c) 2009 marius a. eriksen (marius@monkey.org)+All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:+1. Redistributions of source code must retain the above copyright+ notice, this list of conditions and the following disclaimer.+2. Redistributions in binary form must reproduce the above copyright+ notice, this list of conditions and the following disclaimer in the+ documentation and/or other materials provided with the distribution.+3. The names of the authors may not be used to endorse or promote products+ derived from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES+OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT+NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF+THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Network/BERT.hs view
@@ -0,0 +1,30 @@+-- |+-- Module : Network.BERT+-- Copyright : (c) marius a. eriksen 2009+-- +-- License : BSD3+-- Maintainer : marius@monkey.org+-- Stability : experimental+-- Portability : GHC+-- +-- BERT-RPC client (<http://bert-rpc.org/>). See+-- "Network.BERT.Transport" and "Network.BERT.RPC" for more details.+module Network.BERT+ ( module Network.BERT.Transport+ , module Network.BERT.Client+ , module Network.BERT.Server+ -- * Example+ -- $example+ ) where++import Network.BERT.Transport+import Network.BERT.Client+import Network.BERT.Server++-- $example+-- +-- > t <- fromURI "bert://localhost:8000"+-- > r <- call t "errorcalc" "add" ([123, 300]::[Int])+-- > case r of +-- > Right res -> print (res::Int)+-- > Left e -> print e
+ Network/BERT/Client.hs view
@@ -0,0 +1,51 @@+-- |+-- Module : Network.BERT.Client+-- Copyright : (c) marius a. eriksen 2009+-- +-- License : BSD3+-- Maintainer : marius@monkey.org+-- Stability : experimental+-- Portability : GHC+-- +-- BERT-RPC client (<http://bert-rpc.org/>). This implements the+-- client RPC call logic.++module Network.BERT.Client+ ( Call, call + ) where++import Data.BERT (Term(..), Packet(..), BERT(..))+import Network.BERT.Transport (Transport, withTransport, sendt, recvt)++data Error + = ClientError String+ | ServerError Term+ deriving (Show, Ord, Eq)++-- | Convenience type for @call@+type Call a = IO (Either Error a)++-- | Call the @{mod, func, args}@ synchronously on the endpoint+-- defined by @transport@, returning the results of the call or an+-- error.+call :: (BERT a, BERT b)+ => Transport + -> String + -> String+ -> [a]+ -> Call b+call transport mod fun args = + withTransport transport $ do+ sendt $ TupleTerm [AtomTerm "call", AtomTerm mod, AtomTerm fun, + ListTerm $ map showBERT args]+ recvt >>= handle+ where+ handle (TupleTerm [AtomTerm "reply", reply]) =+ return $ either (const . Left $ ClientError "decode failed") Right+ $ readBERT reply+ handle (TupleTerm (AtomTerm "info":_)) = + recvt >>= handle -- We don't yet handle info directives.+ handle t@(TupleTerm (AtomTerm "error":_)) =+ return $ Left . ServerError $ t+ handle t = fail $ "unknown reply " ++ (show t)+
+ Network/BERT/Server.hs view
@@ -0,0 +1,87 @@+-- |+-- Module : Network.BERT.Server+-- Copyright : (c) marius a. eriksen 2009+-- +-- License : BSD3+-- Maintainer : marius@monkey.org+-- Stability : experimental+-- Portability : GHC+-- +-- BERT-RPC server (<http://bert-rpc.org/>). This implements the+-- client RPC call/reply logic. Only synchronous requests are+-- supported at this time.++module Network.BERT.Server + ( DispatchError(..)+ -- ** Serve+ -- $example+ , serve+ ) where++import Control.Concurrent (forkIO)+import Control.Monad.Trans (liftIO)+import Network.BERT.Transport (Transport, withTransport, servet, recvt, sendt)+import Data.ByteString.Lazy.Char8 as C+import Data.BERT (Term(..))+import Text.Printf (printf)++-- TODO: just do DispatchResult?++data DispatchError + = NoSuchModule+ | NoSuchFunction+ | Undesignated String+ deriving (Eq, Show, Ord)++-- | Serve from the given transport (forever), handling each request+-- with the given dispatch function in a new thread.+serve :: Transport + -> (String -> String -> [Term] -> IO (Either DispatchError Term))+ -> IO ()+serve transport dispatch =+ servet transport $ \t ->+ (forkIO $ withTransport t $ handleCall dispatch) >> return ()++handleCall dispatch =+ recvt >>= handle+ where+ handle (TupleTerm [AtomTerm "info", AtomTerm "stream", _]) =+ sendErr "server" 0 "BERTError" "streams are unsupported" []+ handle (TupleTerm [AtomTerm "info", AtomTerm "cache", _]) =+ recvt >>= handle -- Ignore caching requests.+ handle (TupleTerm [+ AtomTerm "call", AtomTerm mod, + AtomTerm fun, ListTerm args]) = do+ res <- liftIO $ dispatch mod fun args+ case res of+ Left NoSuchModule ->+ sendErr "server" 1 "BERTError" + (printf "no such module \"%s\"" mod :: String) []+ Left NoSuchFunction ->+ sendErr "server" 2 "BERTError" + (printf "no such function \"%s\"" fun :: String) []+ Left (Undesignated detail) ->+ sendErr "server" 0 "HandlerError" detail []+ Right term -> + sendt $ TupleTerm [AtomTerm "reply", term]++ sendErr etype ecode eclass detail backtrace = + sendt $ TupleTerm [+ AtomTerm "error", + TupleTerm [+ AtomTerm etype, IntTerm ecode, BinaryTerm . C.pack $ eclass, + ListTerm $ Prelude.map (BinaryTerm . C.pack) backtrace]]++-- $example+-- +-- To serve requests, create a transport and call 'serve' with a+-- dispatch function.+-- +-- > main = do+-- > t <- fromHostPort "" 8080+-- > serve t dispatch+-- >+-- > dispatch "calc" "add" [IntTerm a, IntTerm b] = +-- > return $ Right $ IntTerm (a + b)+-- > dispatch _ _ _ = do+-- > return $ Left "no such m/f!"
+ Network/BERT/Transport.hs view
@@ -0,0 +1,140 @@+{-# LANGUAGE GeneralizedNewtypeDeriving #-}+-- |+-- Module : Network.BERT.Transport+-- Copyright : (c) marius a. eriksen 2009+-- +-- License : BSD3+-- Maintainer : marius@monkey.org+-- Stability : experimental+-- Portability : GHC+-- +-- Transport for BERT-RPC client. Creates a transport from a URI,+-- leaving flexibility for several underlying transport+-- implementations. The current one is over sockets (TCP), with the+-- URI scheme bert, eg: <bert://localhost:9000>+-- +-- The TCP transport will create a new connection for every request+-- (every block of 'withTransport'), which seems to be what the+-- current server implementations expect. It'd be great to have+-- persistent connections, however.++module Network.BERT.Transport+ ( Transport, fromURI, fromHostPort+ -- ** Transport monad+ , TransportM, withTransport+ , sendt, recvt+ -- ** Server side+ , servet+ ) where++import Control.Monad (forever)+import Control.Monad.State (+ StateT, MonadIO, MonadState, runStateT, + modify, gets, liftIO)+import Network.URI (URI(..), URIAuth(..), parseURI)+import Network.Socket (+ Socket(..), Family(..), SockAddr(..), SocketType(..), + SocketOption(..), AddrInfo(..), connect, socket, sClose, + setSocketOption, bindSocket, listen, accept, iNADDR_ANY, + getAddrInfo, defaultHints)+import Data.Maybe (fromJust)+import Data.Binary (encode, decode)+import qualified Data.ByteString.Lazy as L+import qualified Data.ByteString as B+import qualified Network.Socket.ByteString.Lazy as LS+import qualified System.Posix.Signals as Sig++import Data.BERT (Term(..), BERT(..), Packet(..))+import Data.BERT.Packet (packets)++-- | Defines a transport endpoint. Create with 'fromURI'.+data Transport+ = TcpTransport SockAddr+ | TcpServerTransport Socket+ deriving (Show, Eq)++data TransportState+ = TransportState {+ state_packets :: [Packet]+ , state_socket :: Socket+ }++newtype TransportM a+ = TransportM (StateT TransportState IO a)+ deriving (Monad, MonadIO, MonadState TransportState)++-- | Create a transport from the given URI.+fromURI :: String -> IO Transport+fromURI = fromURI_ . fromJust . parseURI++-- | Create a (TCP) transport from the given host and port+fromHostPort :: (Integral a) => String -> a -> IO Transport+fromHostPort "" port = + return $ TcpTransport + $ SockAddrInet (fromIntegral port) iNADDR_ANY+fromHostPort host port = do+ resolve host >>= return . TcpTransport + . SockAddrInet (fromIntegral port)++fromURI_ uri@(URI { uriScheme = "bert:"+ , uriAuthority = Just URIAuth + { uriRegName = host+ , uriPort = ':':port}}) =+ fromHostPort host (fromIntegral . read $ port)++servet :: Transport -> (Transport -> IO ()) -> IO ()+servet (TcpTransport sa) dispatch = do+ -- Ignore sigPIPE, which can be delivered upon writing to a closed+ -- socket.+ Sig.installHandler Sig.sigPIPE Sig.Ignore Nothing++ sock <- socket AF_INET Stream 0+ setSocketOption sock ReuseAddr 1+ bindSocket sock sa+ listen sock 1++ forever $ do+ (clientsock, _) <- accept sock+ setSocketOption clientsock NoDelay 1+ dispatch $ TcpServerTransport clientsock++resolve host = do+ r <- getAddrInfo (Just hints) (Just host) Nothing+ case r of+ (AddrInfo { addrAddress = (SockAddrInet _ addr) }:_) -> return addr+ _ -> fail $ "Failed to resolve " ++ host+ where+ hints = defaultHints { addrFamily = AF_INET }++-- | Execute the given transport monad action in the context of the+-- passed transport.+withTransport :: Transport -> TransportM a -> IO a+withTransport (TcpTransport sa) m = do+ sock <- socket AF_INET Stream 0+ connect sock sa+ withTransport_ sock m+withTransport (TcpServerTransport sock) m =+ withTransport_ sock m++withTransport_ sock (TransportM m) = do+ ps <- LS.getContents sock >>= return . packets+ (result, _) <- runStateT m TransportState + { state_packets = ps + , state_socket = sock}+ sClose sock+ return result++-- | Send a term (inside the transport monad)+sendt :: Term -> TransportM ()+sendt t = do+ sock <- gets state_socket+ liftIO $ LS.sendAll sock $ encode (Packet t)+ return ()++-- | Receive a term (inside the transport monad)+recvt :: TransportM Term+recvt = do+ ps <- gets state_packets+ modify $ \state -> state { state_packets = drop 1 ps }+ let Packet t = head ps+ return t
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ bert.cabal view
@@ -0,0 +1,29 @@+cabal-version: >= 1.6+name: bert+version: 1.0+build-type: Simple+license: BSD3+license-file: LICENSE+author: marius a. eriksen+category: Data+synopsis: BERT implementation+description: Implements the BERT serialization and RPC protocols+ described at <http://bert-rpc.org/>.+maintainer: marius a. eriksen+copyright: (c) 2009 marius a. eriksen++library+ build-depends: base == 4.*, containers >= 0.2, + bytestring >= 0.9, binary >= 0.5, + mtl >= 1.1, network-bytestring >= 0.1,+ network >= 2.2, unix >= 2.0, time >= 1.1, + parsec >= 2.0+ exposed-modules:+ Data.BERT+ Network.BERT+ Network.BERT.Transport+ Network.BERT.Client+ Network.BERT.Server++executable bert+ main-is: bert.hs
+ bert.hs view
@@ -0,0 +1,69 @@+-- |+-- Copyright : (c) marius a. eriksen 2009+-- +-- License : BSD3+-- Maintainer : marius@monkey.org+-- Stability : experimental+-- Portability : GHC+-- +-- Tool to issue or serve BERT requests.+module Main where++import System.Console.GetOpt+import System.Environment (getArgs, getProgName)+import Data.Maybe (maybe)+import Text.Printf (printf)+import Data.BERT (Term(..))+import qualified Network.BERT as Net++data Flags+ = Help+ deriving (Show, Eq, Ord)++data Mode+ = Call String String String [Term]+ | Serve Int+ deriving (Show, Eq, Ord)++options = + [ Option ['h'] [] (NoArg Help) "show help"+ ]++usage = do+ header <- getProgName >>= + return . printf ("Usage: %s [OPTION...] " +++ "[call <uri> <mod> <fun> [args..]|serve PORT]" )+ return $ usageInfo header options++parseArgs argv = + case getOpt Permute options argv of+ (o, n, []) -> + if Help `elem` o+ then return $ Nothing+ else do+ m <- parse n+ return $ Just (o, m)+ where+ parse ("call":uri:mod:fun:args) = return $ Call uri mod fun (map read args)+ parse ["serve", port] = return $ Serve (read port)+ parse _ = help "Cannot parse command"++ help = fail . flip (++) " [-h for help]"++main = do+ args <- getArgs >>= parseArgs+ case args of+ Just (_, Serve port) -> doServe port+ Just (_, Call uri mod fun args) -> doCall uri mod fun args+ Nothing -> usage >>= putStr++doServe = undefined++doCall :: String -> String -> String -> [Term] -> IO ()+doCall uri mod fun args = do+ t <- Net.fromURI uri+ r <- Net.call t mod fun args :: Net.Call Term+ case r of+ Right res -> putStrLn $ printf "reply: %s" $ show res+ Left error -> putStrLn $ printf "error: %s" $ show error+ return ()