HORIZON HASKELLDocslts/ghc-9.10.xc74966e2026-09-27Search names, modules, packages, or :: a typeCtrl K

GHC 9.10.3 · lts/ghc-9.10.x · c74966e · 2026-09-27

Modulegrapesy-1.1.1Haskell2010

Network.GRPC.Client

  • 29 types
  • 28 values
  • Packagegrapesy-1.1.1
  • Exports58
  • LanguageHaskell2010
  • LicenceBSD-3-Clause
  • SourceConnection.hs

Connecting to the server

6 declarations
datadata Connection
#

Open connection to server

See withConnection.

Before we can send RPC requests, we have to connect to a specific server first. Once we have opened a connection to that server, we can send as many RPC requests over that one connection as we wish. Connection abstracts over this connection, and also maintains some information about the server.

We can make many RPC calls over the same connection.

Instances1CanCallRPC
datadata ConnParams
#

Connection configuration

You may wish to override connReconnectPolicy.

Constructors

  • ConnParams
    • connCompression :: Negotation

      Compression negotation

    • connDefaultTimeout :: Maybe Timeout

      Default timeout

      Individual RPC calls can override this through CallParams.

    • connOnConnection :: OnConnection

      Action to run upon successful connection

    • connReconnectPolicy :: ReconnectPolicy

      Reconnection policy

      NOTE: The default ReconnectPolicy is DontReconnect, as per the spec (see ReconnectPolicy). You may wish to override this in order to enable Wait for Ready semantics (retry connecting to a server when it is not yet ready) as well as automatic reconnects (reconnecting after a server disappears). The latter can be especially important when there are proxies, which tend to drop connections after a certain amount of time.

    • connContentType :: Maybe ContentType

      Optionally override the content type

      If Nothing, the Content-Type header will be omitted entirely (this is not conform gRPC spec).

    • connVerifyHeaders :: Bool

      Should we verify all request headers?

      This is the client analogue of serverVerifyHeaders; see detailed discussion there.

      Arguably, it is less essential to verify headers on the client: a server must deal with all kinds of different clients, and might want to know if any of those clients has expectations that it cannot fulfill. A client however connects to a known server, and knows what information it wants from the server.

    • connInitCompression :: Maybe Compression

      Optionally set the initial compression algorithm

      Under normal circumstances, the grapesy client will only start using compression once the server has informed it what compression algorithms it supports. This means the first message will necessarily be uncompressed. connCompression can be used to override this behaviour, but should be used with care: if the server does not support the selected compression algorithm, it will not be able to decompress any messages sent by the client to the server.

    • connHTTP2Settings :: HTTP2Settings

      HTTP2 settings

Instances1Default
valuewithConnection :: ConnParams -> Server -> (Connection -> IO a) -> IO a
#

Open a connection to the server.

See withRPC for making individual RPCs on the new connection.

The connection to the server is set up asynchronously; the first call to withRPC will block until the connection has been established.

If the server cannot be reached, the behaviour depends on connReconnectPolicy: if the policy allows reconnection attempts, we will wait the time specified by the policy and try again. This implements the gRPC "Wait for ready" semantics.

If the connection to the server is lost after it has been established, any currently ongoing RPC calls will be closed; attempts at further communication on any of these calls will result in a ServerDisconnected exception being thrown. If that exception is caught, and the ReconnectPolicy allows, we will automatically try to re-establish a connection to the server. This can be especially important when there is a proxy between the client and the server, which may drop an existing connection after a certain period.

NOTE: The default ReconnectPolicy is DontReconnect, as per the gRPC specification of "Wait for ready" semantics. You may wish to override this default.

Clients should prefer sending many calls on a single connection, rather than sending few calls on many connections, as minimizing the number of connections used via this interface results in better memory behavior. See well-typed/grapesy#134 for discussion.

Open a connection to the server.

See withConnection for details.

Warning: Connections hold open resources and must be closed using closeConnection. To prevent resource and memory leaks due to asynchronous exceptions, it is recommended to use the bracketed function withConnection whenever possible, and otherwise run functions that allocate and release a resource with asynchronous exceptions masked, and ensure that every use allocate operation is followed by the corresponding release operation even in the presence of asynchronous exceptions, e.g., using bracket.

Reconnection policy

newtypenewtype ReconnectPolicy
#

Reconnect policy

See exponentialBackoff for a convenient function to construct a policy.

