New to Akka? Start with the Akka SDK.
Interceptors
A server interceptor runs logic around every call to a gRPC service, before the request is unmarshalled. Use it for cross-cutting concerns such as authentication, logging or metrics.
The interceptor gets the service name, the method name, the HttpRequestHttpRequest and a next function that continues with the rest of the chain and the service implementation. It can:
- Reject the call by returning a failed
FutureCompletionStage, for example with aGrpcServiceExceptionGrpcServiceException. The failure is turned into a gRPC error response for the client. - Pass information to the service implementation by adding an attribute to the request before calling
next. A Power API implementation reads it from theMetadataMetadatawithmetadata.attribute(key)metadata.getAttribute(key). - Observe the outcome by transforming the response returned by
next.
Interceptors do not see the unmarshalled request or response messages. Errors in a streamed response happen after the response headers are sent and are not visible to the interceptor.
A failure from an interceptor is mapped with the default exception mapping of GrpcExceptionHandlerGrpcExceptionHandler. A custom exception handler given to the service handler does not apply to it.
Calls with any other path of more than one segment are rejected with INVALID_ARGUMENT without reaching the handler.
The interceptor is called from the server stream and must not block. The request entity is a stream that can only be consumed once, so an interceptor that reads it must not pass the same request to next.
Defining an interceptor
- Scala
-
source
// An interceptor that rejects calls without the right token val requireToken: ServerInterceptor = (serviceName, methodName, request, next) => request.headers.find(_.name == "token") match { case Some(header) if header.value == "XYZ" => next(request) case _ => Future.failed( new GrpcServiceException( Status.UNAUTHENTICATED.withDescription(s"Missing or invalid token for $serviceName/$methodName"))) } - Java
-
source
// An interceptor that rejects calls without the right token ServerInterceptor requireToken = (serviceName, methodName, request, next) -> { boolean validToken = request.getHeader("token").map(header -> header.value().equals("XYZ")).orElse(false); if (validToken) { return next.apply(request); } else { return CompletableFuture.failedFuture(new GrpcServiceException( Status.UNAUTHENTICATED.withDescription("Missing or invalid token for " + serviceName + "/" + methodName))); } };
Applying interceptors to one service
Wrap the handler of the service with the interceptors. The wrapped handler can be combined with other handlers just like the generated handler. Here the greeter service requires a token but server reflection does not:
- Scala
-
source
// Create service handlers, the interceptor only applies to the greeter service val handler: HttpRequest => Future[HttpResponse] = ServiceHandler.concatOrNotFound( sys, ServerInterceptor.intercept(GreeterServiceHandler.partial(new GreeterServiceImpl()), requireToken), ServerReflection.partial(List(GreeterService))) // As a Route val handlerRoute: Route = handle(handler) - Java
-
source
// Create service handlers, the interceptor only applies to the greeter service GreeterService impl = new GreeterServiceImpl(mat); Function<HttpRequest, CompletionStage<HttpResponse>> handler = ServiceHandler.concatOrNotFound( sys, ServerInterceptors.intercept( GreeterService.description, GreeterServiceHandlerFactory.create(impl, sys), sys, requireToken), ServerReflection.create(Collections.singletonList(GreeterService.description), sys)); // As a Route Route handlerRoute = handle(handler);
The Java variant takes the ServiceDescription of the service so that calls to other services pass through untouched and can be handled by the other handlers in ServiceHandler.concatOrNotFound. For a service registered under a custom prefix, pass the prefix instead of the ServiceDescription.
Applying interceptors to all services
Wrap the combined handler instead. Every call reaching it passes through the interceptors, including calls to server reflection and health check services. An authentication interceptor applied this way also rejects health checks that do not carry credentials.
- Scala
-
source
val handler = ServerInterceptor.intercept( ServiceHandler.concat( GreeterServiceHandler.partial(new CountingGreeterServiceImpl), ServerReflection.partial(List(GreeterService))), requireToken) - Java
-
source
Function<HttpRequest, CompletionStage<HttpResponse>> handler = ServerInterceptors.intercept( ServiceHandler.concatOrNotFound( system, GreeterServiceHandlerFactory.create(impl, system), ServerReflection.create(Collections.singletonList(GreeterService.description), system)), system, rejectAll);
Calls to services the combined handler does not know also pass through the interceptors, before the handler answers them with 404.
Several interceptors can be given to one call. The first one is the outermost, so it sees the request first and the response last.