Filtering Thrift Requests¶
c.t.finatra.thrift.filters.AccessLoggingFilter →¶
provides for psuedo “Common Log Format” style logging for Thrift requests and responses.
c.t.inject.thrift.filters.DarkTrafficFilter →¶
An implementation of com.twitter.finagle.exp.AbstractDarkTrafficFilter which extends com.twitter.finagle.Filter.TypeAgnostic and thus works in a Finatra ThriftRouter Filter chain.
c.t.finatra.thrift.filters.ExceptionMappingFilter →¶
Filter for use in mapping exceptions to Thrift responses. See ExceptionMappingFilter for more information.
c.t.finatra.thrift.filters.LoggingMDCFilter →¶
properly initializes the framework’s Mapped Diagnostic Context (MDC) adapter for logging.
c.t.finatra.thrift.filters.StatsFilter →¶
Tracks per-method stats scoped under per_method_stats/<method> including success/failure (with exceptions) counters, and a latency (in millis) histogram.
c.t.finatra.thrift.filters.ThriftMDCFilter →¶
places the current Finagle MethodMetadata and ClientId locals into the Mapped Diagnostic Context (MDC).
c.t.finatra.thrift.filters.TraceIdMDCFilter →¶
places the current Finagle TraceId local into the Mapped Diagnostic Context (MDC).
Global Filters¶
The ThriftRouter allows you to build a global Filter chain which will trigger on the request path when executing an RPC call to methods implemented by the added Thrift Controller.
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 to all methods of your Thrift Controller, 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. Filters can be added to the ThriftRouter by type (as in the example above) or by instance.
For more information see the Finagle User's Guide section on Filters.
Per-method Filtering¶
TypeAgnostic Filters¶
You can filter by a TypeAgnostic Filter per-method implemented in a Controller, by calling the handle(ThriftMethod)#filtered Function e.g.:
import com.twitter.finagle.{Filter, Service, SimpleFilter}
import com.twitter.util.Future
val countEchoFilter = new Filter.TypeAgnostic {
private[this] val echos = stats.counter("echo_calls")
def toFilter[Req, Rep]: Filter[Req, Rep, Req, Rep] = new SimpleFilter[Req, Rep]{
def apply(request: Req, service: Service[Req, Rep]): Future[Rep] = {
echos.incr()
service(request)
}
}
}
...
import com.foo.bar.thriftscala.EchoService.Echo
import com.twitter.finatra.thrift.Controller
import com.twitter.util.Future
import scala.util.control.NoStackTrace
class ExampleController extends Controller {
handle(Echo).filtered(countEchoFilter) { args: Echo.Args =>
if (args.msg == "clientError") {
Future.exception(new Exception("client error") with NoStackTrace)
} else {
Future.value(args.msg)
}
}
}
Note that you can chain handle(ThriftMethod)#filtered calls arbitrarily deep.
Typed Filters¶
If you’d like to specify a typed Filter, use the handle(ThriftMethod)#withService Function and apply your typed Filter[-ReqIn, +RepOut, +ReqOut, -RepIn] to your Service[-ReqOut, +RepIn] implementation.
import com.foo.bar.thriftscala.EchoService.Echo
import com.twitter.finagle.{Filter, Service, SimpleFilter}
import com.twitter.inject.Logging
import com.twitter.util.Future
val echoLoggingFilter = new Filter[Echo.Args, String, Echo.Args, String] with Logging {
def apply(request: Echo.Args, service: Service[Echo.Args, String]): Future[String] = {
info(s"Received request message: ${request.msg}")
service(request)
}
}
...
import com.foo.bar.thriftscala.EchoService.Echo
import com.twitter.finatra.thrift.Controller
import com.twitter.util.Future
import scala.util.control.NoStackTrace
class ExampleController extends Controller {
val svc: Service[Echo.Args, String] = Service.mk { args: Echo.Args =>
if (args.msg == "clientError") {
Future.exception(new Exception("client error") with NoStackTrace)
} else {
Future.value(args.msg)
}
}
handle(Echo).withService(echoLoggingFilter.andThen(svc))
}
For more information on the handle(ThriftMethod) DSL of the Controller, see the documentation on Thrift Controllers.
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 Custom Request Scope will remain present in threads launched from a FuturePool.
Adding Classes into the Custom Request Scope¶
First add a dependency on com.twitter:inject-request-scope (finatra/inject/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 custom “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]
}
}
Important
Remember to include this Module in your server’s list of Modules.
You must then “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 a TypeAgnostic Filter executed on the request path.
For example, to define a TypeAgnostic 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()(
finagleRequestScope: FinagleRequestScope
) extends Filter.TypeAgnostic {
def toFilter[Req, Rep]: Filter[Req, Rep, Req, Rep] =
new Filter[Req, Rep, Req, Rep] {
def apply[Req, Rep](request: Req, service: Service[Req, Rep]): Future[Rep] = {
val userId = parseUserId(request) // User-defined method to parse a "user id" from the request
val user = User(userId)
finagleRequestScope.seed[User](user)
service(request)
}
}
}
Next, add the FinagleRequestScopeFilter.TypeAgnostic to your server _above_ the defined Filter which seeds the provided instance.
E.g., for the UserFilter defined above (shown with commonly recommended Filters in the recommended 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[ClientIdAcceptlistFilter]
.filter[FinagleRequestScopeFilter.TypeAgnostic]
.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.