Note: the most up-to-date examples are located in the finatra/examples project.

See examples/thrift-server for an example ThriftServer.

Thrift Server Definition

To start, add a dependency on the finatra-thrift library. We also recommend using Logback as your SLF4J implementation. E.g., with sbt:

"com.twitter" %% "finatra-thrift" % "22.4.0"
"ch.qos.logback" % "logback-classic" % versions.logback,

For more information on logging with Finatra see: Introduction to Logging With Finatra.

Create a new class that extends c.t.finatra.thrift.ThriftServer:

import com.twitter.finatra.thrift.ThriftServer
import com.twitter.finatra.thrift.routing.ThriftRouter

object ExampleServerMain extends ExampleServer

class ExampleServer extends ThriftServer {

  override def configureThrift(router: ThriftRouter): Unit = {
    ???
  }
}

Or in Java, extend the c.t.finatra.thrift.AbstractThriftServer:

import com.twitter.finatra.thrift.AbstractThriftServer;
import com.twitter.finatra.thrift.routing.JavaThriftRouter;

public class ExampleServer extends AbstractThriftServer {

  @Override
  public void configureThrift(JavaThriftRouter router) {
    ...
  }
}

A more complete example includes adding Modules, a Controller, and Filters.

import com.google.inject.Module
import com.twitter.finatra.thrift.ThriftServer
import com.twitter.finatra.thrift.filters.{LoggingMDCFilter, TraceIdMDCFilter}
import com.twitter.finatra.thrift.routing.ThriftRouter

object ExampleServerMain extends ExampleServer

class ExampleServer extends ThriftServer {

  override val modules: Seq[Module] = Seq(
    ExampleModule)

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

in Java:

import com.google.inject.Module;
import com.twitter.finatra.thrift.AbstractThriftServer;
import com.twitter.finatra.thrift.filters.LoggingMDCFilter;
import com.twitter.finatra.thrift.filters.TraceIdMDCFilter;
import com.twitter.finatra.thrift.routing.JavaThriftRouter;
import java.util.Collection;
import java.util.Collections;

public class ExampleServer extends AbstractThriftServer {

  @Override
  public Collection<Module> javaModules() {
    return Collections.singletonList(ExampleModule$.MODULE$);
  }

  // Note: this version uses the `JavaThriftRouter`
  @Override
  public void configureThrift(JavaThriftRouter router) {
    router
      .filter(LoggingMDCFilter.class)
      .filter(TraceIdMDCFilter.class)
      .add(ExampleThriftController.class);
  }
}

Tip

Note: to add Modules to your Java server override the javaModules() method.

This should look familiar as the structure is similar to creating an HttpServer. The server can be thought of as a collection of controllers composed with filters. Additionally, a server can define modules for providing instances to the object graph.

Naming Convention

The Finatra convention is to create a Scala object with a name ending in “Main” that extends your server class. The server class can be used in testing as this allows your server to be instantiated multiple times in tests without worrying about static state persisting across test runs in the same JVM.

object ExampleServerMain extends ExampleServer

The static object, e.g., ExampleServerMain, would then contain a static main() method for the server to serve as the application entry point for running the server in all other cases.

Java Naming Convention

In Java you would create a separate “main” class which defines a static main() method and accepts args for flag parsing:

public final class ExampleServerMain {
    private ExampleServerMain() {
    }

    public static void main(String[] args) {
        new ExampleServer().main(args);
    }
}

This would be the class used as the application entry point for running the server.

Using Generated Java Code

Finatra assumes that users are using the Scrooge Thrift code generator for generating code from a Thrift IDL. Scrooge allows for code generation in multiple languages but Finatra supports either generated Java or generated Scala code only.

If you choose to use generated Java code for your service (instead of generated Scala), you MUST extend the AbstractThriftServer and configure the JavaThriftRouter.

Most typically this is done when the server is being written in Java but is not exclusively so. This means:

  • if you write your server in Scala you can use either generated Java or generated Scala code.
  • if you write your server in Java you MUST use generated Java code.

Serving a Finagle Service[Array[Byte], Array[Byte]]

You have the option to serve a manually constructed Finagle Service[Array[Byte], Array[Byte]] instead of creating a Thrift Controller and having the framework construct a Service[Array[Byte], Array[Byte]] via the ThriftRouter.

To do so, extend the ThriftServerTrait (or AbstractThriftServerTrait in Java) and implement the thriftService: Service[Array[Byte], Array[Byte]] method:

import com.google.inject.Module
import com.twitter.finagle.Service
import com.twitter.finatra.thrift.ThriftServerTrait
import com.twitter.finatra.thrift.filters.{LoggingMDCFilter, TraceIdMDCFilter}

object ExampleServerMain extends ExampleServer

class ExampleServer extends ThriftServerTrait {

