Message Body Components¶
Message body components are conceptually similar to JAX-RS Entity Providers and specify how to parse an incoming c.t.finagle.http.Request into a model or domain object (“message body reader”) or how to transform a given type T into a c.t.finagle.http.Response (“message body writer”).
MessageBodyComponent¶
A c.t.finatra.http.marshalling.MessageBodyComponent is a marker trait superclass of both MessageBodyReader and MessageBodyWriter.
MessageBodyReader[T]¶
A MessageBodyReader can be registered through the c.t.finatra.http.routing.HttpRouter much like adding a Controller or a Filter. It is conceptually similar to the JAX-RS javax.ws.rs.ext.MessageBodyReader interface.
Readers are registered on the HttpRouter within your server definition:
import DoEverythingModule
import ExampleController
import com.twitter.finatra.http.routing.HttpRouter
import com.twitter.finatra.http.{Controller, HttpServer}
object ExampleServerMain extends ExampleServer
class ExampleServer extends HttpServer {
override val modules = Seq(
DoEverythingModule)
override def configureHttp(router: HttpRouter): Unit = {
router
.add[ExampleController]
.register[MyModelObjectMessageBodyReader]
}
}
A MessageBodyReader is invoked by the framework to convert an incoming c.t.finagle.http.Request into a type T when T is specified as the input type for a Controller route callback or when T is passed as a body to a function in the c.t.finatra.http.response.ResponseBuilder.
E.g., a Controller with a route specified:
import com.twitter.finatra.http.Controller
class ExampleController extends Controller {
get("/") { model: MyModelObject =>
...
}
will trigger the framework to search for a registered MessageBodyReader[MyModelObject] that can convert the incoming c.t.finagle.http.Request body contents into MyModelObject.
If a MessageBodyReader for the MyModelObject type cannot be found the DefaultMessageBodyReader implementation configured in the c.t.finatra.http.marshalling.MessageBodyManager will be used.
DefaultMessageBodyReader¶
The framework provides a default MessageBodyReader: c.t.finatra.http.marshalling.DefaultMessageBodyReader which is invoked when a more specific MessageBodyReader cannot be found to convert an incoming c.t.finagle.http.Request body.
The default parses a message body as JSON¶
The c.t.finatra.http.marshalling.DefaultMessageBodyReader parses an incoming c.t.finagle.http.Request body as JSON, marshalling it into the given callback input type using the HttpServer configured ScalaObjectMapper and is the basis of the JSON integration with routing.
This default behavior is overridable. See the MessageBodyModule section for more information on how to provide a different DefaultMessageBodyReader implementation.
MessageBodyWriter[T]¶
A MessageBodyWriter is likewise conceptually similar to the JAX-RS javax.ws.rs.ext.MessageBodyWriter interface and can be registered through the c.t.finatra.http.routing.HttpRouter like a MessageBodyReader, akin to a Controller or a Filter:
import DoEverythingModule
import ExampleController
import com.twitter.finatra.http.routing.HttpRouter
import com.twitter.finatra.http.{Controller, HttpServer}
object ExampleServerMain extends ExampleServer
class ExampleServer extends HttpServer {
override val modules = Seq(
DoEverythingModule)
override def configureHttp(router: HttpRouter): Unit = {
router
.add[ExampleController]
.register[MyModelObjectMessageBodyReader]
.register[MyModelObjectMessageBodyWriter]
}
}
A MessageBodyWriter is used to specify conversion from a type T to a c.t.finagle.http.Response. This can be for the purpose of informing the framework how to render the return type T of a route callback or how to render the type T when passed as a body to a function in the c.t.finatra.http.response.ResponseBuilder.
E.g., a Controller with a route specified:
import com.twitter.finagle.http.Request
import com.twitter.finatra.http.Controller
class ExampleController extends Controller {
get("/") { request: Request =>
...
MyRenderableObjectType(
id = "1",
name = "John Doe",
description = "A renderable return")
}
will trigger the framework to search for a registered MessageBodyWriter[MyRenderableObjectType] that can convert the MyRenderableObjectType type into a c.t.finagle.http.Response.
If a MessageBodyWriter for the MyRenderableObjectType type cannot be found the DefaultMessageBodyWriter implementation configured in the c.t.finatra.http.marshalling.MessageBodyManager will be used.
DefaultMessageBodyWriter¶
The framework provides a default MessageBodyWriter: c.t.finatra.http.marshalling.DefaultMessageBodyWriter which is invoked when a more specific MessageBodyWriter cannot be found to convert given type T into a c.t.finagle.http.Response.
The default serializes an object as JSON¶
The DefaultMessageBodyWriter converts any non-primitive type to an application/json content type response and a JSON representation of the type using the HttpServer configured ScalaObjectMapper to convert the type to JSON.
Note
For primitive and boxed types, the default writer implementation will render a plain/text content type response using the type’s toString value.
Again, the default behavior is overridable. See the c.t.finatra.http.modules.MessageBodyModule section for more information on how to provide a different DefaultMessageBodyWriter implementation.
@MessageBodyWriter Annotation¶
A MessageBodyWriter can be invoked on a class that is annotated with a MessageBodyWriter annotation. That is, a class which is annotated with an annotation that is itself annotated with @MessageBodyWriter.
For example. If you have MyRenderableObjectMessageBodyWriter and you want to signal to the framework to invoke this MessageBodyWriter when trying to convert a given class to a c.t.finagle.http.Response, you can create a custom annotation and annotate the class like so:
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import com.twitter.finatra.http.annotations.MessageBodyWriter;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Target(PARAMETER)
@Retention(RUNTIME)
@MessageBodyWriter
public @interface MyRenderable {}
import MyRenderable
@MyRenderable
case class SomeValues(name: String, age: Int, address: String)
You would then create a custom Module to register the annotation to your MyRenderableObjectMessageBodyWriter. You will need to do this registration in the TwitterModule#singletonStartup lifecycle method which ensures that registration will happen after the object graph has been created but before the server has started.
import MyRenderable
import MyRenderableObjectMessageBodyWriter
import MyRenderableObjectType
import com.twitter.finatra.http.marshalling.MessageBodyManager
import com.twitter.inject.{Injector, TwitterModule}
object MyRenderableObjectMessageBodyModule extends TwitterModule {
override def singletonStartup(injector: Injector): Unit = {
val manager = injector.instance[MessageBodyManager]
manager.addWriterByAnnotation[MyRenderable, MyRenderableObjectMessageBodyWriter]()
manager.addWriterByComponent[MyRenderableObjectType, MyRenderableObjectMessageBodyWriter]()
}
}
In this way, whenever an instance of SomeValues (MessageBodyManager#addByAnnotation) or MyRenderableObjectType (MessageBodyManager#addByComponentType) is passed to the framework to render as a c.t.finagle.http.Response the MyRenderableObjectMessageBodyWriter will be invoked.
Again, this happens when these types are returned from a route callback or when passed as a body to a function in the c.t.finatra.http.response.ResponseBuilder.
MessageBodyManager¶
The c.t.finatra.http.marshalling.MessageBodyManager registers message body components.
Generally, you will not need to interact directly with the manager because the c.t.finatra.http.routing.HttpRouter provides a DSL for registration of components to the bound c.t.finatra.http.marshalling.MessageBodyManager.
MessageBodyModule¶
The c.t.finatra.http.marshalling.DefaultMessageBodyReader, and the c.t.finatra.http.marshalling.DefaultMessageBodyWriter are provided by the framework via configuration in the c.t.finatra.http.modules.MessageBodyModule.
To override the framework defaults, create an instance of a TwitterModule which provides customized implementations for the default reader and writer. Set this Module as a Framework Module by overriding the protected def messageBodyModule in your server.
class ExampleServer extends HttpServer {
override def messageBodyModule = MyCustomMessageBodyModule
override def configureHttp(router: HttpRouter): Unit = {
...
}
}
If your module is defined as a class, you would pass an instance of the class, e.g.,
override val messageBodyModule = new MyCustomMessageBodyModule
See Framework Modules for more information.
Caution
Care should be taken when replacing the framework default c.t.finatra.http.modules.MessageBodyModule. This module binds the framework DefaultMessageBodyReader implementation which is what provides the logic for marshalling HTTP request bodies as JSON into case classes automatically.
If you replace the MessageBodyModule completely and do not retain the binding of the framework DefaultMessageBodyReader implementation, you will lose this functionality.
Thus it is recommended that you choose to extend the c.t.finatra.http.modules.MessageBodyModule in order to customize your logic and remember to invoke super for overridden methods to ensure default behavior is retained if so desired. E.g.,
import com.twitter.finatra.http.modules.MessageBodyModule
import com.twitter.inject.Injector
object MyCustomMessageBodyModule extends MessageBodyModule {
override def singletonStartup(injector: Injector): Unit = {
super.singletonStartup(injector)
???
}
}
See: Custom Request Case class documentation for more information on the JSON integration with routing.
Mustache Support¶
Mustache support for HTTP servers is provided by the finatra/http-mustache library.
This library provides the c.t.finatra.mustache.writer.MustacheMessageBodyWriter which transforms an object into a c.t.finagle.http.Response using a provided Mustache template.
Additionally, the library provides:
a MustacheBodyComponent case class which is a Mustache specific MessageBodyComponent.
the
@Mustache
annotation which is a MessageBodyWriter annotation.and a MustacheModule which registers the annotation and the component to the c.t.finatra.mustache.writer.MustacheMessageBodyWriter for allowing the framework to automatically handle MustacheBodyComponent instances or
@Mustache
annotated classes.
The transformation is performed using a referenced Mustache template
specified by either the MustacheBodyComponent configuration or as a parameter configured in
the @Mustache
annotation.
You must include the MustacheModule in your server’s list of modules in order for the framework to negotiate rendering of Mustache templates via MessageBodyComponents.
For more information the Finatra’s Mustache integration with HTTP see the documentation here.