Skip to content
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

Open
wants to merge 6 commits into
base: add-example
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 50 additions & 0 deletions examples/README.md
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).
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/CalcHumbers/CalcNumbers


# 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.
Copy link
Member

Choose a reason for hiding this comment

The 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:

  • this repository contains the generated Haskell code. Thus, the example requires no specific tooling not code-generation steps.
  • however we encourage users to try re-generating the code to practice this step
  • the code-generation requires to install protoc and proto-lens-protoc separately. Both binaries must be on the PATH before invoking protoc


## Starting the Server
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here you need to say "in an interactive session" because you use ghci below

The server is in ArithServer.hs, and can be started from *ghci*.

~~~
stack ghci
:l ArithServer
someFunc' []
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe rename the function to something more "user friendly" like runExampleServer

~~~

This starts the server on port 3000.
Copy link
Member

Choose a reason for hiding this comment

The 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
Copy link
Member

Choose a reason for hiding this comment

The 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.
11 changes: 9 additions & 2 deletions examples/examples.cabal
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
cabal-version: 1.12

-- This file has been generated from package.yaml by hpack version 0.31.2.
-- This file has been generated from package.yaml by hpack version 0.33.0.
--
-- see: https://github.com/sol/hpack
--
-- hash: 6b8490732f6b8b92312b3d51b0bbd4ccda7081b339c78f7346f5d017c7470535
-- hash: 59e86b4638bab931cb9948900fd08b2b735376fb44120e9889d1af96262ce663

name: examples
version: 0.4.0.1
Expand All @@ -28,7 +28,11 @@ source-repository head

library
exposed-modules:
ArithClient
ArithServer
Example
Proto.Protos.Calcs
Proto.Protos.Calcs_Fields
Proto.Protos.Grpcbin
Proto.Protos.Grpcbin_Fields
other-modules:
Expand All @@ -52,4 +56,7 @@ library
, proto-lens
, proto-lens-runtime
, unliftio-core >=0.1 && <0.3
, warp
, warp-grpc
, warp-tls
default-language: Haskell2010
5 changes: 4 additions & 1 deletion examples/package.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ dependencies:
- http2-grpc-types >= 0.5 && < 0.6
- http-types >= 0.12 && < 0.13
- unliftio-core >= 0.1 && < 0.3
- microlens
- proto-lens-runtime
- proto-lens
- http2-client-grpc
- http2-grpc-proto-lens
- http2-client
- microlens
- warp
- warp-grpc
- warp-tls

library:
source-dirs: src
Expand Down
28 changes: 28 additions & 0 deletions examples/prepare_calcs.sh
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
16 changes: 16 additions & 0 deletions examples/protos/calcs.proto
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;
}

48 changes: 48 additions & 0 deletions examples/src/ArithClient.hs
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

51 changes: 51 additions & 0 deletions examples/src/ArithServer.hs
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)

Loading