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 a GrpcServiceExceptionGrpcServiceException. 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 the MetadataMetadata with metadata.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
sourceval handler = ServerInterceptor.intercept(
  ServiceHandler.concat(
    GreeterServiceHandler.partial(new CountingGreeterServiceImpl),
    ServerReflection.partial(List(GreeterService))),
  requireToken)
Java
sourceFunction<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.

Found an error in this documentation? The source code for this page can be found here. Please feel free to edit and contribute a pull request.