mirror of https://github.com/grpc/grpc-dart.git
Added interceptors. (#86)
* Add draft implementation of interceptors. * Fix review comment. * Revert example files. * Revert interop files. * Revert interop files. * Format. * Fix typos.
This commit is contained in:
parent
694332921a
commit
847a3625e1
1
AUTHORS
1
AUTHORS
|
@ -4,3 +4,4 @@
|
||||||
# Name/Organization <email address>
|
# Name/Organization <email address>
|
||||||
|
|
||||||
Google Inc.
|
Google Inc.
|
||||||
|
German Saprykin <saprykin.h@gmail.com>
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
## 0.5.0 - 2018-06-29
|
||||||
|
|
||||||
|
* Implemented interceptors.
|
||||||
|
|
||||||
## 0.4.1 - 2018-04-04
|
## 0.4.1 - 2018-04-04
|
||||||
|
|
||||||
* Fixes for supporting Dart 2.
|
* Fixes for supporting Dart 2.
|
||||||
|
|
|
@ -25,6 +25,7 @@ export 'src/client/options.dart';
|
||||||
|
|
||||||
export 'src/server/call.dart';
|
export 'src/server/call.dart';
|
||||||
export 'src/server/handler.dart';
|
export 'src/server/handler.dart';
|
||||||
|
export 'src/server/interceptor.dart';
|
||||||
export 'src/server/server.dart';
|
export 'src/server/server.dart';
|
||||||
export 'src/server/service.dart';
|
export 'src/server/service.dart';
|
||||||
|
|
||||||
|
|
|
@ -22,12 +22,14 @@ import '../shared/streams.dart';
|
||||||
import '../shared/timeout.dart';
|
import '../shared/timeout.dart';
|
||||||
|
|
||||||
import 'call.dart';
|
import 'call.dart';
|
||||||
|
import 'interceptor.dart';
|
||||||
import 'service.dart';
|
import 'service.dart';
|
||||||
|
|
||||||
/// Handles an incoming gRPC call.
|
/// Handles an incoming gRPC call.
|
||||||
class ServerHandler extends ServiceCall {
|
class ServerHandler extends ServiceCall {
|
||||||
final ServerTransportStream _stream;
|
final ServerTransportStream _stream;
|
||||||
final Service Function(String service) _serviceLookup;
|
final Service Function(String service) _serviceLookup;
|
||||||
|
final List<Interceptor> _interceptors;
|
||||||
|
|
||||||
StreamSubscription<GrpcMessage> _incomingSubscription;
|
StreamSubscription<GrpcMessage> _incomingSubscription;
|
||||||
|
|
||||||
|
@ -51,14 +53,19 @@ class ServerHandler extends ServiceCall {
|
||||||
bool _isTimedOut = false;
|
bool _isTimedOut = false;
|
||||||
Timer _timeoutTimer;
|
Timer _timeoutTimer;
|
||||||
|
|
||||||
ServerHandler(this._serviceLookup, this._stream);
|
ServerHandler(this._serviceLookup, this._stream,
|
||||||
|
[this._interceptors = const <Interceptor>[]]);
|
||||||
|
|
||||||
DateTime get deadline => _deadline;
|
DateTime get deadline => _deadline;
|
||||||
|
|
||||||
bool get isCanceled => _isCanceled;
|
bool get isCanceled => _isCanceled;
|
||||||
|
|
||||||
bool get isTimedOut => _isTimedOut;
|
bool get isTimedOut => _isTimedOut;
|
||||||
|
|
||||||
Map<String, String> get clientMetadata => _clientMetadata;
|
Map<String, String> get clientMetadata => _clientMetadata;
|
||||||
|
|
||||||
Map<String, String> get headers => _customHeaders;
|
Map<String, String> get headers => _customHeaders;
|
||||||
|
|
||||||
Map<String, String> get trailers => _customTrailers;
|
Map<String, String> get trailers => _customTrailers;
|
||||||
|
|
||||||
void handle() {
|
void handle() {
|
||||||
|
@ -103,6 +110,7 @@ class ServerHandler extends ServiceCall {
|
||||||
}
|
}
|
||||||
final serviceName = pathSegments[1];
|
final serviceName = pathSegments[1];
|
||||||
final methodName = pathSegments[2];
|
final methodName = pathSegments[2];
|
||||||
|
|
||||||
_service = _serviceLookup(serviceName);
|
_service = _serviceLookup(serviceName);
|
||||||
_descriptor = _service?.$lookupMethod(methodName);
|
_descriptor = _service?.$lookupMethod(methodName);
|
||||||
if (_descriptor == null) {
|
if (_descriptor == null) {
|
||||||
|
@ -110,9 +118,32 @@ class ServerHandler extends ServiceCall {
|
||||||
_sinkIncoming();
|
_sinkIncoming();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final error = _applyInterceptors();
|
||||||
|
if (error != null) {
|
||||||
|
_sendError(error);
|
||||||
|
_sinkIncoming();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_startStreamingRequest();
|
_startStreamingRequest();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GrpcError _applyInterceptors() {
|
||||||
|
try {
|
||||||
|
for (final interceptor in _interceptors) {
|
||||||
|
final error = interceptor(this, this._descriptor);
|
||||||
|
if (error != null) {
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
final grpcError = new GrpcError.internal(error.toString());
|
||||||
|
return grpcError;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
void _startStreamingRequest() {
|
void _startStreamingRequest() {
|
||||||
_incomingSubscription.pause();
|
_incomingSubscription.pause();
|
||||||
_requests = _descriptor.createRequestStream(_incomingSubscription);
|
_requests = _descriptor.createRequestStream(_incomingSubscription);
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import '../shared/status.dart';
|
||||||
|
|
||||||
|
import 'call.dart';
|
||||||
|
import 'service.dart';
|
||||||
|
|
||||||
|
/// A gRPC Interceptor.
|
||||||
|
///
|
||||||
|
/// An interceptor is called before the corresponding [ServiceMethod] invocation.
|
||||||
|
/// If the interceptor returns a [GrpcError], the error will be returned as a response and [ServiceMethod] wouldn't be called.
|
||||||
|
/// If the interceptor throws [Exception], [GrpcError.internal] with exception.toString() will be returned.
|
||||||
|
/// If the interceptor returns null, the corresponding [ServiceMethod] of [Service] will be called.
|
||||||
|
typedef Interceptor = GrpcError Function(
|
||||||
|
ServiceCall call, ServiceMethod method);
|
|
@ -21,6 +21,7 @@ import 'package:http2/transport.dart';
|
||||||
import '../shared/security.dart';
|
import '../shared/security.dart';
|
||||||
|
|
||||||
import 'handler.dart';
|
import 'handler.dart';
|
||||||
|
import 'interceptor.dart';
|
||||||
import 'service.dart';
|
import 'service.dart';
|
||||||
|
|
||||||
class ServerTlsCredentials {
|
class ServerTlsCredentials {
|
||||||
|
@ -57,13 +58,16 @@ class ServerTlsCredentials {
|
||||||
/// Listens for incoming RPCs, dispatching them to the right [Service] handler.
|
/// Listens for incoming RPCs, dispatching them to the right [Service] handler.
|
||||||
class Server {
|
class Server {
|
||||||
final Map<String, Service> _services = {};
|
final Map<String, Service> _services = {};
|
||||||
|
final List<Interceptor> _interceptors;
|
||||||
|
|
||||||
ServerSocket _insecureServer;
|
ServerSocket _insecureServer;
|
||||||
SecureServerSocket _secureServer;
|
SecureServerSocket _secureServer;
|
||||||
final _connections = <ServerTransportConnection>[];
|
final _connections = <ServerTransportConnection>[];
|
||||||
|
|
||||||
/// Create a server for the given [services].
|
/// Create a server for the given [services].
|
||||||
Server(List<Service> services) {
|
Server(List<Service> services,
|
||||||
|
[List<Interceptor> interceptors = const <Interceptor>[]])
|
||||||
|
: _interceptors = interceptors {
|
||||||
for (final service in services) {
|
for (final service in services) {
|
||||||
_services[service.$name] = service;
|
_services[service.$name] = service;
|
||||||
}
|
}
|
||||||
|
@ -110,7 +114,7 @@ class Server {
|
||||||
}
|
}
|
||||||
|
|
||||||
void serveStream(ServerTransportStream stream) {
|
void serveStream(ServerTransportStream stream) {
|
||||||
new ServerHandler(lookupService, stream).handle();
|
new ServerHandler(lookupService, stream, _interceptors).handle();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Null> shutdown() async {
|
Future<Null> shutdown() async {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
name: grpc
|
name: grpc
|
||||||
description: Dart implementation of gRPC.
|
description: Dart implementation of gRPC.
|
||||||
version: 0.4.1
|
version: 0.5.0
|
||||||
author: Dart Team <misc@dartlang.org>
|
author: Dart Team <misc@dartlang.org>
|
||||||
homepage: https://github.com/dart-lang/grpc-dart
|
homepage: https://github.com/dart-lang/grpc-dart
|
||||||
|
|
||||||
|
|
|
@ -277,4 +277,60 @@ void main() {
|
||||||
..toServer.close();
|
..toServer.close();
|
||||||
await harness.fromServer.done;
|
await harness.fromServer.done;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
group('Server with interceptor', () {
|
||||||
|
test('processes calls if interceptor allows request', () async {
|
||||||
|
const expectedRequest = 5;
|
||||||
|
const expectedResponse = 7;
|
||||||
|
Future<int> methodHandler(ServiceCall call, Future<int> request) async {
|
||||||
|
expect(await request, expectedRequest);
|
||||||
|
return expectedResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
GrpcError interceptorHandler(ServiceCall call, ServiceMethod method) {
|
||||||
|
if (method.name == "Unary") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new GrpcError.unauthenticated('Request is unauthenticated');
|
||||||
|
}
|
||||||
|
|
||||||
|
harness
|
||||||
|
..interceptor.handler = interceptorHandler
|
||||||
|
..service.unaryHandler = methodHandler
|
||||||
|
..runTest('/Test/Unary', [expectedRequest], [expectedResponse]);
|
||||||
|
|
||||||
|
await harness.fromServer.done;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns error if interceptor blocks request', () async {
|
||||||
|
GrpcError interceptorHandler(ServiceCall call, ServiceMethod method) {
|
||||||
|
if (method.name == "Unary") {
|
||||||
|
return new GrpcError.unauthenticated('Request is unauthenticated');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
harness
|
||||||
|
..interceptor.handler = interceptorHandler
|
||||||
|
..expectErrorResponse(
|
||||||
|
StatusCode.unauthenticated, 'Request is unauthenticated')
|
||||||
|
..sendRequestHeader('/Test/Unary');
|
||||||
|
|
||||||
|
await harness.fromServer.done;
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns internal error if interceptor throws exception', () async {
|
||||||
|
GrpcError interceptorHandler(ServiceCall call, ServiceMethod method) {
|
||||||
|
throw new Exception('Reason is unknown');
|
||||||
|
}
|
||||||
|
|
||||||
|
harness
|
||||||
|
..interceptor.handler = interceptorHandler
|
||||||
|
..expectErrorResponse(
|
||||||
|
StatusCode.internal, 'Exception: Reason is unknown')
|
||||||
|
..sendRequestHeader('/Test/Unary');
|
||||||
|
|
||||||
|
await harness.fromServer.done;
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,18 @@ class TestService extends Service {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TestInterceptor {
|
||||||
|
Interceptor handler;
|
||||||
|
|
||||||
|
GrpcError call(ServiceCall call, ServiceMethod method) {
|
||||||
|
if (handler == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler(call, method);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class TestServerStream extends ServerTransportStream {
|
class TestServerStream extends ServerTransportStream {
|
||||||
final Stream<StreamMessage> incomingMessages;
|
final Stream<StreamMessage> incomingMessages;
|
||||||
final StreamSink<StreamMessage> outgoingMessages;
|
final StreamSink<StreamMessage> outgoingMessages;
|
||||||
|
@ -107,10 +119,12 @@ class ServerHarness {
|
||||||
final toServer = new StreamController<StreamMessage>();
|
final toServer = new StreamController<StreamMessage>();
|
||||||
final fromServer = new StreamController<StreamMessage>();
|
final fromServer = new StreamController<StreamMessage>();
|
||||||
final service = new TestService();
|
final service = new TestService();
|
||||||
|
final interceptor = new TestInterceptor();
|
||||||
|
|
||||||
Server server;
|
Server server;
|
||||||
|
|
||||||
ServerHarness() {
|
ServerHarness() {
|
||||||
server = new Server([service]);
|
server = new Server(<Service>[service], <Interceptor>[interceptor]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static ServiceMethod<int, int> createMethod(String name,
|
static ServiceMethod<int, int> createMethod(String name,
|
||||||
|
|
Loading…
Reference in New Issue