lib/Thrift/Processor.hs (95 lines of code) (raw):
-- Copyright (c) Facebook, Inc. and its affiliates.
{-# LANGUAGE CPP #-}
module Thrift.Processor
( Processor(..)
, Blame(..)
, process
, processCommand
, msgParser
, Some(..)
) where
#if __GLASGOW_HASKELL__ > 804
#define This Some
#endif
import Control.Exception
import Thrift.Binary.Parser
import Data.ByteString
import Data.ByteString.Builder
import Data.ByteString.Lazy (toStrict)
import Data.Int
import Data.Some
#if __GLASGOW_HASKELL == 804
import Data.Monoid ((<>))
#endif
import Data.Proxy
import Data.Text (Text)
import qualified Data.Text as Text
import Thrift.Protocol
import Thrift.Protocol.ApplicationException.Types
data Blame = ClientError | ServerError
deriving (Eq,Ord,Enum,Bounded,Read,Show)
-- | Class of types that can handle parsing + running thrift requests
class Processor s where
-- | Returns the name for a particular function
reqName :: s a -> Text
-- | Parses the structure based on "text" name into a command
reqParser :: Protocol p => Proxy p -> Text -> Parser (Some s)
-- | Generate the serialized response for the returned structure as well as
-- exception information
respWriter
:: Protocol p
=> Proxy p
-> Int32
-> s a
-> Either SomeException a
-> (Builder, Maybe (SomeException, Blame))
-- | Oneway methods of this processor
onewayFns :: Proxy s -> [Text]
-- | `process` should be called once for each received request
process :: (Processor s, Protocol p)
=> Proxy p -- ^ The server's protocol to use
-> SeqNum -- ^ Sequence number
-> (forall r . s r -> IO r)
-- ^ Handler for user-code
-> ByteString -- ^ Input bytes off the wire
-> IO (ByteString, Maybe (SomeException, Blame))
-- ^ Output bytes to put on the wire as well as the exception
-- information for the response
process proxy seqNum handler input = do
(response, exc) <- case parse (msgParser proxy) input of
Left err -> do
-- Parsing failed, so the protocol is broken
let ex = ApplicationException (Text.pack err)
ApplicationExceptionType_ProtocolError
return
( genMsgBegin proxy "" 3 seqNum
<> buildStruct proxy ex
<> genMsgEnd proxy
, Just (toException ex, ClientError) )
Right (This cmd) -> processCommand proxy seqNum handler cmd
return (toStrict (toLazyByteString response), exc)
processCommand
:: (Processor s, Protocol p)
=> Proxy p
-> SeqNum
-> (forall r . s r -> IO r) -- ^ Handler for user-code
-> s r -- ^ input command
-> IO (Builder, Maybe (SomeException, Blame))
processCommand proxy seqNum handler cmd = do
-- Run the handler and generate its return struct, forcing evaluation
(builder, exc) <- respWriter proxy seqNum cmd <$> try (handler cmd)
builder' <- evaluate builder
return (builder', exc)
msgParser
:: (Processor s, Protocol p)
=> Proxy p -> Parser (Some s)
msgParser proxy = do
MsgBegin funName msgTy _ <- parseMsgBegin proxy
command <- case msgTy of
1 -> reqParser proxy funName
2 -> fail $ Text.unpack $ funName <> " expected call but got reply"
3 -> fail $ Text.unpack $ funName <> " expected call but got exception"
4 -> reqParser proxy funName
_ -> fail $ Text.unpack $ funName <> ": invalid message type"
parseMsgEnd proxy
return command