-
Notifications
You must be signed in to change notification settings - Fork 281
Added interceptors. #86
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi German Saprykin
Thanks for the contribution!
I have a bunch of comments and questions but the basic approach is good I think.
test/server_test.dart
Outdated
await harness.fromServer.done; | ||
}); | ||
|
||
test('Server returns error if interceptor blocks request', () async { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it ends up being implicit from the other tests, but perhaps add an explicit test of the interceptor not blocking the request?
lib/src/server/handler.dart
Outdated
|
||
GrpcError _applyInterceptors() { | ||
for (final interceptor in _interceptors) { | ||
final error = interceptor.handle(this); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want to take into account if the interceptor throws and handle that gracefully somehow?
lib/src/server/handler.dart
Outdated
class ServerHandler extends ServiceCall { | ||
final ServerTransportStream _stream; | ||
final Service Function(String service) _serviceLookup; | ||
final Iterable<Interceptor> _interceptors; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps store this as a List.
If you end up storing a custom iterator, it might have confusing behaviour for repeated iterations.
lib/src/server/handler.dart
Outdated
Timer _timeoutTimer; | ||
|
||
ServerHandler(this._serviceLookup, this._stream); | ||
ServerHandler(this._serviceLookup, this._stream, this._interceptors); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would break the public api. Not neccesarily a bad thing as we are pre 1.0.
Perhaps turn it into a named argument.
Anyway we need an update to the pubspec.yaml
version string.
lib/src/server/interceptor.dart
Outdated
import 'call.dart'; | ||
|
||
abstract class Interceptor { | ||
GrpcError handle(ServiceCall call); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should there be a way to silently intercept without returning an error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is use case?
What's better way to implement? It could be
GrpcError.custom
, check error code and sink stream without sending the error.- Exception, on any exception silently sink stream
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about the use-case - it just seemed more general.
I have not used the interceptors for other GRpc implementations so I'm a bit unsure how they are used...
One way would be to make a response class with an enum. Something like:
enum InterceptorResponseType { error, ok, block}
class InterceptorResponse {
final InterceptorResponseType type;
final GrpcError error;
InterceptorResponse.ok() : type = InterceptorResponseType.ok;
InterceptorResponse.error(this.error) : type = InterceptorResponseType.error;
InterceptorResponce.block() : type = InterceptorResponseType.block;
}
lib/src/server/interceptor.dart
Outdated
|
||
import 'call.dart'; | ||
|
||
abstract class Interceptor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lib/src/server/server.dart
Outdated
/// Create a server for the given [services]. | ||
Server(List<Service> services) { | ||
Server(List<Service> services, | ||
{Iterable<Interceptor> interceptors = const []}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add a type annotation to the list literal:
const <Interceptor>[]
lib/src/server/server.dart
Outdated
/// Listens for incoming RPCs, dispatching them to the right [Service] handler. | ||
class Server { | ||
final Map<String, Service> _services = {}; | ||
final Iterable<Interceptor> _interceptors; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also here I think a List is preferable.
test/server_test.dart
Outdated
|
||
test('Server returns error if interceptor blocks request', () async { | ||
GrpcError interceptorHandler(ServiceCall call) { | ||
return new GrpcError.unauthenticated('Request is unauthenticated'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make it more realistic: branch on the method name here.
lib/src/server/interceptor.dart
Outdated
import 'call.dart'; | ||
|
||
abstract class Interceptor { | ||
GrpcError handle(ServiceCall call); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might want to make this async, such that we can do general async handling in the interceptor
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds interesting, looks like it will require a refactoring, old code is fully synchronous.
I tried to return Future and mark functions async. UT started to fail.
@sigurdm Don't you mind to do it in separate PR?
Should Interceptor return always Future<GrpcError>
in this case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point that the current handling is synchronous. Yes - that should be pushed to another PR.
Yes in that case it would return Future
Thank you for your pull request. Before we can look at your contribution, we need to ensure all contributors are covered by a Contributor License Agreement. After the following items are addressed, please respond with a new comment here, and the automated system will re-verify.
Regards, |
Test failures are on |
Yes, looks like the test failures are also happening in master. Filed #91 to track. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changes looks good!
Just a few typos
lib/src/server/interceptor.dart
Outdated
|
||
/// A gRPC Interceptor. | ||
/// | ||
/// Interceptor is called before correspoding [ServiceMethod] invocation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typos:
Interceptor is called before its => An interceptor is called before the corresponding
correspoding => corresponding
If interceptor returns [GrpcError] => If the interceptor returns a [GrpcError]
If interceptor returns => If the interceptor returns
correspoding => the corresponding
lib/src/server/interceptor.dart
Outdated
import 'call.dart'; | ||
|
||
abstract class Interceptor { | ||
GrpcError handle(ServiceCall call); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point that the current handling is synchronous. Yes - that should be pushed to another PR.
Yes in that case it would return Future
lib/src/server/interceptor.dart
Outdated
import 'call.dart'; | ||
|
||
abstract class Interceptor { | ||
GrpcError handle(ServiceCall call); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure about the use-case - it just seemed more general.
I have not used the interceptors for other GRpc implementations so I'm a bit unsure how they are used...
One way would be to make a response class with an enum. Something like:
enum InterceptorResponseType { error, ok, block}
class InterceptorResponse {
final InterceptorResponseType type;
final GrpcError error;
InterceptorResponse.ok() : type = InterceptorResponseType.ok;
InterceptorResponse.error(this.error) : type = InterceptorResponseType.error;
InterceptorResponce.block() : type = InterceptorResponseType.block;
}
/// 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( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@sigurdm I have checked go implementation for interceptors
https://github.com/grpc/grpc-go/blob/a3e99ebee0a498c3093e9741d09c579f0fc34a7f/server.go#L1020
From my understanding it has only two cases - no-error
and error
,
if there is an error, write a status and stop processing a request.
If there is no error, continue processing.
Imo it doesn't make sense to add this feature (I mean silent blocking) now.
Also I am not sure it conforms to grpc standard or not.
What do you think? Is it required?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No let's stick with error/no-error!
Is anything else required to be done in this PR? |
No I don't think so. Thanks! |
Merging now that the build is passing. Thanks @mogol for the contribution! |
Fixes #79
Draft implementation for interceptors #79 to support analysis of metadata (e.g. authorization)
Any feedback is appreciated.