{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
module Capnp.Rpc.Server
( Server(..)
, ServerOps(..)
, CallInfo(..)
, runServer
, MethodHandler
, pureHandler
, rawHandler
, rawAsyncHandler
, methodThrow
, methodUnimplemented
, untypedHandler
, toUntypedHandler
, fromUntypedHandler
, invoke
) where
import Control.Concurrent.STM
import Control.Monad.STM.Class
import Data.Word
import Control.Exception.Safe (MonadCatch, finally, try)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Control.Monad.Primitive (PrimMonad, PrimState)
import Data.Typeable (Typeable)
import Capnp.Classes
( Cerialize
, Decerialize(Cerial, decerialize)
, FromPtr(fromPtr)
, ToStruct(toStruct)
)
import Capnp.Convert (valueToMsg)
import Capnp.Message (ConstMsg, MutMsg)
import Capnp.Rpc.Errors (eMethodUnimplemented, wrapException)
import Capnp.Rpc.Promise (Fulfiller, breakPromise, fulfill, newCallback)
import Capnp.TraversalLimit (defaultLimit, evalLimitT)
import Capnp.Untyped (Ptr)
import Data.Mutable (freeze)
import qualified Capnp.Gen.Capnp.Rpc.Pure as RpcGen
import qualified Capnp.Message as Message
import qualified Capnp.Untyped as Untyped
import qualified Internal.TCloseQ as TCloseQ
newtype MethodHandler m p r = MethodHandler
{ handleMethod
:: Maybe (Ptr ConstMsg)
-> Fulfiller (Maybe (Ptr ConstMsg))
-> m ()
}
invoke
:: MonadSTM m
=> MethodHandler m (Maybe (Ptr ConstMsg)) (Maybe (Ptr ConstMsg))
-> Maybe (Ptr ConstMsg)
-> Fulfiller (Maybe (Ptr ConstMsg))
-> m ()
invoke = handleMethod
pureHandler ::
( MonadCatch m
, MonadSTM m
, PrimMonad m
, s ~ PrimState m
, Decerialize p
, FromPtr ConstMsg (Cerial ConstMsg p)
, Cerialize r
, ToStruct (MutMsg s) (Cerial (MutMsg s) r)
) =>
(cap -> p -> m r)
-> cap
-> MethodHandler m p r
pureHandler f cap = MethodHandler
{ handleMethod = \ptr reply -> do
param <- evalLimitT defaultLimit $
fromPtr Message.empty ptr >>= decerialize
result <- try $ f cap param
case result of
Right val -> do
struct <- evalLimitT defaultLimit $
valueToMsg val >>= freeze >>= Untyped.rootPtr
liftSTM $ fulfill reply (Just (Untyped.PtrStruct struct))
Left e ->
liftSTM $ breakPromise reply (wrapException False e)
}
rawHandler ::
( MonadCatch m
, MonadSTM m
, PrimMonad m
, s ~ PrimState m
, Decerialize p
, FromPtr ConstMsg (Cerial ConstMsg p)
, Decerialize r
, ToStruct ConstMsg (Cerial ConstMsg r)
) =>
(cap -> Cerial ConstMsg p -> m (Cerial ConstMsg r))
-> cap
-> MethodHandler m p r
rawHandler f cap = MethodHandler
{ handleMethod = \ptr reply -> do
cerial <- evalLimitT defaultLimit $ fromPtr Message.empty ptr
result <- try $ f cap cerial
case result of
Right val -> liftSTM $ fulfill reply (Just (Untyped.PtrStruct (toStruct val)))
Left e -> liftSTM $ breakPromise reply (wrapException False e)
}
rawAsyncHandler ::
( MonadCatch m
, MonadSTM m
, PrimMonad m
, s ~ PrimState m
, Decerialize p
, FromPtr ConstMsg (Cerial ConstMsg p)
, Decerialize r
, ToStruct ConstMsg (Cerial ConstMsg r)
) =>
(cap -> Cerial ConstMsg p -> Fulfiller (Cerial ConstMsg r) -> m ())
-> cap
-> MethodHandler m p r
rawAsyncHandler f cap = MethodHandler
{ handleMethod = \ptr reply -> do
fulfiller <- newCallback $ \case
Left e -> breakPromise reply e
Right v -> fulfill reply $ Just (Untyped.PtrStruct (toStruct v))
cerial <- evalLimitT defaultLimit $ fromPtr Message.empty ptr
f cap cerial fulfiller
}
toUntypedHandler
:: MethodHandler m p r
-> MethodHandler m (Maybe (Ptr ConstMsg)) (Maybe (Ptr ConstMsg))
toUntypedHandler MethodHandler{..} = MethodHandler{..}
fromUntypedHandler
:: MethodHandler m (Maybe (Ptr ConstMsg)) (Maybe (Ptr ConstMsg))
-> MethodHandler m p r
fromUntypedHandler MethodHandler{..} = MethodHandler{..}
untypedHandler
:: (Maybe (Ptr ConstMsg) -> Fulfiller (Maybe (Ptr ConstMsg)) -> m ())
-> MethodHandler m (Maybe (Ptr ConstMsg)) (Maybe (Ptr ConstMsg))
untypedHandler = MethodHandler
methodThrow :: MonadIO m => RpcGen.Exception -> MethodHandler m p r
methodThrow exn = MethodHandler
{ handleMethod = \_ fulfiller -> liftIO $ breakPromise fulfiller exn
}
methodUnimplemented :: MonadIO m => MethodHandler m p r
methodUnimplemented = methodThrow eMethodUnimplemented
class Monad m => Server m a | a -> m where
shutdown :: a -> m ()
shutdown _ = pure ()
unwrap :: Typeable b => a -> Maybe b
unwrap _ = Nothing
data ServerOps m = ServerOps
{ handleCall
:: Word64
-> Word16
-> MethodHandler m (Maybe (Ptr ConstMsg)) (Maybe (Ptr ConstMsg))
, handleStop :: m ()
, handleCast :: forall a. Typeable a => Maybe a
}
data CallInfo = CallInfo
{ interfaceId :: !Word64
, methodId :: !Word16
, arguments :: Maybe (Ptr ConstMsg)
, response :: Fulfiller (Maybe (Ptr ConstMsg))
}
runServer :: TCloseQ.Q CallInfo -> ServerOps IO -> IO ()
runServer q ops = go `finally` handleStop ops
where
go = atomically (TCloseQ.read q) >>= \case
Nothing ->
pure ()
Just CallInfo{interfaceId, methodId, arguments, response} -> do
handleMethod
(handleCall ops interfaceId methodId)
arguments
response
go