When we get disconnected from the server, we will runReconnectPolicy to decide what to do next. This can run arbitrary IO actions; two example use cases are

  • wait until we reconnect (run threadDelay)

  • update some application-specific state to indicate that we are not currently connected to the server (see also onReconnect)

Instances1Default
datadata Reconnect
#

Decision made by a ReconnectPolicy

Constructors

newtypenewtype OnConnection
#

An action to run upon successful (re)connection to a server

This can be used to, for example, display a message to the user that the connection has been (re)established .

Instances1Default
valueexponentialBackoff
  1. :: (Int -> IO ())

    Execute the delay (in microseconds)

    The default choice here can simply be threadDelay, but it is also possible to use this to add some logging. Simple example:

    waitFor :: Int -> IO ()
    waitFor delay = do
      putStrLn $ "Disconnected. Reconnecting after " ++ show delay ++ "μs"
      threadDelay delay
      putStrLn "Reconnecting now."
  2. -> Double

    Exponent

  3. -> (Double, Double)

    Initial delay

  4. -> Word

    Maximum number of attempts

  5. -> ReconnectPolicy
#

Exponential backoff

If the exponent is 1, the delay interval will be the same every step; for an exponent of greater than 1, we will wait longer each step.

Connection parameters

datadata Address
#

Address

The address of a server to connect to. This is not standard gRPC nomenclature, but follows convention such as adopted by grpcurl and grpc-client-cli, which distinguish between the address of a server to connect to (hostname and port), and the (optional) HTTP authority, which is an (optional) string to be included as the HTTP2 :authority pseudo-header.

Constructors

  • Address
    • addressHost :: HostName

      Hostname

    • addressPort :: PortNumber

      TCP port

    • addressAuthority :: Maybe String

      Authority

      When the authority is not specified, it defaults to addressHost.

      This is used both for the HTTP2 :authority pseudo-header as well as for TLS SNI (if using a secure connection).

      Although the HTTP(2) specification allows the authority to include a port number, and many servers can accept this, this will not work with TLS, and it is therefore recommended not to include a port number. Note that the HTTP2 spec explicitly disallows the authority to include userinfo@.

Instances1Show
  • Show AddressDefined in grpc-spec-1.0.0 · Network.GRPC.Spec.Headers.PseudoHeaders

Secure connection (TLS)

datadata ServerValidation
#

How does the client want to validate the server?

Constructors

  • ValidateServer CertificateStoreSpec

    Validate the server

    The CertificateStore is a collection of trust anchors. If Nothing is specified, the system certificate store will be used.

  • NoServerValidation

    Skip server validation

    WARNING: This is dangerous. Although communication with the server will still be encrypted, you cannot be sure that the server is who they claim to be.

Instances1Show
datadata CertificateStoreSpec
#

Certificate store specification (for certificate validation)

This is a deep embedding, describing how to construct a certificate store. The actual construction happens in loadCertificateStore.

There are three primitive ways to construct a CertificateStore: certStoreFromSystem, certStoreFromCerts, and certStoreFromPath; please refer to the corresponding documentation.

You can also combine CertificateStores through the Monoid instance.

Instances3Show, Semigroup, Monoid

Load certificate store from disk

The path may point to single file (multiple PEM formatted certificates concanated) or directory (one certificate per file, file names are hashes from certificate).

Make RPCs

2 declarations
datadata Call (rpc :: k)
#

State of the call

This type is kept abstract (opaque) in the public facing API.

valuewithRPC
  1. :: (MonadMask m, MonadIO m, SupportsClientRpc rpc, HasCallStack)
  2. => Connection
  3. -> CallParams rpc
  4. -> Proxy rpc
  5. -> Call rpc -> m a
  6. -> m a
#

Scoped RPC call

This is the low-level API for making RPC calls, providing full flexibility. You may wish to consider using the infrastructure from Network.GRPC.Client.StreamType.IO instead.

Typical usage:

withRPC conn def (Proxy @ListFeatures) $ \call -> do
  .. use 'call' to send and receive messages

for some previously established connection conn (see withConnection) and where ListFeatures is some kind of RPC.

The call is setup in the background, and might not yet have been established when the body is run. If you want to be sure that the call has been setup, you can call recvResponseMetadata.

