Git Product home page Git Product logo

Comments (2)

Zachaccino avatar Zachaccino commented on August 17, 2024

I made a record to represent data like this:

data GenericRPC =
  GenericRPC {
    service :: ByteString,
    method :: ByteString
  }
  deriving (Eq, Show)

instance IsRPC GenericRPC where
  path rpc = "/" <> service rpc <> "/" <> method rpc

and my RPC is then represented as follows:

GenericRPC "Greeter" "SayHello"

However, when I run the server and client together, this error occurs.

Right (Right ([(":status","200"),("content-type","application/grpc+proto"),("trailer","Grpc-Status"),("trailer","Grpc-Message"),("trailer","Grpc-Status-Details-Bin")],Just [("grpc-message","unknown service Greeter"),("grpc-status","12")],Left "not enough bytes"))        

I made sure that both the client and server was given the same GenericRPC that I created. Not sure why the server cannot find the service.

from http2-grpc-haskell.

Zachaccino avatar Zachaccino commented on August 17, 2024

I think I got it. It was just me being stupid all along... But anyway, I suppose I should provide a simple example of how to use this library since there isn't any up to date examples I can find.

Since the library is expecting breaking changes, this might not work in the near future. so the versions are documented below:
http2-client-grpc - 0.8.0.0
warp-grpc - 0.3.0.0

This example demonstrates:

  1. How to represent RPC.
  2. How to create a simple unary request.
  3. How to encode and decode request and response.

To use this example, you need to generate your certificate and key using the following command:

openssl genrsa -out key.pem 2048
openssl req -new -key key.pem -out certificate.csr
openssl x509 -req -in certificate.csr -signkey key.pem -out certificate.pem

In Protobuf File

syntax = "proto3";

package default.v1;

service Greeter {
  rpc SayHello(HelloRequest) returns (HelloResponse) {}
}

message HelloRequest {
  string content = 1;
}

message HelloResponse {
  string content = 1;
}

The following haskell code contains a module called Proto.Greeter and Proto.Greeter_Fields. This is just the auto-generated code using protoc compiler on our protobuf file shown above.

The Client Code:

import qualified Network.GRPC.Client            as GC
import qualified Network.HTTP2.Client           as HC
import qualified Network.GRPC.Client.Helpers    as GCH
import Network.GRPC.Client.Helpers              (GrpcClient)
import qualified Data.ProtoLens.Message         as PLM
import Network.HTTP2.Client.Exceptions          (ClientIO)
import qualified Proto.Greeter                  as PD
import qualified Proto.Greeter_Fields           as PDF
import Proto.Greeter                            (Greeter(..))
import Control.Monad.IO.Class                   (liftIO)
import qualified RPC                            as R
import RPC                                      (GenericRPC)
import           Proto.Greeter                  ( Greeter(..)
                                                , HelloRequest(..)
                                                , HelloResponse(..)
                                                )
import Network.GRPC.HTTP2.Types                 (IsRPC(..))


mkClient :: ClientIO GrpcClient
mkClient = GCH.setupGrpcClient $ GCH.grpcClientConfigSimple "127.0.0.1" 8000 True


runClient :: ClientIO ()
runClient = do
  client <- mkClient
  ret <- (GCH.rawUnary sayHelloRPC client request) :: ClientIO (Either HC.TooMuchConcurrency (GC.RawReply HelloResponse))
  liftIO $ print ret
  return ()
  where
    sayHelloRPC = R.createRPC "Greeter" "SayHello"
    request :: HelloRequest
    request = R.createHelloRequest "CLIENT REQUEST"

The Server Code:

