From 169dbb7fff279f20a05da37197ce5b8a455971fe Mon Sep 17 00:00:00 2001 From: Connor Clark Date: Sun, 30 Apr 2017 15:38:29 -0700 Subject: [PATCH] First draft of a tutorial (#15) * add basic tutorial, re-export client stuff in HighLevel.Generated * add a relative link in README.md * forgot to document the language extensions * cabal file fixup * more context to compile-proto-file * haskell syntax highlighting in markdown * link to gRPC official tutorials for basic concepts * add a note on how to build the examples * prominent notice of required gRPC version * fix typo * do some error handling, show how to run the example executables * use mapM --- README.md | 4 + examples/tutorial/Arithmetic.hs | 129 ++++++++++++++ examples/tutorial/ArithmeticClient.hs | 39 +++++ examples/tutorial/ArithmeticServer.hs | 50 ++++++ examples/tutorial/TUTORIAL.md | 216 ++++++++++++++++++++++++ examples/tutorial/arithmetic.proto | 17 ++ grpc-haskell.cabal | 45 +++++ src/Network/GRPC/HighLevel/Generated.hs | 10 ++ src/Network/GRPC/LowLevel/Client.hs | 6 +- 9 files changed, 513 insertions(+), 3 deletions(-) create mode 100644 examples/tutorial/Arithmetic.hs create mode 100644 examples/tutorial/ArithmeticClient.hs create mode 100644 examples/tutorial/ArithmeticServer.hs create mode 100644 examples/tutorial/TUTORIAL.md create mode 100644 examples/tutorial/arithmetic.proto diff --git a/README.md b/README.md index edde377..3dcdd25 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,8 @@ have extended and released under the same [`LICENSE`](./LICENSE) Installation ------------ +**The current version of this library requires gRPC version 1.2.0. Newer versions may work but have not been tested.** + Run the following command from the root of this repository to install the `compile-proto-file` executable: @@ -17,6 +19,8 @@ $ nix-env -iA grpc-haskell -f release.nix Usage ----- +There is a tutorial [here](examples/tutorial/TUTORIAL.md) + ```bash $ compile-proto-file --help Dumps a compiled .proto file to stdout diff --git a/examples/tutorial/Arithmetic.hs b/examples/tutorial/Arithmetic.hs new file mode 100644 index 0000000..9e3f911 --- /dev/null +++ b/examples/tutorial/Arithmetic.hs @@ -0,0 +1,129 @@ +{-# LANGUAGE DeriveGeneric #-} +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE OverloadedStrings #-} +{-# OPTIONS_GHC -fno-warn-unused-imports #-} +{-# OPTIONS_GHC -fno-warn-name-shadowing #-} +-- | Generated by Haskell protocol buffer compiler. DO NOT EDIT! +module Arithmetic where +import qualified Prelude as Hs +import qualified Proto3.Suite.DotProto as HsProtobuf +import qualified Proto3.Suite.Types as HsProtobuf +import qualified Proto3.Suite.Class as HsProtobuf +import qualified Proto3.Wire as HsProtobuf +import Control.Applicative ((<*>), (<|>)) +import qualified Data.Text as Hs (Text) +import qualified Data.ByteString as Hs +import qualified Data.String as Hs (fromString) +import qualified Data.Vector as Hs (Vector) +import qualified Data.Int as Hs (Int16, Int32, Int64) +import qualified Data.Word as Hs (Word16, Word32, Word64) +import GHC.Generics as Hs +import GHC.Enum as Hs +import Network.GRPC.HighLevel.Generated as HsGRPC +import Network.GRPC.HighLevel.Client as HsGRPC +import Network.GRPC.HighLevel.Server as HsGRPC hiding (serverLoop) +import Network.GRPC.HighLevel.Server.Unregistered as HsGRPC + (serverLoop) +import Network.GRPC.LowLevel.Call as HsGRPC + +data Arithmetic request response = Arithmetic{arithmeticAdd :: + request 'HsGRPC.Normal TwoInts OneInt -> + Hs.IO (response 'HsGRPC.Normal OneInt), + arithmeticRunningSum :: + request 'HsGRPC.ClientStreaming OneInt OneInt -> + Hs.IO (response 'HsGRPC.ClientStreaming OneInt)} + deriving Hs.Generic + +arithmeticServer :: + Arithmetic HsGRPC.ServerRequest HsGRPC.ServerResponse -> + HsGRPC.ServiceOptions -> Hs.IO () +arithmeticServer + Arithmetic{arithmeticAdd = arithmeticAdd, + arithmeticRunningSum = arithmeticRunningSum} + (ServiceOptions serverHost serverPort useCompression + userAgentPrefix userAgentSuffix initialMetadata sslConfig logger) + = (HsGRPC.serverLoop + HsGRPC.defaultOptions{HsGRPC.optNormalHandlers = + [(HsGRPC.UnaryHandler + (HsGRPC.MethodName "/arithmetic.Arithmetic/Add") + (HsGRPC.convertGeneratedServerHandler arithmeticAdd))], + HsGRPC.optClientStreamHandlers = + [(HsGRPC.ClientStreamHandler + (HsGRPC.MethodName "/arithmetic.Arithmetic/RunningSum") + (HsGRPC.convertGeneratedServerReaderHandler + arithmeticRunningSum))], + HsGRPC.optServerStreamHandlers = [], + HsGRPC.optBiDiStreamHandlers = [], optServerHost = serverHost, + optServerPort = serverPort, optUseCompression = useCompression, + optUserAgentPrefix = userAgentPrefix, + optUserAgentSuffix = userAgentSuffix, + optInitialMetadata = initialMetadata, optSSLConfig = sslConfig, + optLogger = logger}) + +arithmeticClient :: + HsGRPC.Client -> + Hs.IO (Arithmetic HsGRPC.ClientRequest HsGRPC.ClientResult) +arithmeticClient client + = (Hs.pure Arithmetic) <*> + ((Hs.pure (HsGRPC.clientRequest client)) <*> + (HsGRPC.clientRegisterMethod client + (HsGRPC.MethodName "/arithmetic.Arithmetic/Add"))) + <*> + ((Hs.pure (HsGRPC.clientRequest client)) <*> + (HsGRPC.clientRegisterMethod client + (HsGRPC.MethodName "/arithmetic.Arithmetic/RunningSum"))) + +data TwoInts = TwoInts{twoIntsX :: Hs.Int32, twoIntsY :: Hs.Int32} + deriving (Hs.Show, Hs.Eq, Hs.Ord, Hs.Generic) + +instance HsProtobuf.Named TwoInts where + nameOf _ = (Hs.fromString "TwoInts") + +instance HsProtobuf.Message TwoInts where + encodeMessage _ TwoInts{twoIntsX = twoIntsX, twoIntsY = twoIntsY} + = (Hs.mconcat + [(HsProtobuf.encodeMessageField (HsProtobuf.FieldNumber 1) + twoIntsX), + (HsProtobuf.encodeMessageField (HsProtobuf.FieldNumber 2) + twoIntsY)]) + decodeMessage _ + = (Hs.pure TwoInts) <*> + (HsProtobuf.at HsProtobuf.decodeMessageField + (HsProtobuf.FieldNumber 1)) + <*> + (HsProtobuf.at HsProtobuf.decodeMessageField + (HsProtobuf.FieldNumber 2)) + dotProto _ + = [(HsProtobuf.DotProtoField (HsProtobuf.FieldNumber 1) + (HsProtobuf.Prim HsProtobuf.Int32) + (HsProtobuf.Single "x") + [] + Hs.Nothing), + (HsProtobuf.DotProtoField (HsProtobuf.FieldNumber 2) + (HsProtobuf.Prim HsProtobuf.Int32) + (HsProtobuf.Single "y") + [] + Hs.Nothing)] + +data OneInt = OneInt{oneIntResult :: Hs.Int32} + deriving (Hs.Show, Hs.Eq, Hs.Ord, Hs.Generic) + +instance HsProtobuf.Named OneInt where + nameOf _ = (Hs.fromString "OneInt") + +instance HsProtobuf.Message OneInt where + encodeMessage _ OneInt{oneIntResult = oneIntResult} + = (Hs.mconcat + [(HsProtobuf.encodeMessageField (HsProtobuf.FieldNumber 1) + oneIntResult)]) + decodeMessage _ + = (Hs.pure OneInt) <*> + (HsProtobuf.at HsProtobuf.decodeMessageField + (HsProtobuf.FieldNumber 1)) + dotProto _ + = [(HsProtobuf.DotProtoField (HsProtobuf.FieldNumber 1) + (HsProtobuf.Prim HsProtobuf.Int32) + (HsProtobuf.Single "result") + [] + Hs.Nothing)] diff --git a/examples/tutorial/ArithmeticClient.hs b/examples/tutorial/ArithmeticClient.hs new file mode 100644 index 0000000..9c86928 --- /dev/null +++ b/examples/tutorial/ArithmeticClient.hs @@ -0,0 +1,39 @@ +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE OverloadedLists #-} +{-# LANGUAGE GADTs #-} + +import Arithmetic +import Network.GRPC.HighLevel.Generated + +clientConfig :: ClientConfig +clientConfig = ClientConfig { clientServerHost = "localhost" + , clientServerPort = 50051 + , clientArgs = [] + , clientSSLConfig = Nothing + } + +main :: IO () +main = withGRPCClient clientConfig $ \client -> do + (Arithmetic arithmeticAdd arithmeticRunningSum) <- arithmeticClient client + + -- Request for the Add RPC + ClientNormalResponse (OneInt x) _meta1 _meta2 _status _details + <- arithmeticAdd (ClientNormalRequest (TwoInts 2 2) 1 []) + putStrLn ("2 + 2 = " ++ show x) + + -- Request for the RunningSum RPC + ClientWriterResponse reply _streamMeta1 _streamMeta2 streamStatus streamDtls + <- arithmeticRunningSum $ ClientWriterRequest 1 [] $ \send -> do + eithers <- mapM send [OneInt 1, OneInt 2, OneInt 3] + :: IO [Either GRPCIOError ()] + case sequence eithers of + Left err -> error ("Error while streaming: " ++ show err) + Right _ -> return () + + case reply of + Just (OneInt y) -> print ("1 + 2 + 3 = " ++ show y) + Nothing -> putStrLn ("Client stream failed with status " + ++ show streamStatus + ++ " and details " + ++ show streamDtls) + return () diff --git a/examples/tutorial/ArithmeticServer.hs b/examples/tutorial/ArithmeticServer.hs new file mode 100644 index 0000000..36a233a --- /dev/null +++ b/examples/tutorial/ArithmeticServer.hs @@ -0,0 +1,50 @@ +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE OverloadedLists #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE BangPatterns #-} + +import Arithmetic +import Network.GRPC.HighLevel.Generated + +import Data.String (fromString) + +handlers :: Arithmetic ServerRequest ServerResponse +handlers = Arithmetic { arithmeticAdd = addHandler + , arithmeticRunningSum = runningSumHandler + } + +addHandler :: ServerRequest 'Normal TwoInts OneInt + -> IO (ServerResponse 'Normal OneInt) +addHandler (ServerNormalRequest _metadata (TwoInts x y)) = do + let answer = OneInt (x + y) + return (ServerNormalResponse answer + [("metadata_key_one", "metadata_value")] + StatusOk + "addition is easy!") + + +runningSumHandler :: ServerRequest 'ClientStreaming OneInt OneInt + -> IO (ServerResponse 'ClientStreaming OneInt) +runningSumHandler req@(ServerReaderRequest metadata recv) = + loop 0 + where loop !i = + do msg <- recv + case msg of + Left err -> return (ServerReaderResponse + Nothing + [] + StatusUnknown + (fromString (show err))) + Right (Just (OneInt x)) -> loop (i + x) + Right Nothing -> return (ServerReaderResponse + (Just (OneInt i)) + [] + StatusOk + "") + +options :: ServiceOptions +options = defaultServiceOptions + +main :: IO () +main = arithmeticServer handlers options diff --git a/examples/tutorial/TUTORIAL.md b/examples/tutorial/TUTORIAL.md new file mode 100644 index 0000000..d98bf95 --- /dev/null +++ b/examples/tutorial/TUTORIAL.md @@ -0,0 +1,216 @@ +## Introduction to gRPC-Haskell + +*This tutorial assumes that you already have a basic understanding of gRPC as well as Haskell.* For an intoduction to the concepts of gRPC, see the [official tutorials](http://www.grpc.io/docs/tutorials/). + +This will go through a basic example of using the library, with the `arithmetic` example in the `examples/arithmetic` directory. After cloning this repository, it would be a good idea to run `stack haddock` from within the repository directory to generate the documentation so you can read more about the functions and types we're using as we go. Also remember that [typed holes](https://wiki.haskell.org/GHC/Typed_holes) can be very handy. + +To build the examples, you can run + +``` +$ stack build --flag grpc-haskell:with-examples +``` + +The gRPC service we will be implementing provides two amazing functions: + +1. `Add`, which adds two integers. +2. `RunningSum`, which receives a stream of integers from the client and finally returns a single integer that is the sum of all the integers it has received. + +You can run the examples by running `stack exec arithmetic-server` and `stack exec arithmetic-client`. + +### Library Organization + +**tl;dr: you probably only need to import `Network.GRPC.HighLevel.Generated`.** Other modules are exposed for advanced users only. + +This library exposes quite a few modules, but you won't need to worry about most of them. They are currently organized based on the level of abstraction they afford over using the C [gRPC Core library](http://www.grpc.io/grpc/core/) directly: + +* *`Unsafe`* modules directly wrap functions in the gRPC Core library. Using them directly is like using C: you need to think about memory management, pointers, and so on. The rest of the library is built on top of these functions and users of gRPC-haskell should never need to deal with the `Unsafe` modules directly. +* *`LowLevel`* modules still require an understanding of the gRPC Core library, but guarantee memory and thread safety. Only advanced users with special requirements would use `LowLevel` modules directly. +* *`HighLevel`* modules give you an opinionated Haskell interface to gRPC that should cover most use cases while (hopefully) being easy to use. You should only need to import the `Network.GRPC.HighLevel.Generated` module to start using the library. If you need to import other modules, we probably forgot to re-export something and you should open an issue or PR. + +### Getting started + +To start out, we need to generate code for our protocol buffers and RPCs. The `compile-proto-file` command is provided as part of `grpc-haskell`. You can either use `stack install` to install the command globally, or use `stack exec` within the `grpc-haskell` directory. + +``` +$ stack exec -- compile-proto-file --proto examples/echo/echo.proto > examples/echo/echo-hs/Echo.hs +``` + +The `.proto` file compiler always names the generated module the same as the `.proto` file, capitalizing the first letter if it is not already. Since our proto file is `arithmetic.proto`, the generated code should be placed in `Arithmetic.hs`. + +The important things to notice in this generated file are: + +1. For each proto message type, an equivalent Haskell type with the same name has been generated. +2. The `arithmeticServer` function takes a a record containing handlers for each RPC endpoint and some options, and starts a server. So, you just need to call this function to get a server running. +3, The `arithmeticClient` function takes a `Client` (which is just a proof that the gRPC core has been started) and gives you a record of functions that can be used to run RPCs. + +### The server + +First, we need to turn on some language extensions: + +```haskell +{-# LANGUAGE DataKinds #-} +{-# LANGUAGE GADTs #-} +{-# LANGUAGE OverloadedLists #-} +{-# LANGUAGE OverloadedStrings #-} +``` + +All we need to do to run a server is call the `arithmeticServer` function: + +```haskell +main :: IO () +main = arithmeticServer handlers options +``` + +So we just need to define `handlers` and `options`. + +`options` is easy-- it's just some basic options for the server. We can just use the default options for now, which will start the server listening on `localhost:50051`: + +```haskell +options :: ServiceOptions +options = defaultServiceOptions +``` + +`handlers` is a bit more involved. Its type is `Arithmetic ServerRequest ServerResponse`. Values of this type contain a record field for each RPC defined in your `.proto` file. + +```haskell +handlers :: Arithmetic ServerRequest ServerResponse +handlers = Arithmetic { arithmeticAdd = addHandler + , arithmeticRunningSum = runningSumHandler + } +``` + +You can think of the handlers as being of type `ServerRequest -> ServerResponse`, though there are a few more type parameters in there. The most important one is the first parameter, which specifies whether the RPC is streaming (`ClientStreaming`, `ServerStreaming`, or `BidiStreaming`) or not (`Normal`). + +The `ServerRequest` passed to your handler contains all the tools you will need to handle the request, including: + +1. The metadata the client sent with the request. +2. The protocol buffer message sent with the request, which has already been parsed into a Haskell type for you. +3. If it's a streaming request, you will also be given functions for sending or receiving messages in the stream. + +#### The unary RPC handler for `Add` + +So, let's pattern match on the `ServerRequest` for the `addHandler` function: + +```haskell +addHandler (ServerNormalRequest metadata (TwoInts x y)) = -- to be continued! +``` + +The body of the `addHandler` function just needs to add `x` and `y` and then bundle the answer up in a `ServerResponse`: + +```haskell +addHandler (ServerNormalRequest _metadata (TwoInts x y)) = do + let answer = OneInt (x + y) + return (ServerNormalResponse answer + [("metadata_key_one", "metadata_value")] + StatusOk + "addition is easy!") +``` + +Since this is a non-streaming "Normal" RPC, we use the the `ServerNormalResponse` constructor. Its parameters are the response message, some (optional) metadata key-value pairs, a status code, and a string with additional details about the status, which would normally be used to explain any errors in handling the request. + +#### The client streaming handler for `RunningSum` + +Now let's make our `runningSumHandler`. Since this is an RPC where the server reads from a stream of numbers, we pattern match on the `ServerReaderRequest` constructor: + +```haskell +runningSumHandler req@(ServerReaderRequest metadata recv) = -- to be continued! +``` + +Unlike the unary "Normal" request handler, we don't get a message from the client in this pattern match. Instead, we get an IO action `recv`, which we can run to wait for the client to send us another message. + +There are three possibilities when we try to receive another message from the client: + +1. The RPC breaks with some gRPC error, such as losing the connection with the client. +2. We receive another message from the client. +3. The client has sent its last message and is waiting for a response. + +We write a simple loop that keeps track of the running sum and finally sends off a `ServerReaderResponse` when the client finishes streaming or an error occurs: + +```haskell +runningSumHandler req@(ServerReaderRequest metadata recv) = + loop 0 + where loop !i = + do msg <- recv + case msg of + Left err -> return (ServerReaderResponse + Nothing + [] + StatusUnknown + (fromString (show err))) + Right (Just (OneInt x)) -> loop (i + x) + Right Nothing -> return (ServerReaderResponse + (Just (OneInt i)) + [] + StatusOk + "") +``` + +The `ServerReaderResponse` type is almost the same as `ServerNormalResponse`, except that the first argument, the message to send back to the client, is optional. Otherwise, it takes metadata (which we leave empty), a status code, and a string containing more information about the status code. + +### The client + +The client-side code generated for us is `arithmeticClient`, which takes a `Client` as input and gives us a record containing actions that execute RPCs. To start up the C gRPC library and get a `Client`, we use `withGRPCClient`, which takes a `ClientConfig`: + + + +```haskell +clientConfig :: ClientConfig +clientConfig = ClientConfig { clientServerHost = "localhost" + , clientServerPort = 50051 + , clientArgs = [] + , clientSSLConfig = Nothing + } + +main :: IO () +main = withGRPCClient clientConfig $ \client -> do + (Arithmetic arithmeticAdd arithmeticRunningSum) <- arithmeticClient client + -- to be continued! +``` + +Now that we are on the client side, the `Arithmetic` record contains functions that make RPC requests. You can think of these functions as roughly having the type `ClientRequest -> ClientResult`. Like before, the particular constructors will vary depending on whether the RPC is streaming or not. + +#### Requesting unary RPC + +Here we construct a `ClientNormalRequest`, which takes as input a message, a timeout in seconds, and metadata. The result is a `ClientNormalResponse`, containing the server's response, the initial and trailing metadata for the call, and the status and status details string. + +```haskell +-- Request for the Add RPC + ClientNormalResponse (OneInt x) _meta1 _meta2 _status _details + <- arithmeticAdd (ClientNormalRequest (TwoInts 2 2) 1 []) + print ("2 + 2 = " ++ (show x)) +``` + +#### Executing a client streaming RPC + +Doing a streaming request is slightly trickier. As input to the streaming RPC action, we pass in another IO action that tells `grpc-haskell` what to send. It takes a `send` action as input. This is a bit convoluted, but it guarantees that you can't send streaming messages outside of the context of a streaming call! + +```haskell +-- Request for the RunningSum RPC +ClientWriterResponse reply _streamMeta1 _streamMeta2 streamStatus streamDtls + <- arithmeticRunningSum $ ClientWriterRequest 1 [] $ \send -> do + eithers <- mapM send [OneInt 1, OneInt 2, OneInt 3] + :: IO [Either GRPCIOError ()] + case sequence eithers of + Left err -> error ("Error while streaming: " ++ show err) + Right _ -> return () +``` + +Each `send` potentially returns an error message, with the type `Either GRPCIOError ()`. We use `sequence` to run all the `send` actions, and then use `sequence` again to collapse all the `Either`s. If an error is encountered while streaming, there's nothing we can do to salvage the RPC, so a more serious program would need to do some application-specific error-handling. Since this is just a tutorial, we print an error message and exit. Otherwise, we `return ()` to finish sending. + +We can now inspect the `reply` to get our answer to the RPC. + +```haskell +case reply of + Just (OneInt y) -> print ("1 + 2 + 3 = " ++ show y) + Nothing -> putStrLn ("Client stream failed with status " + ++ show streamStatus + ++ " and details " + ++ show streamDtls) +``` + +To run the examples and see the requests, start the `arithmetic-server` process in the background, and then run the `arithmetic-client` process: + +``` +$ stack exec -- arithmetic-server & +$ stack exec -- arithmetic-client +``` diff --git a/examples/tutorial/arithmetic.proto b/examples/tutorial/arithmetic.proto new file mode 100644 index 0000000..190c38d --- /dev/null +++ b/examples/tutorial/arithmetic.proto @@ -0,0 +1,17 @@ +syntax = "proto3"; +package arithmetic; + +service Arithmetic { + rpc Add (TwoInts) returns (OneInt) {} + + rpc RunningSum (stream OneInt) returns (OneInt) {} +} + +message TwoInts { + int32 x = 1; + int32 y = 2; +} + +message OneInt { + int32 result = 1; +} diff --git a/grpc-haskell.cabal b/grpc-haskell.cabal index 3b3033c..c18e515 100644 --- a/grpc-haskell.cabal +++ b/grpc-haskell.cabal @@ -170,6 +170,51 @@ executable echo-server hs-source-dirs: examples/echo/echo-hs main-is: EchoServer.hs + +executable arithmetic-server + if flag(with-examples) + build-depends: + base >=4.8 && <5.0 + , async + , bytestring == 0.10.* + , containers ==0.5.* + , grpc-haskell + , optparse-generic + , proto3-suite + , proto3-wire + , text + , vector + other-modules: + Arithmetic + else + buildable: False + default-language: Haskell2010 + ghc-options: -Wall -g -threaded -rtsopts -with-rtsopts=-N -O2 + hs-source-dirs: examples/tutorial/ + main-is: ArithmeticServer.hs + +executable arithmetic-client + if flag(with-examples) + build-depends: + base >=4.8 && <5.0 + , async + , bytestring == 0.10.* + , containers ==0.5.* + , grpc-haskell + , optparse-generic + , proto3-suite + , proto3-wire + , text + , vector + other-modules: + Arithmetic + else + buildable: False + default-language: Haskell2010 + ghc-options: -Wall -g -threaded -rtsopts -with-rtsopts=-N -O2 + hs-source-dirs: examples/tutorial/ + main-is: ArithmeticClient.hs + executable echo-client if flag(with-examples) build-depends: diff --git a/src/Network/GRPC/HighLevel/Generated.hs b/src/Network/GRPC/HighLevel/Generated.hs index fdff6f7..caac181 100644 --- a/src/Network/GRPC/HighLevel/Generated.hs +++ b/src/Network/GRPC/HighLevel/Generated.hs @@ -28,10 +28,17 @@ module Network.GRPC.HighLevel.Generated ( -- * Server Auth , ServerSSLConfig(..) + + -- * Client +, withGRPCClient +, ClientConfig(..) +, ClientRequest(..) +, ClientResult(..) ) where import Network.GRPC.HighLevel.Server +import Network.GRPC.HighLevel.Client import Network.GRPC.LowLevel import Network.GRPC.LowLevel.Call import System.IO (hPutStrLn, stderr) @@ -84,3 +91,6 @@ defaultServiceOptions = ServiceOptions , Network.GRPC.HighLevel.Generated.sslConfig = Nothing , Network.GRPC.HighLevel.Generated.logger = hPutStrLn stderr } + +withGRPCClient :: ClientConfig -> (Client -> IO a) -> IO a +withGRPCClient c f = withGRPC $ \grpc -> withClient grpc c $ \client -> f client diff --git a/src/Network/GRPC/LowLevel/Client.hs b/src/Network/GRPC/LowLevel/Client.hs index a2d0969..a310454 100644 --- a/src/Network/GRPC/LowLevel/Client.hs +++ b/src/Network/GRPC/LowLevel/Client.hs @@ -54,8 +54,8 @@ data ClientSSLConfig = ClientSSLConfig -- | Configuration necessary to set up a client. -data ClientConfig = ClientConfig {serverHost :: Host, - serverPort :: Port, +data ClientConfig = ClientConfig {clientServerHost :: Host, + clientServerPort :: Port, clientArgs :: [C.Arg], -- ^ Optional arguments for setting up the -- channel on the client. Supplying an empty @@ -69,7 +69,7 @@ data ClientConfig = ClientConfig {serverHost :: Host, } clientEndpoint :: ClientConfig -> Endpoint -clientEndpoint ClientConfig{..} = endpoint serverHost serverPort +clientEndpoint ClientConfig{..} = endpoint clientServerHost clientServerPort addMetadataCreds :: C.ChannelCredentials -> Maybe C.ClientMetadataCreate