Leaving the scope of withRPC before the client informs the server that they have sent their last message (using sendInput or sendEndOfInput) is considered a cancellation, and accordingly throws a GrpcException with GrpcCancelled (see also https://grpc.io/docs/guides/cancellation/).

There is one exception to this rule: if the server unilaterally closes the RPC (that is, the server already sent the trailers), then the call is considered closed and the cancellation exception is not raised. Under normal circumstances (with well-behaved server handlers) this should not arise. (The gRPC specification itself is not very specific about this case; see discussion at https://stackoverflow.com/questions/55511528/should-grpc-server-side-half-closing-implicitly-terminate-the-client.)

If there are still inbound messages upon leaving the scope of withRPC no exception is raised (but the call is nonetheless still closed, and the server handler will be informed that the client has disappeared).

Note on timeouts: if a timeout is specified for the call (either through callTimeout or through connDefaultTimeout), when the timeout is reached the RPC is cancelled; any further attempts to receive or send messages will result in a GrpcException with GrpcDeadlineExceeded. As per the gRPC specification, this does not rely on the server; this does mean that the same deadline also applies if the client is slow (rather than the server).

Parameters

Timeouts

datadata Timeout
#

Timeout

Instances4Eq, Show, Generic, Rep
newtypenewtype TimeoutValue
#

Positive integer with ASCII representation of at most 8 digits

Instances4Eq, Show, Generic, Rep
datadata TimeoutUnit
#

Timeout unit

Constructors

Instances4Eq, Show, Generic, Rep

Translate Timeout to microseconds

For Nanosecond timeout we round up.

Note: the choice of Integer for the result is important: timeouts can be quite long, and might easily exceed the range of a 32-bit int: 2^31 microseconds is roughly 35 minutes (on 64-bit architectures this is much less important; 2^63 microseconds is 292,277.2 years). We could use Int64 or Word64, but Integer works nicely with the unbounded-delays package.

Ongoing calls

3 declarations

Call denotes a previously opened request (see withRPC).

Protobuf communication patterns

This is a general implementation of the gRPC specification. As such, these functions do not provide explicit support for the common communication patterns of non-streaming, server-side streaming, client-side streaming, or bidirectional streaming. These are not part of the gRPC standard, but are part of its Protobuf instantiation, although these patterns are of course not really Protobuf specific. We provide support for these communication patterns, independent from a choice of serialization format, in Network.GRPC.Common.StreamType and Network.GRPC.Client.StreamType (and Network.GRPC.Server.StreamType for the server side).

If you only use the abstractions provided in "Network.GRPC.*.StreamType", you can ignore the rest of the discussion below, which applies only to the more general interface.

Stream elements

Both sendInput and recvOutput work with StreamElem:

data StreamElem b a =
    StreamElem a
  | FinalElem a b
  | NoMoreElems b

The intuition is that we are sending messages of type a (see "Inputs and outputs", below) and then when we send the final message, we can include some additional information of type b (see "Metadata", below).

Inputs and outputs

By convention, we refer to messages sent from the client to the server as "inputs" and messages sent from the server to the client as "outputs" (we inherited this terminology from proto-lens.) On the client side we therefore have recvOutput and sendInput defined as

recvOutput :: Call rpc -> m (StreamElem (ResponseTrailingMetadata rpc) (Output rpc))
sendInput  :: Call rpc -> StreamElem NoMetadata (Input rpc) -> m ()

and on the server side we have recvInput and sendOutput:

recvInput  :: Call rpc -> IO (StreamElem NoMetadata (Input rpc))
sendOutput :: Call rpc -> StreamElem (ResponseTrailingMetadata rpc) (Output rpc) -> IO ()

Metadata

Both the server and the client can send some metadata before they send their first message; see withRPC and callRequestMetadata for the client-side (and setResponseInitialMetadata for the server-side).

The gRPC specification allows the server, but not the client, to include some final metadata as well; this is the reason between the use of ResponseTrailingMetadata for messages from the server to the client versus NoMetadata for messages from the client.

FinalElem versus NoMoreElems

Network.GRPC.Common.StreamElem allows to mark the final message as final when it is sent (Network.GRPC.Common.FinalElem), or retroactively indicate that the previous message was in fact final (Network.GRPC.Common.NoMoreElems). The reason for this is technical in nature.

Suppose we are doing a grpc+proto non-streaming RPC call. The input message from the client to the server will be sent over one or more HTTP2 DATA frames (chunks of the input). The server will expect the last of those frames to be marked as END_STREAM. The HTTP2 specification does allow sending an separate empty DATA frame with the END_STREAM flag set to indicate no further data is coming, but not all gRPC servers will wait for this, and might either think that the client is broken and disconnect, or might send the client a RST_STREAM frame to force it to close the stream. To avoid problems, therefore, it is better to mark the final DATA frame as END_STREAM; in order to be able to do that, sendInput needs to know whether an input is the final one. It is therefore better to use FinalElem instead of NoMoreElems for outgoing messages, if possible.

For incoming messages the situation is different. Now we do expect HTTP trailers (final metadata), which means that we cannot tell from DATA frames alone if we have received the last message: it will be the frame containing the trailers that is marked as END_STREAM, with no indication on the data frame just before it that it was the last one. We cannot wait for the next frame to come in, because that would be a blocking call (we might have to wait for the next TCP packet), and if the output was not the last one, we would unnecessarily delay making the output we already received available to the client code. Typically therefore clients will receive a StreamElem followed by NoMoreElems.

Of course, for a given RPC and its associated communication pattern we may know whether any given message was the last; in the example above of a non-streaming grpc+proto RPC call, we only expect a single output. In this case the client can (and should) call recvOutput again to wait for the trailers (which, amongst other things, will include the trailerGrpcStatus). The specialized functions from Network.GRPC.Client.StreamType take care of this; if these functions are not applicable, users may wish to use recvFinalOutput.

Receive an output from the peer

After the final Output, you will receive any custom metadata (application defined trailers) that the server returns. We do NOT include the GrpcStatus here: a status of GrpcOk carries no information, and any other status will result in a GrpcException. Calling recvOutput again after receiving the trailers is a bug and results in a RecvAfterFinal exception.

valuerecvResponseMetadata :: MonadIO m => Call rpc -> m (ResponseMetadata rpc)
#

The initial metadata that was included in the response headers

The server can send two sets of metadata: an initial set of type ResponseInitialMetadata when it first initiates the response, and then a final set of type ResponseTrailingMetadata after the final message (see recvOutput).

It is however possible for the server to send only a single set; this is the gRPC "Trailers-Only" case. The server can choose to do so when it knows it will not send any messages; in this case, the initial response metadata is fact of type ResponseTrailingMetadata instead. The ResponseMetadata type distinguishes between these two cases.

If the "Trailers-Only" case can be ruled out (that is, if it would amount to a protocol error), you can use recvResponseInitialMetadata instead.

This can block: we need to wait until we receive the metadata. The precise communication pattern will depend on the specifics of each server:

  • It might be necessary to send one or more inputs to the server before it returns any replies.

  • The response metadata will be available before the first output from the server, and may indeed be available well before.

Protocol specific wrappers

valuesendFinalInput :: MonadIO m => Call rpc -> Input rpc -> m ()
#

Send final input

For some servers it is important that the client marks the final input /when it is sent/. If you really want to send the final input and separately tell the server that no more inputs will be provided, use sendEndOfInput (or sendInput).

Low-level/specialized API

datadata ResponseHeaders_ (f :: Type -> Type)
#

Response headers

Constructors

Instances10Generic, Coerce, Traversable, HasRequiredHeaders, Eq, Show, …
datadata ProperTrailers_ (f :: Type -> Type)
#

Information sent by the peer after the final output

Response trailers are a HTTP2 concept: they are HTTP headers that are sent after the content body. For example, imagine the server is streaming a file that it's reading from disk; it could use trailers to give the client an MD5 checksum when streaming is complete.

Constructors

Instances8Generic, Coerce, Traversable, Eq, Show, Rep, …

Trailers sent after the response, allowing for invalid trailers

We do not parameterize this over the type of synthesized errors: unlike response (or request) headers, we have no opportunity to check the trailers for synthesized errors ahead of time, so having a type to signal "trailers without synthesized errors" is not particularly useful.

datadata TrailersOnly_ (f :: Type -> Type)
#

Trailers sent in the gRPC Trailers-Only case

We deal with the HTTP status elsewhere.

Constructors

Instances10Generic, Coerce, Traversable, HasRequiredHeaders, Eq, Show, …

Communication patterns

2 declarations
valuerpc :: (CanCallRPC m, SupportsClientRpc rpc, SupportsStreamingType rpc styp, Default (RequestMetadata rpc)) => ClientHandler' styp m rpc
#

Construct RPC handler

This has an ambiguous type, and is intended to be called using a type application indicating the rpc method to call, such as

rpc @Ping

provided that Ping is some type with an IsRPC instance. In some cases it may also be needed to provide a streaming type:

rpc @Ping @NonStreaming

though in most cases the streaming type should be clear from the context or from the choice of rpc.

See nonStreaming and co for examples. See also rpcWith.

Exceptions

3 declarations