grapesy-1.1.1: src/Network/GRPC/Client.hs
module Network.GRPC.Client (
-- * Connecting to the server
Connection -- opaque
, Server(..)
, ConnParams(..)
, withConnection
, openConnection
, closeConnection
-- ** Reconnection policy
, ReconnectPolicy(..)
, ReconnectDecision(..)
, Reconnect(..)
, OnConnection(..)
, ReconnectTo(..)
, exponentialBackoff
-- ** Connection parameters
, Scheme(..)
, Address(..)
-- ** Secure connection (TLS)
, ServerValidation(..)
, Util.TLS.CertificateStoreSpec
, Util.TLS.certStoreFromSystem
, Util.TLS.certStoreFromCerts
, Util.TLS.certStoreFromPath
-- * Make RPCs
, Call -- opaque
, withRPC
-- ** Parameters
, CallParams -- opaque
, callTimeout
, callRequestMetadata
-- ** Timeouts
, Timeout(..)
, TimeoutValue(TimeoutValue, getTimeoutValue)
, TimeoutUnit(..)
, timeoutToMicro
-- * Ongoing calls
--
-- $openRequest
, sendInput
, recvOutput
, recvResponseMetadata
-- ** Protocol specific wrappers
, sendNextInput
, sendFinalInput
, sendEndOfInput
, recvResponseInitialMetadata
, recvNextOutput
, recvFinalOutput
, recvTrailers
-- ** Low-level\/specialized API
, ResponseHeaders_(..)
, ResponseHeaders
, ResponseHeaders'
, ProperTrailers_(..)
, ProperTrailers
, ProperTrailers'
, TrailersOnly_(..)
, TrailersOnly
, TrailersOnly'
, recvNextOutputElem
, recvInitialResponse
, recvOutputWithMeta
, sendInputWithMeta
-- * Communication patterns
, rpc
, rpcWith
-- * Exceptions
, ServerDisconnected(..)
, CallSetupFailure(..)
, InvalidTrailers(..)
) where
import Network.GRPC.Client.Call
import Network.GRPC.Client.Connection
import Network.GRPC.Client.Session (CallSetupFailure(..), InvalidTrailers(..))
import Network.GRPC.Client.StreamType (rpc, rpcWith)
import Network.GRPC.Spec
import Network.GRPC.Util.HTTP2.Stream (ServerDisconnected(..))
import Network.GRPC.Util.TLS qualified as Util.TLS
{-------------------------------------------------------------------------------
Ongoing calls
-------------------------------------------------------------------------------}
-- $openRequest
--
-- 'Call' denotes a previously opened request (see 'withRPC').
--
-- == Protobuf communication patterns
--
-- This is a general implementation of the
-- [gRPC specification](https://github.com/grpc/grpc/blob/master/doc/PROTOCOL-HTTP2.md).
-- 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](https://protobuf.dev/reference/protobuf/proto3-spec/#service_definition), 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](https://hackage.haskell.org/package/proto-lens-0.7.1.4/docs/Data-ProtoLens-Service-Types.html#t:HasMethodImpl).)
-- 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 'Network.GRPC.Server.recvInput' and
-- 'Network.GRPC.Server.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 'Network.GRPC.Server.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'.