Filtering Thrift Requests

The ThriftRouter allows you to build a global filter chain which will trigger on the request path when executing an RPC call to the ThriftController.

Filters must be a subclass of the c.t.finagle.Filter.TypeAgnostic which is a

c.t.finagle.Filter[T, Rep, T, Rep]

that is polymorphic in T.

If you want to apply a filter or filters in your Thrift server call the ThriftRouter#filter method, to register a Filter.TypeAgnostic.

import DoEverythingModule
import com.twitter.finatra.thrift.ThriftServer
import com.twitter.finatra.thrift.routing.ThriftRouter
import com.twitter.finatra.thrift.filters._

object ExampleServerMain extends ExampleServer

class ExampleServer extends ThriftServer {

  override val modules = Seq(
    DoEverythingModule)

  override def configureThrift(router: ThriftRouter): Unit = {
    router
      .filter[LoggingMDCFilter]
      .filter[TraceIdMDCFilter]
      .filter[ThriftMDCFilter]
      .filter[AccessLoggingFilter]
      .filter[StatsFilter]
      .add[ExampleThriftController]
  }
}

Note, like HTTP, filters are applied in the order they are defined on all methods.

For more information see the Finagle User’s Guide section on Filters.

Request Scope

Guice supports custom scopes in addition to the defined @Singleton, @SessionScoped, and @RequestScoped scopes.

@RequestScoped is often used to allow injection of instances which can change depending on the incoming request (e.g. the currently authenticated User).

Finatra provides a custom implementation of the default Guice @RequestScoped functionality which works across Finagle non-blocking threads. The default Guice @RequestScoped implementation uses ThreadLocals which will not work within the context of a Twitter c.t.util.Future.

Note

Fields added to the Finatra Request Scope will remain present in threads launched from a Finagle FuturePool.

Adding Classes into the Finatra Request Scope

First add a dependency on com.twitter:inject-request-scope

Then define a module which mixes in the c.t.inject.requestscope.RequestScopeBinding trait. This trait defines #bindRequestScope[T] which will bind the given type to an “unseeded” Provider[T] of the type in the “FinagleRequestScope”. E.g.,

import com.twitter.inject.TwitterModule
import com.twitter.inject.requestscope.RequestScopeBinding

object UserModule
  extends TwitterModule
  with RequestScopeBinding {

  override def configure(): Unit = {
    bindRequestScope[User]
  }
}

You must remember to “seed” this Provider[T] by obtaining an instance of the FinagleRequestScope and calling #seed[T](instance). For request scoping, you would generally do this in another Filter executed on the request path.

For example, to define a Filter which seeds a User into the “FinagleRequestScope”:

import com.twitter.finagle.{Filter, Service}
import com.twitter.inject.requestscope.FinagleRequestScope
import com.twitter.util.Future
import javax.inject.{Inject, Singleton}

@Singleton
class UserFilter @Inject()(
    requestScope: FinagleRequestScope)
  extends Filter.TypeAgnostic {

  override def toFilter[T, U]: Filter[T, U, T, U] = new Filter[T, U, T, U] {
    override def apply[T, U](request: T, service: Service[T, U]): Future[U] = {
      val userId = parseUserId(request) // User-defined method to parse a "user id" from the request
      val user = User(userId)
      requestScope.seed[User](user)
      service(request)
    }
  }
}

Next, you must ensure to add the FinagleRequestScopeFilter to your server before the defined Filter which seeds the provided instance.

E.g., for the UserFilter defined above (shown with common filters in a recommended filter order):

import com.google.inject.Module
import com.twitter.finatra.thrift.exceptions.FinatraThriftExceptionMapper
import com.twitter.finatra.thrift.ThriftServer
import com.twitter.finatra.thrift.routing.ThriftRouter
import com.twitter.finatra.thrift.filters._
import com.twitter.finatra.thrift.modules.ClientIdAcceptlistModule

class Server extends ThriftServer {
  override def modules: Seq[Module] = Seq(ClientIdAcceptlistModule)

  override def configureThrift(router: ThriftRouter): Unit = {
    router
      .filter[LoggingMDCFilter]
      .filter[TraceIdMDCFilter]
      .filter[ThriftMDCFilter]
      .filter[AccessLoggingFilter]
      .filter[StatsFilter]
      .filter[ExceptionMappingFilter]
      .filter[ClientIdWhitelistFilter]
      .filter[FinagleRequestScopeFilter]
      .filter[UserFilter]
      .exceptionMapper[FinatraThriftExceptionMapper]
      .add[MyController]
    }
}

Lastly, wherever you need to access the Request scoped User inject a User or a Provider[User] type.

import com.twitter.finagle.Service
import com.twitter.finatra.thrift.Controller
import javax.inject.{Inject, Provider, Singleton}

@Singleton
class MyController @Inject()(
  dao: GroupsDAO,
  user: Provider[User])
  extends Controller with MyService.BaseServiceIface {

  val getUser: Service[GetUser.Args, GetUser.SuccessType] = handle(GetUser) { args: GetUser.Args =>
    "The incoming user has id " + user.get.id
  }
}

Note

The Provider[User] type must be used when injecting into a Singleton class.