  override val modules: Seq[Module] = Seq(
    ExampleModule)

  // the `Service[Array[Byte], Array[Byte]]` to serve
  override def thriftService: Service[Array[Byte], Array[Byte]] = ???
}

in Java:

import com.google.inject.Module;
import com.twitter.finagle.Service;
import com.twitter.finatra.thrift.AbstractThriftServerTrait;
import java.util.Collection;
import java.util.Collections;

public class ExampleServer extends AbstractThriftServerTrait {

  @Override
  public Collection<Module> javaModules() {
    return Collections.singletonList(ExampleModule$.MODULE$);
  }

  @Override
  public Service<byte[], byte[]> thriftService() {
    // the `Service<byte[], byte[]>` to serve
    return null;
  }
}

Important

The ThriftRouter exposes a DSL for users which the framework uses to construct a filtered Service[Array[Byte], Array[Byte]].

Note, you must choose one or the other: either implement your service with a Controller added via the ThriftRouter or serve a manually constructed Finagle Service[Array[Byte], Array[Byte]].

Override Default Behavior

Flags

Some deployment environments may make it difficult to set Flag values with command line arguments. If this is the case, Finatra’s ThriftServer’s core flags can be set from code.

For example, instead of setting the -thrift.port flag, you can override the following method in your server.

import com.twitter.finatra.thrift.ThriftServer
import com.twitter.finatra.thrift.routing.ThriftRouter

class ExampleServer extends ThriftServer {

  override val defaultThriftPort: String = ":9090"

  override def configureThrift(router: ThriftRouter): Unit = {
    ???
  }
}

For a list of what flags can be set programmatically, please see the ThriftServerTrait class.

For more information on using and setting command-line flags see Flags.

Framework Modules

The ThriftServer provides some base configurations in the form of modules added by default to a server’s object graph. This includes:

Caution

Modules are de-duplicated before being installed to create the Injector. If a Framework Module is also configured in the server’s list of Modules, the Framework Module will be replaced.

Finagle Server Configuration

If you want to further configure the underlying Finagle server you can override configureThriftServer in your server to specify additional configuration on (or override the default configuration of) the underlying Finagle server.

For example:

import com.twitter.finagle.ThriftMux
import com.twitter.finatra.thrift.ThriftServer
import com.twitter.finatra.thrift.routing.ThriftRouter

class ExampleServer extends ThriftServer {

  override def configureThrift(router: ThriftRouter): Unit = {
    ...
  }

  override def configureThriftServer(server: ThriftMux.Server): ThriftMux.Server = {
    server
      .withMaxRequestSize(???)
      .withAdmissionControl.concurrencyLimit(
        maxConcurrentRequests = ???,
        maxWaiters = ???)
  }
}

For more information on Finagle server configuration see the documentation here; specifically the server documentation here.

Server-side Response Classification

The default Response Classifier for Thrift servers is ThriftResponseClassifier.ThriftExceptionsAsFailures, which classifies any deserialized Thrift Exception as a failure. To configure server-side Response Classification you could choose to set the classifier directly on the underlying Finagle server by overriding the configureThriftServer in your server, e.g.,

override def configureThriftServer(server: ThriftMux.Server): ThriftMux.Server = {
    server.withResponseClassifier(???)
}

However, since the server-side ResponseClassifier could affect code not just at the Finagle level, we actually recommend overriding the specific framework module, ThriftResponseClassifierModule instead. This binds an instance of an ThriftResponseClassifier to the object graph that is then available to be injected into things like the Thrift StatsFilter for a more accurate reporting of metrics that takes into account server-side response classification.

For example, in your ThriftServer you would do:

import com.google.inject.Module
import com.twitter.finatra.http.HttpServer
import com.twitter.finatra.http.routing.HttpRouter

class ExampleServer extends ThriftServer {

  override thriftResponseClassifierModule: Module = ???
}

The bound value is also then set on the underlying Finagle server before serving.

Testing

For information on testing a Thrift server see the Thrift Server Feature Tests section.