// Server-side interceptors: one chain wrapped around every method, whatever its call
// kind. An interceptor sees a call the way the engine does — the request messages it
// carries and the replies it produces — so one shape covers all four kinds: a unary
// call has one request and one reply, a streaming side has many. Registration order is
// outer-to-inner: the first-registered interceptor runs first and wraps the rest.
//
// A bidirectional call is the one shape whose messages arrive over time, so its chain
// runs once per batch of requests and once at half-close with none, which is where
// `BidiHandler::on_end` answers.
///|
/// What a call is, apart from the messages in it: the path, and which of gRPC's two
/// streaming sides it has. An interceptor that only wants unary calls reads this and
/// passes anything else straight on.
pub(all) struct CallInfo {
path : String
client_streaming : Bool
server_streaming : Bool
}
///|
/// A server interceptor: `(info, ctx, requests, next) -> replies`. Call `next(requests)`
/// to run the rest of the chain, with the messages possibly changed; return without
/// calling it to answer the call itself.
pub type Interceptor = (
CallInfo,
@stream.RpcContext,
Array[Bytes],
(Array[Bytes]) -> Array[Bytes],
) -> Array[Bytes]
///|
/// A unary server interceptor: `(ctx, request, next) -> reply`, the shape gRPC-go calls a
/// unary interceptor. It applies to unary methods only; [`Interceptor`] covers every kind.
pub type UnaryInterceptor = (
@stream.RpcContext,
Bytes,
(@stream.RpcContext, Bytes) -> Bytes,
) -> Bytes
///|
/// A server-streaming interceptor: `(ctx, request, next) -> replies`. It applies to
/// server-streaming methods only.
pub type StreamInterceptor = (
@stream.RpcContext,
Bytes,
(@stream.RpcContext, Bytes) -> Array[Bytes],
) -> Array[Bytes]
///|
/// Add an interceptor to the server's chain. It runs on every method the server serves,
/// whichever of the four call kinds it is; interceptors run in registration order,
/// outermost first.
pub fn H2Server::add_interceptor(
self : H2Server,
interceptor : Interceptor,
) -> Unit {
self.interceptors.push(interceptor)
}
///|
/// Add a unary interceptor. It is an [`Interceptor`] that only answers unary calls, the
/// one-request-one-reply shape; every other kind passes through untouched.
pub fn H2Server::add_unary_interceptor(
self : H2Server,
interceptor : UnaryInterceptor,
) -> Unit {
self.interceptors.push((info, ctx, requests, next) => {
if info.client_streaming || info.server_streaming {
return next(requests)
}
let request = if requests.length() > 0 { requests[0] } else { b"" }
let reply = interceptor(ctx, request, (_c, r) => {
let replies = next([r])
if replies.length() > 0 {
replies[0]
} else {
b""
}
})
[reply]
})
}
///|
/// Add a server-streaming interceptor: one request, an ordered run of replies.
pub fn H2Server::add_stream_interceptor(
self : H2Server,
interceptor : StreamInterceptor,
) -> Unit {
self.interceptors.push((info, ctx, requests, next) => {
if info.client_streaming || !info.server_streaming {
return next(requests)
}
let request = if requests.length() > 0 { requests[0] } else { b"" }
interceptor(ctx, request, (_c, r) => next([r]))
})
}
///|
/// Run a call through the chain: `base` is the registered handler, adapted to
/// `requests -> replies`.
fn H2Server::intercepted(
self : H2Server,
info : CallInfo,
ctx : @stream.RpcContext,
requests : Array[Bytes],
base : (Array[Bytes]) -> Array[Bytes],
) -> Array[Bytes] {
let mut h = base
for i = self.interceptors.length() - 1; i >= 0; i = i - 1 {
let interceptor = self.interceptors[i]
let next = h
h = messages => interceptor(info, ctx, messages, next)
}
h(requests)
}
///|
/// The shape of a call a registered handler serves.
fn @stream.Handler::info(self : @stream.Handler, path : String) -> CallInfo {
match self {
Unary(_) => { path, client_streaming: false, server_streaming: false, }
ServerStreaming(_) =>
{ path, client_streaming: false, server_streaming: true, }
ClientStreaming(_) =>
{ path, client_streaming: true, server_streaming: false, }
Bidi(_) => { path, client_streaming: true, server_streaming: true, }
// A pushed method answers over time, so it is the server side that streams; how many
// request messages it took is whatever the call sent.
Pushed(_) => { path, client_streaming: false, server_streaming: true, }
}
}