packages feed

http2-client-grpc 0.3.0.0 → 0.4.0.0

raw patch · 3 files changed

+190/−47 lines, 3 filesdep +tlsPVP ok

version bump matches the API change (PVP)

Dependencies added: tls

API changes (from Hackage documentation)

+ Network.GRPC.Client: Compressed :: CompressMode
+ Network.GRPC.Client: Uncompressed :: CompressMode
+ Network.GRPC.Client: data CompressMode
+ Network.GRPC.Client.Helpers: GrpcClient :: Http2Client -> Authority -> [(ByteString, ByteString)] -> Timeout -> Compression -> GrpcClient
+ Network.GRPC.Client.Helpers: GrpcClientConfig :: !HostName -> !PortNumber -> ![(ByteString, ByteString)] -> !Timeout -> !Compression -> !(Maybe ClientParams) -> GoAwayHandler -> FallBackFrameHandler -> GrpcClientConfig
+ Network.GRPC.Client.Helpers: [_grpcClientAuthority] :: GrpcClient -> Authority
+ Network.GRPC.Client.Helpers: [_grpcClientCompression] :: GrpcClient -> Compression
+ Network.GRPC.Client.Helpers: [_grpcClientConfigCompression] :: GrpcClientConfig -> !Compression
+ Network.GRPC.Client.Helpers: [_grpcClientConfigFallbackHandler] :: GrpcClientConfig -> FallBackFrameHandler
+ Network.GRPC.Client.Helpers: [_grpcClientConfigGoAwayHandler] :: GrpcClientConfig -> GoAwayHandler
+ Network.GRPC.Client.Helpers: [_grpcClientConfigHeaders] :: GrpcClientConfig -> ![(ByteString, ByteString)]
+ Network.GRPC.Client.Helpers: [_grpcClientConfigHost] :: GrpcClientConfig -> !HostName
+ Network.GRPC.Client.Helpers: [_grpcClientConfigPort] :: GrpcClientConfig -> !PortNumber
+ Network.GRPC.Client.Helpers: [_grpcClientConfigTLS] :: GrpcClientConfig -> !(Maybe ClientParams)
+ Network.GRPC.Client.Helpers: [_grpcClientConfigTimeout] :: GrpcClientConfig -> !Timeout
+ Network.GRPC.Client.Helpers: [_grpcClientHeaders] :: GrpcClient -> [(ByteString, ByteString)]
+ Network.GRPC.Client.Helpers: [_grpcClientHttp2Client] :: GrpcClient -> Http2Client
+ Network.GRPC.Client.Helpers: [_grpcClientTimeout] :: GrpcClient -> Timeout
+ Network.GRPC.Client.Helpers: data GrpcClient
+ Network.GRPC.Client.Helpers: data GrpcClientConfig
+ Network.GRPC.Client.Helpers: grpcClientConfigSimple :: HostName -> PortNumber -> UseTlsOrNot -> GrpcClientConfig
+ Network.GRPC.Client.Helpers: rawStreamClient :: (Service s, HasMethod s m, MethodStreamingType s m ~ 'ClientStreaming) => RPC s m -> GrpcClient -> a -> (a -> IO (a, Either StreamDone (CompressMode, MethodInput s m))) -> IO (Either TooMuchConcurrency (a, (RawReply (MethodOutput s m))))
+ Network.GRPC.Client.Helpers: rawStreamServer :: (Service s, HasMethod s m, MethodStreamingType s m ~ 'ServerStreaming) => RPC s m -> GrpcClient -> a -> MethodInput s m -> (a -> HeaderList -> MethodOutput s m -> IO a) -> IO (Either TooMuchConcurrency (a, HeaderList, HeaderList))
+ Network.GRPC.Client.Helpers: rawUnary :: (Service s, HasMethod s m) => RPC s m -> GrpcClient -> MethodInput s m -> IO (Either TooMuchConcurrency (RawReply (MethodOutput s m)))
+ Network.GRPC.Client.Helpers: setupGrpcClient :: GrpcClientConfig -> IO GrpcClient
+ Network.GRPC.Client.Helpers: tlsSettings :: UseTlsOrNot -> HostName -> PortNumber -> Maybe ClientParams
+ Network.GRPC.Client.Helpers: type UseTlsOrNot = Bool
- Network.GRPC.Client: open :: (Service s, HasMethod s m) => RPC s m -> Http2Client -> Authority -> HeaderList -> Timeout -> Compression -> RPCCall a -> IO (Either TooMuchConcurrency a)
+ Network.GRPC.Client: open :: (Service s, HasMethod s m) => Http2Client -> Authority -> HeaderList -> Timeout -> Encoding -> Decoding -> RPCCall s m a -> IO (Either TooMuchConcurrency a)
- Network.GRPC.Client: singleRequest :: (Service s, HasMethod s m) => RPC s m -> Compression -> MethodInput s m -> RPCCall (RawReply (MethodOutput s m))
+ Network.GRPC.Client: singleRequest :: (Service s, HasMethod s m) => RPC s m -> MethodInput s m -> RPCCall s m (RawReply (MethodOutput s m))
- Network.GRPC.Client: streamReply :: (Service s, HasMethod s m, MethodStreamingType s m ~ 'ServerStreaming) => RPC s m -> Compression -> a -> MethodInput s m -> (a -> HeaderList -> MethodOutput s m -> IO a) -> RPCCall (a, HeaderList, HeaderList)
+ Network.GRPC.Client: streamReply :: (Service s, HasMethod s m, MethodStreamingType s m ~ 'ServerStreaming) => RPC s m -> a -> MethodInput s m -> (a -> HeaderList -> MethodOutput s m -> IO a) -> RPCCall s m (a, HeaderList, HeaderList)
- Network.GRPC.Client: streamRequest :: (Service s, HasMethod s m, MethodStreamingType s m ~ 'ClientStreaming) => RPC s m -> a -> (a -> IO (Compression, a, Either StreamDone (MethodInput s m))) -> RPCCall (a, RawReply (MethodOutput s m))
+ Network.GRPC.Client: streamRequest :: (Service s, HasMethod s m, MethodStreamingType s m ~ 'ClientStreaming) => RPC s m -> a -> (a -> IO (a, Either StreamDone (CompressMode, MethodInput s m))) -> RPCCall s m (a, RawReply (MethodOutput s m))

Files

http2-client-grpc.cabal view
@@ -1,5 +1,5 @@ name:                http2-client-grpc-version:             0.3.0.0+version:             0.4.0.0 synopsis:            Implement gRPC-over-HTTP2 clients. description:         Uses http2-client and proto-lens to generate client code. homepage:            https://github.com/lucasdicioccio/http2-client-grpc#readme@@ -16,6 +16,7 @@ library   hs-source-dirs:      src   exposed-modules:     Network.GRPC.Client+                     , Network.GRPC.Client.Helpers   build-depends:       base >= 4.7 && < 5                      , binary                      , bytestring@@ -26,6 +27,7 @@                      , proto-lens                      , proto-lens-protoc                      , text+                     , tls                      , zlib   default-language:    Haskell2010 
src/Network/GRPC/Client.hs view
@@ -18,6 +18,7 @@   , singleRequest   , streamReply   , streamRequest+  , CompressMode(..)   , StreamDone(..)   -- * Errors.   , InvalidState(..)@@ -37,6 +38,7 @@ import Data.Binary.Get (Decoder(..), pushChunk, pushEndOfInput) import qualified Data.ByteString.Char8 as ByteString import Data.ProtoLens.Service.Types (Service(..), HasMethod, HasMethodImpl(..), StreamingType(..))+import GHC.TypeLits (Symbol)  import Network.GRPC.HTTP2.Types import Network.GRPC.HTTP2.Encoding@@ -68,15 +70,16 @@ throwOnPushPromise _ _ _ _ _ = throwIO UnallowedPushPromiseReceived  -- | Wait for an RPC reply.-waitReply :: (Service s, HasMethod s m) => RPC s m -> Compression -> Http2Stream -> IncomingFlowControl -> IO (RawReply (MethodOutput s m))-waitReply rpc compress stream flowControl = do+waitReply :: (Service s, HasMethod s m) => RPC s m -> Decoding -> Http2Stream -> IncomingFlowControl -> IO (RawReply (MethodOutput s m))+waitReply rpc decoding stream flowControl = do     format . fromStreamResult <$> waitStream stream flowControl throwOnPushPromise   where+    decompress = _getDecodingCompression decoding     format rsp = do        (hdrs, dat, trls) <- rsp        let res =              case lookup grpcMessageH hdrs of-               Nothing     -> fromDecoder $ pushEndOfInput $ flip pushChunk dat $ decodeOutput rpc compress+               Nothing     -> fromDecoder $ pushEndOfInput $ flip pushChunk dat $ decodeOutput rpc decompress                Just errMsg -> Left $ unpack errMsg         return (hdrs, trls, res)@@ -93,15 +96,17 @@  -- | Newtype helper used to uniformize all type of streaming modes when -- passing arguments to the 'open' call.-newtype RPCCall a = RPCCall {-    runRPC :: Http2Client -> Http2Stream -> IncomingFlowControl -> OutgoingFlowControl -> IO a+newtype RPCCall s (m ::Symbol) a = RPCCall {+    runRPC :: Http2Client -> Http2Stream -> IncomingFlowControl -> OutgoingFlowControl -> Encoding -> Decoding -> IO a   } +-- | Helper to get the proxy object from an RPCCall.+rpcFromCall :: RPCCall s m a -> RPC s m+rpcFromCall _ = RPC+ -- | Main handler to perform gRPC calls to a service. open :: (Service s, HasMethod s m)-     => RPC s m-     -- ^ A token carrying information specifying the RPC to call.-     -> Http2Client+     => Http2Client      -- ^ A connected HTTP2 client.      -> Authority      -- ^ The HTTP2-Authority portion of the URL (e.g., "dicioccio.fr:7777").@@ -109,23 +114,24 @@      -- ^ A set of HTTP2 headers (e.g., for adding authentication headers).      -> Timeout      -- ^ Timeout in seconds.-     -> Compression-     -- ^ An indication of the compression that you will be using and accepting.  Compression-     -- should be per message, however a bug in gRPC-Go (to be confirmed) seems-     -- to turn message compression mandatory if advertised in the HTTP2-     -- headers, even though the specification states that compression per-     -- message is optional irrespectively of headers.-     -> RPCCall a+     -> Encoding+     -- ^ Compression used for encoding.+     -> Decoding+     -- ^ Compression allowed for decoding+     -> RPCCall s m a      -- ^ The actual RPC handler.      -> IO (Either TooMuchConcurrency a)-open rpc conn authority extraheaders timeout compression doStuff = do+open conn authority extraheaders timeout encoding decoding call = do+    let rpc = rpcFromCall call+    let compress = _getEncodingCompression encoding+    let decompress = _getDecodingCompression decoding     let request = [ (":method", "POST")                   , (":scheme", "http")                   , (":authority", authority)                   , (":path", path rpc)                    , (grpcTimeoutH, showTimeout timeout)-                  , (grpcEncodingH, grpcCompressionHV compression)-                  , (grpcAcceptEncodingH, mconcat [grpcAcceptEncodingHVdefault, ",", grpcCompressionHV compression])+                  , (grpcEncodingH, grpcCompressionHV compress)+                  , (grpcAcceptEncodingH, mconcat [grpcAcceptEncodingHVdefault, ",", grpcCompressionHV decompress])                   , ("content-type", grpcContentTypeHV)                   , ("te", "trailers")                   ] <> extraheaders@@ -133,25 +139,22 @@         let             initStream = headers stream request (setEndHeader)             handler isfc osfc = do-                (runRPC doStuff) conn stream isfc osfc+                (runRPC call) conn stream isfc osfc encoding decoding         in StreamDefinition initStream handler  -- | gRPC call for Server Streaming. streamReply   :: (Service s, HasMethod s m, MethodStreamingType s m ~ 'ServerStreaming)   => RPC s m-  -- ^ The RPC to call.-  -> Compression-  -- ^ The compression to use.-  -- TODO: revisit compressions.+  -- ^ RPC to call.   -> a   -- ^ An initial state.   -> MethodInput s m   -- ^ The input.   -> (a -> HeaderList -> MethodOutput s m -> IO a)   -- ^ A state-passing handler that is called with the message read.-  -> RPCCall (a, HeaderList, HeaderList)-streamReply rpc compress v0 req handler = RPCCall $ \conn stream isfc osfc -> do+  -> RPCCall s m (a, HeaderList, HeaderList)+streamReply rpc v0 req handler = RPCCall $ \conn stream isfc osfc encoding decoding -> do     let {         loop v1 decode hdrs = _waitEvent stream >>= \case             (StreamPushPromiseEvent _ _ _) ->@@ -164,21 +167,23 @@                 _addCredit isfc (ByteString.length dat)                 _ <- _consumeCredit isfc (ByteString.length dat)                 _ <- _updateWindow isfc-                handleAllChunks v1 hdrs decode dat loop+                handleAllChunks decoding v1 hdrs decode dat loop     } in do         let ocfc = _outgoingFlowControl conn-        sendSingleMessage rpc req compress setEndStream conn ocfc stream osfc+        let decompress = _getDecodingCompression decoding+        sendSingleMessage rpc req encoding setEndStream conn ocfc stream osfc         _waitEvent stream >>= \case             StreamHeadersEvent _ hdrs ->-                loop v0 (decodeOutput rpc compress) hdrs+                loop v0 (decodeOutput rpc decompress) hdrs             _                         ->                 throwIO (InvalidState "no headers")   where-    handleAllChunks v1 hdrs decode dat exitLoop =+    handleAllChunks decoding v1 hdrs decode dat exitLoop =        case pushChunk decode dat of            (Done unusedDat _ (Right val)) -> do                v2 <- handler v1 hdrs val-               handleAllChunks v2 hdrs (decodeOutput rpc compress) unusedDat exitLoop+               let decompress = _getDecodingCompression decoding+               handleAllChunks decoding v2 hdrs (decodeOutput rpc decompress) unusedDat exitLoop            (Done unusedDat _ (Left err)) -> do                throwIO (StreamReplyDecodingError $ "done-error: " ++ err)            (Fail _ _ err)                 -> do@@ -188,6 +193,8 @@  data StreamDone = StreamDone +data CompressMode = Compressed | Uncompressed+ -- | gRPC call for Client Streaming. streamRequest   :: (Service s, HasMethod s m, MethodStreamingType s m ~ 'ClientStreaming)@@ -195,37 +202,41 @@   -- ^ RPC to call.   -> a   -- ^ An initial state.-  -> (a -> IO (Compression, a, Either StreamDone (MethodInput s m)))+  -> (a -> IO (a, Either StreamDone (CompressMode, MethodInput s m)))   -- ^ A state-passing action to retrieve the next message to send to the server.-  -> RPCCall (a, RawReply (MethodOutput s m))-streamRequest rpc v0 handler = RPCCall $ \conn stream isfc streamFlowControl ->+  -> RPCCall s m (a, RawReply (MethodOutput s m))+streamRequest rpc v0 handler = RPCCall $ \conn stream isfc streamFlowControl encoding decoding ->+    let decompress = _getDecodingCompression decoding in     let ocfc = _outgoingFlowControl conn         go v1 = do-            (compress, v2, nextEvent) <- handler v1+            (v2, nextEvent) <- handler v1             case nextEvent of-                Right msg -> do-                    sendSingleMessage rpc msg compress id conn ocfc stream streamFlowControl+                Right (doCompress, msg) -> do+                    let compress = case doCompress of+                            Compressed -> _getEncodingCompression encoding+                            Uncompressed -> uncompressed+                    sendSingleMessage rpc msg encoding id conn ocfc stream streamFlowControl                     go v2                 Left _ -> do                     sendData conn stream setEndStream ""-                    reply <- waitReply rpc compress stream isfc+                    reply <- waitReply rpc decoding stream isfc                     pure (v2, reply)     in go v0 - -- | Serialize and send a single message. sendSingleMessage   :: (Service s, HasMethod s m)   => RPC s m   -> MethodInput s m-  -> Compression+  -> Encoding   -> FlagSetter   -> Http2Client   -> OutgoingFlowControl   -> Http2Stream   -> OutgoingFlowControl   -> IO ()-sendSingleMessage rpc msg compression flagMod conn connectionFlowControl stream streamFlowControl = do+sendSingleMessage rpc msg encoding flagMod conn connectionFlowControl stream streamFlowControl = do+    let compress = _getEncodingCompression encoding     let goUpload dat = do             let !wanted = ByteString.length dat             gotStream <- _withdrawCredit streamFlowControl wanted@@ -237,16 +248,17 @@             else do                 sendData conn stream id (ByteString.take got dat)                 goUpload (ByteString.drop got dat)-    goUpload . toStrict . toLazyByteString . encodeInput rpc compression $ msg+    goUpload . toStrict . toLazyByteString . encodeInput rpc compress $ msg  -- | gRPC call for an unary request. singleRequest   :: (Service s, HasMethod s m)   => RPC s m-  -> Compression+  -- ^ RPC to call.   -> MethodInput s m-  -> RPCCall (RawReply (MethodOutput s m))-singleRequest rpc compress msg = RPCCall $ \conn stream isfc osfc -> do+  -- ^ RPC's input.+  -> RPCCall s m (RawReply (MethodOutput s m))+singleRequest rpc msg = RPCCall $ \conn stream isfc osfc encoding decoding -> do     let ocfc = _outgoingFlowControl conn-    sendSingleMessage rpc msg compress setEndStream conn ocfc stream osfc-    waitReply rpc compress stream isfc+    sendSingleMessage rpc msg encoding setEndStream conn ocfc stream osfc+    waitReply rpc decoding stream isfc
+ src/Network/GRPC/Client/Helpers.hs view
@@ -0,0 +1,129 @@+{-# LANGUAGE DataKinds         #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE GADTs             #-}++-- | Set of helpers helping with writing gRPC clients with not much exposure of+-- the http2-client complexity.+--+-- Th+module Network.GRPC.Client.Helpers where++import Control.Exception (throwIO)+import qualified Data.ByteString.Char8 as ByteString+import Data.ByteString.Char8 (ByteString)+import Data.Default.Class (def)+import Data.ProtoLens.Service.Types (Service(..), HasMethod, HasMethodImpl(..), StreamingType(..))+import qualified Network.TLS as TLS+import qualified Network.TLS.Extra.Cipher as TLS+import Network.HPACK (HeaderList)++import Network.HTTP2+import Network.HTTP2.Client+import Network.GRPC.Client+import Network.GRPC.HTTP2.Types+import Network.GRPC.HTTP2.Encoding++-- | A simplified gRPC Client connected via an HTTP2Client to a given server.+-- Each call from one client will share similar headers, timeout, compression.+data GrpcClient = GrpcClient {+    _grpcClientHttp2Client :: Http2Client+  -- ^ Underlying HTTP2 client.+  , _grpcClientAuthority   :: Authority+  -- ^ Authority header of the server the client is connected to.+  , _grpcClientHeaders     :: [(ByteString, ByteString)]+  -- ^ Extra HTTP2 headers to pass to every call (e.g., authentication tokens).+  , _grpcClientTimeout     :: Timeout+  -- ^ Timeout for RPCs.+  , _grpcClientCompression :: Compression+  -- ^ Compression shared for every call and expected for every answer.+  }++-- | Configuration to setup a GrpcClient.+data GrpcClientConfig = GrpcClientConfig {+    _grpcClientConfigHost            :: !HostName+  -- ^ Hostname of the server.+  , _grpcClientConfigPort            :: !PortNumber+  -- ^ Port of the server.+  , _grpcClientConfigHeaders         :: ![(ByteString, ByteString)]+  -- ^ Extra HTTP2 headers to pass to every call (e.g., authentication tokens).+  , _grpcClientConfigTimeout         :: !Timeout+  -- ^ Timeout for RPCs.+  , _grpcClientConfigCompression     :: !Compression+  -- ^ Compression shared for every call and expected for every answer.+  , _grpcClientConfigTLS             :: !(Maybe ClientParams)+  -- ^ TLS parameters for the session.+  , _grpcClientConfigGoAwayHandler   :: GoAwayHandler+  -- ^ HTTP2 handler for GoAways.+  , _grpcClientConfigFallbackHandler :: FallBackFrameHandler+  -- ^ HTTP2 handler for unhandled frames.+  }++grpcClientConfigSimple :: HostName -> PortNumber -> UseTlsOrNot -> GrpcClientConfig+grpcClientConfigSimple host port tls =+    GrpcClientConfig host port [] (Timeout 3000) gzip (tlsSettings tls host port) throwIO ignoreFallbackHandler++type UseTlsOrNot = Bool++tlsSettings :: UseTlsOrNot -> HostName -> PortNumber -> Maybe ClientParams+tlsSettings False _ _ = Nothing+tlsSettings True host port = Just $ TLS.ClientParams {+          TLS.clientWantSessionResume    = Nothing+        , TLS.clientUseMaxFragmentLength = Nothing+        , TLS.clientServerIdentification = (host, ByteString.pack $ show port)+        , TLS.clientUseServerNameIndication = True+        , TLS.clientShared               = def+        , TLS.clientHooks                = def { TLS.onServerCertificate = \_ _ _ _ -> return []+                                               }+        , TLS.clientSupported            = def { TLS.supportedCiphers = TLS.ciphersuite_default }+        , TLS.clientDebug                = def+         }+++setupGrpcClient :: GrpcClientConfig -> IO GrpcClient+setupGrpcClient config = do+  let host = _grpcClientConfigHost config+  let port = _grpcClientConfigPort config+  let tls = _grpcClientConfigTLS config+  let compression = _grpcClientConfigCompression config+  let onGoAway = _grpcClientConfigGoAwayHandler config+  let onFallback = _grpcClientConfigFallbackHandler config+  let timeout = _grpcClientConfigTimeout config+  let headers = _grpcClientConfigHeaders config+  let authority = ByteString.pack $ host <> ":" <> show port++  conn <- newHttp2FrameConnection host port tls+  cli <- newHttp2Client conn 8192 8192 [] onGoAway onFallback+  return $ GrpcClient cli authority headers timeout compression ++rawUnary+  :: (Service s, HasMethod s m)+  => RPC s m+  -> GrpcClient+  -> MethodInput s m+  -> IO (Either TooMuchConcurrency (RawReply (MethodOutput s m)))+rawUnary rpc (GrpcClient client authority headers timeout compression) input =+    let call = singleRequest rpc input+    in open client authority headers timeout (Encoding compression) (Decoding compression) call++rawStreamServer +  :: (Service s, HasMethod s m, MethodStreamingType s m ~ 'ServerStreaming)+  => RPC s m+  -> GrpcClient+  -> a+  -> MethodInput s m+  -> (a -> HeaderList -> MethodOutput s m -> IO a)+  -> IO (Either TooMuchConcurrency (a, HeaderList, HeaderList))+rawStreamServer rpc (GrpcClient client authority headers timeout compression) v0 input handler =+    let call = streamReply rpc v0 input handler+    in open client authority headers timeout (Encoding compression) (Decoding compression) call++rawStreamClient+  :: (Service s, HasMethod s m, MethodStreamingType s m ~ 'ClientStreaming)+  => RPC s m+  -> GrpcClient+  -> a+  -> (a -> IO (a, Either StreamDone (CompressMode, MethodInput s m)))+  -> IO (Either TooMuchConcurrency (a, (RawReply (MethodOutput s m))))+rawStreamClient rpc (GrpcClient client authority headers timeout compression) v0 getNext =+    let call = streamRequest rpc v0 getNext+    in open client authority headers timeout (Encoding compression) (Decoding compression) call