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" % "24.2.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) {
...
}
}
Tip
Take a look at the server naming conventions which is especially important for Java users.
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.
Also, see the server naming conventions which is especially important for Java users.
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 for defining a server 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¶
Similarly, in Java the best practice is to create a separate “main” class which defines a Java main() method that accepts the Java command-line arguments for flag parsing:
public final class ExampleServerMain {
private ExampleServerMain() {
}
public static void main(String[] args) {
new ExampleServer().main(args);
}
}
The ExampleServerMain 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:
the ExceptionManagerModule (see: Thrift Exception Mapping)
an overridable default ThriftResponseClassifierModule (see: Server-side Response Classification)
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.