mirror of https://github.com/grpc/grpc-node.git
Add interceptors example
This commit is contained in:
parent
bcea4b40bf
commit
443992bd04
|
@ -0,0 +1,120 @@
|
|||
# Interceptor
|
||||
|
||||
Node gRPC provides simple APIs to implement and install interceptors on clients
|
||||
and servers. An interceptor intercepts the execution of each incoming/outgoing
|
||||
RPC call on the client or server where it is installed. Users can use
|
||||
interceptors to do logging, authentication/authorization, metrics collection,
|
||||
and many other functions that can be shared across RPCs.
|
||||
|
||||
## Run the server
|
||||
|
||||
```
|
||||
node server.js
|
||||
```
|
||||
|
||||
## Run the client
|
||||
|
||||
```
|
||||
node client.js
|
||||
```
|
||||
|
||||
# Explanation
|
||||
|
||||
In Node gRPC, clients and servers each have their own types of interceptors.
|
||||
|
||||
## Client
|
||||
|
||||
Node gRPC client interceptors are formally specified in [gRFC L5](https://github.com/grpc/proposal/blob/master/L5-node-client-interceptors.md).
|
||||
An interceptor is a function that can wrap a call object with an
|
||||
`InterceptingCall`, with intercepting functions for individual call operations.
|
||||
To illustrate, the following is a trivial interceptor with all interception
|
||||
methods:
|
||||
|
||||
```js
|
||||
const interceptor = function(options, nextCall) {
|
||||
const requester = {
|
||||
start: function(metadata, listener, next) {
|
||||
const listener = {
|
||||
onReceiveMetadata: function(metadata, next) {
|
||||
next(metadata);
|
||||
},
|
||||
onReceiveMessage: function(message, next) {
|
||||
next(message);
|
||||
},
|
||||
onReceiveStatus: function(status, next) {
|
||||
next(status);
|
||||
}
|
||||
};
|
||||
next(metadata, listener);
|
||||
},
|
||||
sendMessage: function(message, next) {
|
||||
next(messasge);
|
||||
},
|
||||
halfClose: function(next) {
|
||||
next();
|
||||
},
|
||||
cancel: function(message, next) {
|
||||
next();
|
||||
}
|
||||
};
|
||||
return new InterceptingCall(nextCall(options), requester);
|
||||
};
|
||||
```
|
||||
|
||||
The requester intercepts outgoing operations, and the listener intercepts
|
||||
incoming operations. Each intercepting method can read or modify the data for
|
||||
that operation before passing it along to the `next` callback.
|
||||
|
||||
The `RequesterBuilder` and `ListenerBuilder` utility classes provide an
|
||||
alternative way to construct requester and listener objects
|
||||
|
||||
## Server
|
||||
|
||||
Node gRPC server interceptors are formally specified in [gRFC L112](https://github.com/grpc/proposal/blob/master/L112-node-server-interceptors.md).
|
||||
Similar to client interceptors, a server interceptor is a function that can
|
||||
wrap a call object with a `ServerInterceptingCall`, with intercepting functions
|
||||
for individual call operations. Server intercepting functions broadly mirror
|
||||
the client intercepting functions, with sending and receiving switched. To
|
||||
illustrate, the following is a trivial server interceptor with all interception
|
||||
methods:
|
||||
|
||||
```js
|
||||
const interceptor = function(methodDescriptor, call) {
|
||||
const responder = {
|
||||
start: function(next) {
|
||||
const listener = {
|
||||
onReceiveMetadata: function(metadata, next) {
|
||||
next(metadata);
|
||||
},
|
||||
onReceiveMessage: function(message, next) {
|
||||
next(message);
|
||||
},
|
||||
onReceiveHalfClose: function(next) {
|
||||
next();
|
||||
},
|
||||
onCancel: function() {
|
||||
}
|
||||
};
|
||||
next(listener);
|
||||
},
|
||||
sendMetadata: function(metadata, next) {
|
||||
next(metadata);
|
||||
},
|
||||
sendMessage: function(message, next) {
|
||||
next(message);
|
||||
},
|
||||
sendStatus: function(status, next) {
|
||||
next(status);
|
||||
}
|
||||
};
|
||||
return new ServerInterceptingCall(call, responder);
|
||||
}
|
||||
```
|
||||
|
||||
As with client interceptors, the responder intercepts outgoing operations and
|
||||
the listener intercepts incoming operations. Each intercepting method can read
|
||||
or modify the data for that operation before passing it along to the `next`
|
||||
callback.
|
||||
|
||||
The `ResponderBuilder` and `ServerListenerBuilder` utility classes provide an
|
||||
alternative way to build responder and server listener objects.
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2024 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
const grpc = require('@grpc/grpc-js');
|
||||
const protoLoader = require('@grpc/proto-loader');
|
||||
const parseArgs = require('minimist');
|
||||
|
||||
const PROTO_PATH = __dirname + '/../protos/echo.proto';
|
||||
|
||||
const packageDefinition = protoLoader.loadSync(
|
||||
PROTO_PATH,
|
||||
{keepCase: true,
|
||||
longs: String,
|
||||
enums: String,
|
||||
defaults: true,
|
||||
oneofs: true
|
||||
});
|
||||
const echoProto = grpc.loadPackageDefinition(packageDefinition).grpc.examples.echo;
|
||||
|
||||
function authInterceptor(options, nextCall) {
|
||||
const requester = (new grpc.RequesterBuilder())
|
||||
.withStart((metadata, listener, next) => {
|
||||
metadata.set('authorization', 'some-secret-token');
|
||||
next(metadata, listener);
|
||||
}).build();
|
||||
return new grpc.InterceptingCall(nextCall(options), requester);
|
||||
}
|
||||
|
||||
// logger is to mock a sophisticated logging system. To simplify the example, we just print out the content.
|
||||
function logger(format, ...args) {
|
||||
console.log(`LOG (client):\t${format}\n`, ...args);
|
||||
}
|
||||
|
||||
function loggingInterceptor(options, nextCall) {
|
||||
const listener = (new grpc.ListenerBuilder())
|
||||
.withOnReceiveMessage((message, next) => {
|
||||
logger(`Receive a message ${JSON.stringify(message)} at ${(new Date()).toISOString()}`);
|
||||
next(message);
|
||||
}).build();
|
||||
const requester = (new grpc.RequesterBuilder())
|
||||
.withSendMessage((message, next) => {
|
||||
logger(`Send a message ${JSON.stringify(message)} at ${(new Date()).toISOString()}`);
|
||||
next(message);
|
||||
}).build();
|
||||
return new grpc.InterceptingCall(nextCall(options), requester);
|
||||
}
|
||||
|
||||
function callUnaryEcho(client, message) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const deadline = new Date();
|
||||
deadline.setSeconds(deadline.getSeconds() + 10);
|
||||
client.unaryEcho({message: message}, {deadline}, (error, value) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
console.log(`UnaryEcho: ${JSON.stringify(value)}`);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function callBidiStreamingEcho(client) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const deadline = new Date();
|
||||
deadline.setSeconds(deadline.getSeconds() + 10);
|
||||
const call = client.bidirectionalStreamingEcho({deadline});
|
||||
call.on('data', value => {
|
||||
console.log(`BidiStreamingEcho: ${JSON.stringify(value)}`);
|
||||
});
|
||||
call.on('status', status => {
|
||||
if (status.code === grpc.status.OK) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(status);
|
||||
}
|
||||
});
|
||||
call.on('error', () => {
|
||||
// Ignore error event
|
||||
});
|
||||
for (let i = 0; i < 5; i++) {
|
||||
call.write({message: `Request ${i + 1}`});
|
||||
}
|
||||
call.end();
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
let argv = parseArgs(process.argv.slice(2), {
|
||||
string: 'target',
|
||||
default: {target: 'localhost:50051'}
|
||||
});
|
||||
const client = new echoProto.Echo(argv.target, grpc.credentials.createInsecure(), {interceptors: [authInterceptor, loggingInterceptor]});
|
||||
await callUnaryEcho(client, 'hello world');
|
||||
await callBidiStreamingEcho(client);
|
||||
}
|
||||
|
||||
main();
|
|
@ -0,0 +1,120 @@
|
|||
/*
|
||||
*
|
||||
* Copyright 2024 gRPC authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
*/
|
||||
|
||||
const grpc = require('@grpc/grpc-js');
|
||||
const protoLoader = require('@grpc/proto-loader');
|
||||
const parseArgs = require('minimist');
|
||||
|
||||
const PROTO_PATH = __dirname + '/../protos/echo.proto';
|
||||
|
||||
const packageDefinition = protoLoader.loadSync(
|
||||
PROTO_PATH,
|
||||
{keepCase: true,
|
||||
longs: String,
|
||||
enums: String,
|
||||
defaults: true,
|
||||
oneofs: true
|
||||
});
|
||||
const echoProto = grpc.loadPackageDefinition(packageDefinition).grpc.examples.echo;
|
||||
|
||||
function unaryEcho(call, callback) {
|
||||
console.log(`unary echoing message ${call.request.message}`);
|
||||
callback(null, call.request);
|
||||
}
|
||||
|
||||
function bidirectionalStreamingEcho(call) {
|
||||
call.on('data', request => {
|
||||
console.log(`bidi echoing message ${request.message}`);
|
||||
call.write(request);
|
||||
});
|
||||
call.on('end', () => {
|
||||
call.end();
|
||||
});
|
||||
}
|
||||
|
||||
const serviceImplementation = {
|
||||
unaryEcho,
|
||||
bidirectionalStreamingEcho
|
||||
}
|
||||
|
||||
function validateAuthorizationMetadata(metadata) {
|
||||
const authorization = metadata.get('authorization');
|
||||
if (authorization.length < 1) {
|
||||
return false;
|
||||
}
|
||||
return authorization[0] === 'some-secret-token';
|
||||
}
|
||||
|
||||
function authInterceptor(methodDescriptor, call) {
|
||||
const listener = (new grpc.ServerListenerBuilder())
|
||||
.withOnReceiveMetadata((metadata, next) => {
|
||||
if (validateAuthorizationMetadata(metadata)) {
|
||||
next(metadata);
|
||||
} else {
|
||||
call.sendStatus({
|
||||
code: grpc.status.UNAUTHENTICATED,
|
||||
details: 'Auth metadata not correct'
|
||||
});
|
||||
}
|
||||
}).build();
|
||||
const responder = (new grpc.ResponderBuilder())
|
||||
.withStart(next => {
|
||||
next(listener);
|
||||
}).build();
|
||||
return new grpc.ServerInterceptingCall(call, responder);
|
||||
}
|
||||
|
||||
// logger is to mock a sophisticated logging system. To simplify the example, we just print out the content.
|
||||
function logger(format, ...args) {
|
||||
console.log(`LOG (server):\t${format}\n`, ...args);
|
||||
}
|
||||
|
||||
function loggingInterceptor(methodDescriptor, call) {
|
||||
const listener = new grpc.ServerListenerBuilder()
|
||||
.withOnReceiveMessage((message, next) => {
|
||||
logger(`Receive a message ${JSON.stringify(message)} at ${(new Date()).toISOString()}`);
|
||||
next(message);
|
||||
}).build();
|
||||
const responder = new grpc.ResponderBuilder()
|
||||
.withStart(next => {
|
||||
next(listener);
|
||||
})
|
||||
.withSendMessage((message, next) => {
|
||||
logger(`Send a message ${JSON.stringify(message)} at ${(new Date()).toISOString()}`);
|
||||
next(message);
|
||||
}).build();
|
||||
return new grpc.ServerInterceptingCall(call, responder);
|
||||
}
|
||||
|
||||
function main() {
|
||||
const argv = parseArgs(process.argv.slice(2), {
|
||||
string: 'port',
|
||||
default: {port: '50051'}
|
||||
});
|
||||
const server = new grpc.Server({interceptors: [authInterceptor, loggingInterceptor]});
|
||||
server.addService(echoProto.Echo.service, serviceImplementation);
|
||||
server.bindAsync(`0.0.0.0:${argv.port}`, grpc.ServerCredentials.createInsecure(), (err, port) => {
|
||||
if (err != null) {
|
||||
return console.error(err);
|
||||
}
|
||||
console.log(`gRPC listening on ${port}`)
|
||||
});
|
||||
client = new echoProto.Echo(`localhost:${argv.port}`, grpc.credentials.createInsecure());
|
||||
}
|
||||
|
||||
main();
|
Loading…
Reference in New Issue