Client Middleware
Client middleware wraps every outbound RPC, before and after the call itself. It’s the right place for the cross-cutting work that doesn’t belong in the call site: logging, tracing, metrics, auth, request/response shaping, retries, and back-off.
Onion model
gRPCity uses Koa-style onion-model middleware: each function decides when to call next(), and code on either side of next() runs around everything below it.
Three middlewares a, b, c registered in that order produce this trace:
enter a
enter b
enter c
rpc-method()
exit c
exit b
exit aUsage
gRPCity Client provides the clients.use() method to add middleware.
The current version only supports middleware for async unary and async stream methods; stream and callback are not yet supported.
Usage is as follows:
const clients = await loader.initClients()
// Add one by one
clients.use(f1)
clients.use(f2)
clients.use(f3)
// Add with multiple parameters
clients.use(f1, f2, f3)
// Add using an array
clients.use([f1, f2, f3])Context
const middleware = async (ctx, next) => {
console.log(ctx)
await next()
console.log(ctx)
}The first printed context includes path, method, and request, which are added before calling the server and can be modified. The method includes the type of call and the metadata and options parameters of the request.
{
path: '/helloworld.Greeter/SayGreet',
method: {
requestStream: false,
responseStream: false,
metadata: Metadata { internalRepr: [Map], options: {} },
options: { deadline: 2024-01-16T16:22:11.836Z }
},
request: { name: 'greeter' }
}The second printed context adds peer, status, metadata, and response, which are added after execution and can be modified. The final response is based on the modified result.
{
path: '/helloworld.Greeter/SayGreet',
method: {
requestStream: false,
responseStream: false,
metadata: Metadata { internalRepr: [Map], options: {} },
options: { deadline: 2024-01-16T16:22:11.836Z }
},
request: { name: 'greeter' },
metadata: Metadata {
internalRepr: Map(9) {
'content-type' => [Array],
'x-cache-control' => [Array],
'x-business-id' => [Array],
'x-timestamp-client' => [Array],
'x-client-hostname' => [Array],
'x-service-path' => [Array],
'user-agent' => [Array],
'x-timestamp-server' => [Array],
'date' => [Array]
},
options: {}
},
response: { message: 'hello, greeter' },
status: {
code: 0,
details: 'OK',
metadata: Metadata { internalRepr: Map(0) {}, options: {} }
},
peer: '::1:9099'
}Example
Using call log printing as an example:
const log = async (ctx, next) => {
const startTime = Date.now()
await next()
const responseTimeMs = Date.now() - startTime
console.log({
path: ctx.path,
peer: ctx.peer,
responseTimeMs
})
}
}
export default logAdding middleware:
import log from './log.js'
// other...
clients.use(log)With this, we have completed the writing and usage of client-side middleware.