{-# LANGUAGE OverloadedStrings #-}


import qualified Network.GRPC.Server            as S
import Network.GRPC.Server                      ( ServiceHandler
                                                , UnaryHandler
                                                )
import qualified Network.Wai.Handler.WarpTLS    as WT
import Network.Wai.Handler.WarpTLS              (TLSSettings)
import qualified Network.Wai.Handler.Warp       as W
import Network.Wai.Handler.Warp                 (Settings)
import qualified Proto.Greeter                  as PD
import qualified Proto.Greeter_Fields           as PDF
import Proto.Greeter                            (Greeter(..))
import Data.ProtoLens.Message                   as PLM
import qualified Network.GRPC.HTTP2.Encoding    as GE
import qualified RPC                            as R
import RPC                                      (GenericRPC)
import           Proto.Greeter                  ( Greeter(..)
                                                , HelloRequest(..)
                                                , HelloResponse(..)
                                                )



-- Some useful data types.
type TLSKeyPath = FilePath
type TLSCertPath = FilePath


-- Create a custom tls certificate given path to the key and certificate.
loadTlsSettings :: TLSCertPath -> TLSKeyPath -> TLSSettings
loadTlsSettings = WT.tlsSettings


-- Create a default tls certificate.
defaultTlsSettings :: TLSSettings
defaultTlsSettings = loadTlsSettings cp kp
  where
    kp = "/path/to/key.pem"
    cp = "/path/to/certificate.pem"


-- Create a default warp setting.
defaultWarpSetting :: Settings
defaultWarpSetting = W.setHost "127.0.0.1" $ W.setPort 8000 W.defaultSettings


-- Create a set of handlers.
handlers :: [ServiceHandler]
handlers =
  [ S.unary sayHelloRPC handleSayHello
  ]
  where
    sayHelloRPC = R.createRPC "Greeter" "SayHello"


handleSayHello :: UnaryHandler HelloRequest HelloResponse
handleSayHello _ input = do
  print ("sayHello")
  print $ show input
  return $ R.createHelloResponse "SERVER RESPONSE"


-- Start a grpc server.
runServer :: IO ()
runServer = do
  print "Server Started"
  runGrpc defaultTlsSettings defaultWarpSetting handlers [GE.gzip]
  print "Server Terminated"

The RPC Representation:

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE OverloadedLabels #-}


import           Data.ByteString                (ByteString)
import qualified Data.ByteString                as BS
import qualified Data.ByteString.Lazy           as BL
import           Data.ByteString.Builder        (Builder)
import           Proto3.Wire                    as W
import qualified Proto3.Wire.Encode             as Enc
import qualified Proto3.Wire.Decode             as Dec
import qualified Proto.Greeter_Fields           as PDF
import qualified Proto.Greeter                  as PD
import qualified Data.Monoid                    as MND
import           Proto.Greeter                  ( Greeter(..)
                                                , HelloRequest(..)
                                                , HelloResponse(..)
                                                )
import Network.GRPC.HTTP2.Encoding              ( GRPCInput(..)
                                                , GRPCOutput(..)
                                                , Compression(..)
                                                , Decoder
                                                )
import Network.GRPC.HTTP2.Types                 (IsRPC(..))
import qualified Data.Binary.Get                as BG
import           Data.Binary.Builder            as BB
import           Data.Bifunctor                 as BF
import qualified Data.ProtoLens.Labels          as PLL
import           Lens.Micro                     ( (^.)
                                                , (&)
                                                , (.~)
                                                )
import qualified Data.ProtoLens.Message         as PLM
import qualified Data.Text.Lazy                 as TL

-- A representation of an RPC.
data GenericRPC =
  GenericRPC {
    service :: ByteString,
    method :: ByteString
  }
  deriving (Eq, Show)


createRPC :: ByteString -> ByteString -> GenericRPC
createRPC = GenericRPC


instance IsRPC GenericRPC where
  path rpc = "/" <> service rpc <> "/" <> method rpc


-- Encoder and Decoder
class GenericEncoder a where
  encode :: a -> Enc.MessageBuilder
  decode :: Dec.Parser Dec.RawMessage a


instance GenericEncoder a => GRPCInput GenericRPC a where
  encodeInput _ = genericEncode
  decodeInput _ = genericDecode


instance GenericEncoder a => GRPCOutput GenericRPC a where
  encodeOutput _ = genericEncode
  decodeOutput _ = genericDecode


genericEncode :: GenericEncoder m => Compression -> m -> Builder
genericEncode compression plain =
    mconcat [ BB.singleton (if _compressionByteSet compression then 1 else 0)
            , BB.putWord32be (fromIntegral $ BS.length bin)
            , BB.fromByteString bin
            ]
  where
    bin = _compressionFunction compression $ BL.toStrict $ Enc.toLazyByteString (encode plain)


genericDecode :: GenericEncoder m => Compression -> Decoder (Either String m)
genericDecode compression =
  BG.runGetIncremental $ do
    isCompressed <- BG.getInt8
    let decompress = if isCompressed == 0 then pure else _decompressionFunction compression
    n <- BG.getWord32be
    BF.first show . Dec.parse decode <$> (decompress =<< BG.getByteString (fromIntegral n))


createHelloRequest :: TL.Text -> HelloRequest
createHelloRequest c =
  PLM.defMessage
    & #content .~ (TL.toStrict c)


createHelloResponse :: TL.Text -> HelloResponse
createHelloResponse c =
  PLM.defMessage
    & #content .~ (TL.toStrict c)


-- Making concrete encode and decode methods.
instance GenericEncoder HelloRequest where
  encode r = Enc.text 1 . TL.fromStrict $ r ^. #content
  decode = createHelloRequest <$> (Dec.one Dec.text MND.mempty `Dec.at` 1)


-- Making concrete encode and decode methods.
instance GenericEncoder HelloResponse where
  encode r = Enc.text 1 . TL.fromStrict $ r ^. #content
  decode = createHelloResponse <$> (Dec.one Dec.text MND.mempty `Dec.at` 1)

from http2-grpc-haskell.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.