-
Notifications
You must be signed in to change notification settings - Fork 24
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add example #35
base: add-example
Are you sure you want to change the base?
Add example #35
Changes from all commits
a2c15c5
c205020
bcf6ec6
f015f26
afeb8c9
55bf6a2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
# A Simple GRPC Client/Server in Haskell | ||
This directory contains a simple Client/Server example with a single RPC call. | ||
The Client calls the server with a list of numbers, and the server sums up these numbers and sends the answer back to the client. | ||
|
||
# The Protobuf Definition | ||
The protobuf definition is in a single file, *calcs.proto*. | ||
``` | ||
syntax = "proto3"; | ||
|
||
package calcs; | ||
|
||
service Arithmetic { | ||
rpc Add(CalcNumbers) returns (CalcNumber) {} | ||
} | ||
|
||
message CalcNumbers { | ||
repeated CalcNumber values = 1; | ||
} | ||
|
||
message CalcNumber { | ||
uint32 code = 1; | ||
} | ||
``` | ||
|
||
Each RPC call takes a single message, and returns a single message. In this case the request is a *CalcNumbers* and the response is a *CalcNumber*. Protobuf allows message types to be nested. In this case *CalcHumbers* is comprised of a list of *CalcNumber* values. *CalcNumber* itself is a [32 bit unsigned int](https://developers.google.com/protocol-buffers/docs/proto3#scalar_value_types). | ||
|
||
# Getting it working | ||
The *calcs.proto* definition file is used to generate Haskell code via the *protoc* tool, which needs to be installed separately. There are two Haskell files generated. The simplest way to build the client and server is to copy these generaeted source files to somewhere accessible within your stack package. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think what you should say in this paragraph at this point is:
|
||
|
||
## Starting the Server | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here you need to say "in an interactive session" because you use |
||
The server is in ArithServer.hs, and can be started from *ghci*. | ||
|
||
~~~ | ||
stack ghci | ||
:l ArithServer | ||
someFunc' [] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. maybe rename the function to something more "user friendly" like |
||
~~~ | ||
|
||
This starts the server on port 3000. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add something about the fact that the server runs waiting for client queries (and hence does not give the prompt back). |
||
|
||
## Starting the Client | ||
The client is ArithClient.hs and can be started as shown below: | ||
|
||
~~~ | ||
stack ghci | ||
ArithClient.main | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. same as above, maybe rename the function to something more "user friendly" like runExampleClient |
||
~~~ | ||
|
||
The client sends a single request to the server, processes the response and terminates. | ||
Both client and server output a few log lines to show what is happening. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
#!/bin/bash | ||
|
||
#mkdir -p gen | ||
#mkdir -p gen-bin | ||
#mkdir -p protos | ||
|
||
#curl 'https://raw.githubusercontent.com/moul/pb/master/grpcbin/grpcbin.proto' > protos/grpcbin.proto | ||
|
||
stack install --local-bin-path=gen-bin proto-lens-protoc | ||
|
||
protolens="`pwd`/gen-bin/proto-lens-protoc" | ||
|
||
if [ -x "${protolens}" ] | ||
then | ||
echo "using ${protolens}" ; | ||
else | ||
echo "no proto-lens-protoc" | ||
exit 2 | ||
fi; | ||
|
||
protoc "--plugin=protoc-gen-haskell-protolens=${protolens}" \ | ||
--haskell-protolens_out=./gen \ | ||
./protos/calcs.proto | ||
|
||
echo "# Generated modules:" | ||
find gen -name "*.hs" | sed -e 's/gen\///' | sed -e 's/\.hs$//' | tr '/' '.' | ||
|
||
cp -r gen/* examples/src |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
syntax = "proto3"; | ||
|
||
package calcs; | ||
|
||
service Arithmetic { | ||
rpc Add(CalcNumbers) returns (CalcNumber) {} | ||
} | ||
|
||
message CalcNumbers { | ||
repeated CalcNumber values = 1; | ||
} | ||
|
||
message CalcNumber { | ||
uint32 code = 1; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
{-# LANGUAGE DataKinds #-} | ||
{-# LANGUAGE OverloadedStrings #-} | ||
|
||
module ArithClient where | ||
|
||
import Control.Monad (void) | ||
import Control.Monad.IO.Class (liftIO) | ||
import Proto.Protos.Calcs | ||
import Proto.Protos.Calcs_Fields | ||
import Network.GRPC.Client | ||
import Network.HTTP2.Client | ||
import Network.GRPC.Client.Helpers | ||
import Network.GRPC.HTTP2.Encoding | ||
import Lens.Micro | ||
import Network.GRPC.HTTP2.ProtoLens | ||
|
||
import Data.ProtoLens | ||
|
||
-- create a simple CalcNumbers via lens operations for later use | ||
val1, val2, val3 :: CalcNumber | ||
val1 = defMessage & (code .~ 7) | ||
val2 = defMessage & (code .~ 77) | ||
val3 = defMessage & (code .~ 777) | ||
|
||
vals :: CalcNumbers | ||
vals = defMessage & (values .~ [val1, val2, val3]) | ||
--- | ||
|
||
main :: IO () | ||
main = do | ||
let encoding = Encoding uncompressed | ||
let decoding = Decoding uncompressed | ||
let host = "127.0.0.1" | ||
let port = 3000 | ||
void $ runClientIO $ do | ||
conn <- newHttp2FrameConnection host port (tlsSettings False host port) | ||
liftIO $ print $ "connecting on port " ++ (show port) | ||
runHttp2Client conn 8192 8192 [] defaultGoAwayHandler ignoreFallbackHandler $ \client -> do | ||
liftIO $ putStrLn "~~~connected~~~" | ||
let ifc = _incomingFlowControl client | ||
let ofc = _outgoingFlowControl client | ||
liftIO $ _addCredit ifc 10000000 | ||
_ <- _updateWindow ifc | ||
reply <- open client "127.0.0.1" [] (Timeout 100) encoding decoding | ||
(singleRequest (RPC :: RPC Arithmetic "add") vals ) | ||
|
||
liftIO $ print reply | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
{-# LANGUAGE FlexibleContexts #-} | ||
{-# LANGUAGE OverloadedStrings #-} | ||
{-# LANGUAGE DataKinds #-} | ||
module ArithServer where | ||
|
||
import Network.GRPC.Server | ||
import Lens.Micro | ||
import Lens.Micro.Extras | ||
import Data.ProtoLens.Message (defMessage) | ||
import Network.Wai.Handler.WarpTLS | ||
import Network.Wai.Handler.Warp (defaultSettings, getPort) | ||
import Network.GRPC.HTTP2.ProtoLens (RPC(..)) | ||
import Network.GRPC.HTTP2.Encoding (gzip) | ||
import Proto.Protos.Calcs (Arithmetic, CalcNumbers, CalcNumber) | ||
import Proto.Protos.Calcs_Fields | ||
import System.Environment (getArgs) | ||
|
||
-- Eoin Cavanagh, modified from https://github.com/lucasdicioccio/warp-grpc-example/blob/master/src/Lib.hs | ||
|
||
someFunc :: IO () | ||
someFunc = do | ||
args <- getArgs | ||
someFunc' args | ||
|
||
someFunc' :: [String] -> IO () | ||
someFunc' p = do | ||
print $ "starting on port " ++ ((show . getPort) defaultSettings) | ||
-- for simplicity, configures Warp to support insecure sessions | ||
-- not recommended for production use | ||
let insecureTlsSettings = defaultTlsSettings { onInsecure = AllowInsecure } | ||
runGrpc insecureTlsSettings defaultSettings (handlers p) [gzip] | ||
|
||
handlers :: [String] -> [ServiceHandler] | ||
handlers _ = | ||
[unary (RPC :: RPC Arithmetic "add") handleAdd | ||
] | ||
|
||
|
||
-- sum up the values provided in the request and return the total | ||
handleAdd :: UnaryHandler IO CalcNumbers CalcNumber | ||
handleAdd _ input = do | ||
let vals = view values input | ||
print ("add"::[Char], vals) | ||
print (show vals) | ||
return $ performAdd input | ||
|
||
performAdd :: CalcNumbers -> CalcNumber | ||
performAdd nums = defMessage & code .~ total | ||
where | ||
total = sum (toListOf (values . traverse . code) nums) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
s/CalcHumbers/CalcNumbers