// Server interceptors: a chain wrapped around a method handler, outermost first. Each
// interceptor sees the context and request, may rewrite either, calls `next` to proceed,
// and may post-process the reply — or return without calling `next` to short-circuit.
// This shows a unary chain rewriting request and reply, a short-circuiting auth guard, a
// server-streaming interceptor appending a message, and the one shape that covers all four
// call kinds — `add_interceptor`, which is what the two narrower forms are built from.
// Driven over the pure engines.
//
// moon run src/examples/17-interceptors
///|
fn drive(
server : @moonrpc.H2Server,
client : @moonrpc.H2Client,
id : Int32,
initial : Array[@http2.Frame],
) -> Unit raise {
let to_server : Array[@http2.Frame] = []
let to_client : Array[@http2.Frame] = []
for f in client.preface() {
to_server.push(f)
}
for f in server.preface() {
to_client.push(f)
}
for f in initial {
to_server.push(f)
}
let mut si = 0
let mut ci = 0
let mut steps = 0
while !client.is_done(id) && steps < 100000 {
steps = steps + 1
if si < to_server.length() {
let f = to_server[si]
si = si + 1
for r in server.feed(f) {
to_client.push(r)
}
} else if ci < to_client.length() {
let f = to_client[ci]
ci = ci + 1
for r in client.feed(f) {
to_server.push(r)
}
} else {
break
}
}
}
///|
fn ascii(bs : Bytes) -> String {
let buf = StringBuilder()
for b in bs {
buf.write_char((b.to_int() & 0xFF).unsafe_to_char())
}
buf.to_string()
}
///|
fn run() -> Unit raise {
// A unary chain: registration order is outer-to-inner, so the first-registered
// interceptor wraps the rest and its reply post-processing is the outermost.
let server = @moonrpc.H2Server::new()
server.register("/svc.S/M", req => b"handler(" + req + b")")
server.add_unary_interceptor((ctx, req, next) => {
b"outer[" + next(ctx, req) + b"]"
})
server.add_unary_interceptor((ctx, req, next) => next(ctx, b"inner+" + req))
// A fresh client per server: an H2Client's HPACK codec is stateful over one connection.
let c1 = @moonrpc.H2Client::new()
let (id, initial) = c1.unary("/svc.S/M", b"x")
drive(server, c1, id, initial)
println("unary chain reply: \{ascii(c1.reply(id).messages[0])}")
// A second server whose interceptor short-circuits: it returns before `next`, so the
// handler never runs.
let guarded = @moonrpc.H2Server::new()
guarded.register("/svc.S/Secret", _req => b"the-secret")
guarded.add_unary_interceptor((_ctx, _req, _next) => b"denied: no token")
let c2 = @moonrpc.H2Client::new()
let (id2, init2) = c2.unary("/svc.S/Secret", b"give me the secret")
drive(guarded, c2, id2, init2)
println("guarded call reply: \{ascii(c2.reply(id2).messages[0])}")
// A server-streaming interceptor appends a trailing message to the handler's run.
let streamer = @moonrpc.H2Server::new()
streamer.register_server_streaming("/svc.S/Feed", (_ctx, req) => {
[req + b"-1", req + b"-2"]
})
streamer.add_stream_interceptor((ctx, req, next) => {
let out = next(ctx, req)
out.push(b"intercepted")
out
})
let c3 = @moonrpc.H2Client::new()
let (id3, init3) = c3.unary("/svc.S/Feed", b"n")
drive(streamer, c3, id3, init3)
let reply3 = c3.reply(id3)
println("stream chain (\{reply3.messages.length()} replies):")
for m in reply3.messages {
println(" \{ascii(m)}")
}
// One interceptor over all four kinds: the chain reports the path and which sides
// stream, and passes the messages through untouched.
let universal = @moonrpc.H2Server::new()
universal.register("/svc.S/M", req => b"echo:" + req)
universal.register_server_streaming("/svc.S/Feed", (_ctx, req) => {
[req + b"-1", req + b"-2"]
})
universal.add_interceptor((info, _ctx, requests, next) => {
println(
" chain: \{info.path} client_streaming=\{info.client_streaming} server_streaming=\{info.server_streaming}",
)
next(requests)
})
let c4 = @moonrpc.H2Client::new()
let (id4, init4) = c4.unary("/svc.S/M", b"one")
drive(universal, c4, id4, init4)
println("universal, unary: \{ascii(c4.reply(id4).messages[0])}")
// The same client and the same connection carry the second call: a fresh stream id,
// which is what multiplexing is.
let (id5, init5) = c4.unary("/svc.S/Feed", b"n")
drive(universal, c4, id5, init5)
let streamed = c4.reply(id5).messages
println(
"universal, server streaming (\{streamed.length()} replies): \{ascii(streamed[0])} \{ascii(streamed[1])}",
)
}
///|
#coverage.skip
fn main {
run() catch {
e => println("error: \{e}")
}
}