The server can be run using the standard infrastructure offered by the
http2 package, but Network.GRPC.Server.Run provides some convenience
functions.
If you are using Protobuf (or if you have another way to compute a list of
methods at the type level), you may wish to use the infrastructure from
Network.GRPC.Server.StreamType (in particular,
fromMethods or
fromServices) to construct the set of
handlers.
The most important responsibility of this function is to deal with
any exceptions that the handler might throw, but in principle it has
full control over how requests are handled.
When a handler throws an exception other than a GrpcException, we use
this function to render that exception for the client (server-side
logging is taken care of by serverTopLevel). The default
implementation simply calls displayException on the exception, which
means the full context is visible on the client, which is most useful
for debugging. However, it is a potential security concern: if the
exception happens to contain sensitive information, this information
will also be visible on the client. You may therefore wish to override
the default behaviour.
When enabled, we verify at the start of each request that all request
headers are valid. By default we do not do this, throwing an error
only in scenarios where we really cannot continue.
Even if enabled, we will not attempt to parse rpc-specific metadata
(merely that the metadata is syntactically correct). See
getRequestMetadata for detailed discussion.
Depending on the choice of override, this may or may not be conform spec.
See https://datatracker.ietf.org/doc/html/rfc2045#section-5 for a spec
of the Content-Type header; the gRPC spec however disallows most of what
is technically allowed by this RPC.
Instances5Eq, Show, Generic, Default, Rep
EqContentTypeDefined in grpc-spec-1.0.0 · Network.GRPC.Spec.Headers.Common
ShowContentTypeDefined in grpc-spec-1.0.0 · Network.GRPC.Spec.Headers.Common
GenericContentTypeDefined in grpc-spec-1.0.0 · Network.GRPC.Spec.Headers.Common
DefaultContentTypeDefined in grpc-spec-1.0.0 · Network.GRPC.Spec.Headers.Common
Use the "raw" API by calling mkRpcHandler; this gives you full control
over the interaction with the client.
Use the API from Network.GRPC.Server.StreamType to define handlers that
use the Protobuf stream types. This API is more convenient, and can be used
to guarantee at compile-time that you have a handler for every method of
the services you support, but provides less flexibility (although it offers
an "escape" to the full API through
RawMethod).
Note on cancellation. The GRPC spec allows clients to "cancel" a
request (https://grpc.io/docs/guides/cancellation/). This does not
correspond to any specific message being sent across the network; instead,
the client simply disappears. The spec is quite clear that it is the
responsibility of the handler itself to monitor for this. In grapesy this
works as follows:
Handlers are not terminated when a client disappears. This allows the
handler to finish what it's doing, and terminate cleanly.
When a handler tries to receive a message from the client (recvInput), or
send a message to the client (sendOutput), and the client disappeared,
this will result in a ClientDisconnected exception,
which the handler can catch and deal with.
Cancellation is always at the request of the client. If the handler
terminates early (that is, before sending the final output and trailers), a
HandlerTerminated exception will be raised and sent to
the client as GrpcException with GrpcUnknown error code.
When the handler sends its first message to the client, grapesy must first
send the initial metadata (of type ResponseInitialMetadata) to the client.
This metadata can be updated at any point before that first message (for
example, after receiving some messages from the client) by calling
setResponseInitialMetadata. If this function is never called, however, then
we need a default value; mkRpcHandler therefore calls
setResponseInitialMetadata once before the handler proper, relying on the
Default instance.
For RPCs where a sensible default does not exist (perhaps the initial
response metadata needs the request metadata from the client, or even some
messages from the client), you can use mkRpcHandlerNoDefMetadata.
We do not make RpcHandler an instance of MFunctor (from the mmorph
package) because RpcHandler m is not a monad; this means that even though
the types line up, the concepts do not.
This will send a GrpcStatus of GrpcOk to the client; for anything else
(i.e., to indicate something went wrong), the server handler should call
sendGrpcException.
This is a blocking call if this is the final message (i.e., the call will not
return until the message has been written to the HTTP2 stream).
This closes the connection to the client; sending further messages will
result in an exception being thrown.
Instead of calling sendGrpcException handlers can also simply throw the
gRPC exception (the grapesyclient API treats this the same way: a
GrpcStatus other than GrpcOk will be raised as a GrpcException). The
difference is primarily one of preference/convenience, but the two are not
completely the same: when the GrpcException is thrown,
Context.serverTopLevel will see the handler throw an exception (and, by
default, log that exception); when using sendGrpcException, the handler is
considered to have terminated normally. For handlers defined using
Network.GRPC.Server.StreamType throwing the exception is the only option.
Technical note: if the response to the client has not yet been initiated when
sendGrpcException is called, this will make use of the gRPC
Trailers-Only
case.
The request metadata is included in the client's request headers when they
first make the request, and is therefore available immediately to the handler
(even if the first message from the client may not yet have been sent).
Dealing with invalid metadata
Metadata can be "invalid" to varying degrees, and we deal with this in
different ways:
The header could be syntactically invalid (e.g. binary data in an ASCII
header), or could use a reserved name. If serverVerifyHeaders is enabled,
such a request will be rejected; if not, getRequestMetadata will throw an
exception in this case. If you need access to these ill-formed headers, be
sure to disableserverVerifyHeaders, call getRequestHeaders to get
the full set of request headers, and then inspect requestUnrecognized.
There might be some additional metadata present. This is really a special
case of the previous point: it depends on the ParseMetadata instance
whether these additional headers result in an exception or whether they
are simply ignored. As above, the full set (including any ignored headers)
is always available through getRequestHeaders/requestMetadata.
Note: the ParseMetadata instance for NoMetadata is defined to throw an
exception if any metadata is present. The rationale here is that for rpc
without Metadata, there is no need to call getRequestMetadata and co; if
these functions are not called, then any metadata that is present will simply
be ignored. If getRequestMetadatais called, this amounts to check that
no metadata is present.
This can be set at any time before the response is initiated (either
implicitly by calling sendOutput, or explicitly by calling
initiateResponse or sendTrailersOnly). If the response has already
been initiated (and therefore the initial response metadata already sent),
will throw ResponseAlreadyInitiated.
Note that this is about the initial metadata; additional metadata can be
sent after the final message; see sendOutput.
This tells the client that there will be no more outputs. You should call
this (or sendFinalOutput) even when there is no special information to be
included in the trailers.
depending on whether the client indicates that msg0 is the last message
when it sends it, or indicates end-of-stream only after sending the last
message.
Many applications do not need to distinguish between these two cases, but
the API provided by recvInput makes it a bit awkward to treat them the
same, especially since it is an error to call recvInput again after
receiving either FinalElem or NoMoreElems. In this case, it may be more
convenient to use recvNextInputElem, which will report both cases as
This will cause the initial response metadata to be sent
(see also setResponseMetadata).
Does nothing if the response was already initated (that is, the response
headers, or trailers in the case of sendTrailersOnly, have already been
sent).
Use the gRPC Trailers-Only case for non-error responses
Under normal circumstances a gRPC server will respond to the client with
an initial set of headers, then zero or more messages, and finally a set of
trailers. When there are no messages, this can be collapsed into a single
set of trailers (or headers, depending on your point of view); the gRPC
specification refers to this as the Trailers-Only case. It mandates:
Most responses are expected to have both headers and trailers but
Trailers-Only is permitted for calls that produce an immediate error.
In grapesy, if a server handler throws a GrpcException, we will make use
of this Trailers-Only case if applicable, as per the specification.
However, some servers make use of Trailers-Only also in non-error cases.
For example, the listFeatures handler in the official Python route guide
example server will use Trailers-Only if there are no features to report.
Since this is not conform the gRPC specification, we do not do this in
grapesy by default, but we make the option available through
sendTrailersOnly.
Get full request headers, including any potential invalid headers
NOTE: When serverVerifyHeaders is enabled the caller can be sure that the
RequestHeaders' do not contain any errors, even though unfortunately this
is not visible from the type.
This is indicative of a misbehaving peer: a client should not use a
compression algorithm unless they have evidence that the server supports
it. The server cannot process such a request, as it has no way of
decompression messages sent by the client.
Note on terminology: HTTP has "methods" such as POST, GET, etc; gRPC
supports only POST, and when another HTTP method is chosen, this will
result in CallSetupInvalidResourceHeaders. However, gRPC itself also
has the concept of a "method" (a method, or gRPC call, supported by a
particular service); it's these methods that
CallSetupUnimplementedMethod is referring to.
If you choose to catch this exception, you are advised to match against
the type, rather than against the constructor, and then use the record
accessors to get access to the fields. Future versions of grapesy may
record more information.