{-# LANGUAGE OverloadedStrings #-} module Network.GRPC.Client ( -- * Connecting to the server Connection -- opaque , Server(..) , ConnParams(..) , withConnection -- ** Reconnection policy , ReconnectPolicy(..) , 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'.