Writing an Http Service in Finatra

Getting started with Finatra and Swagger

Divan Visagie
From The Couch
Published in
6 min readJul 15, 2016

--

Scala has many options when it comes to HTTP frameworks , but the one that sits with me the best has to be Twitter’s Finatra, It’s built in monitoring , battle testing at Twitter and similarity to what I am used to in C# (Nancy), is what has me sold.

But enough about why , talk is cheap, let me show you the code!

I’m going to do this the good old impatient way and tell you to use the activator seed that Twitter has so kindly provided, to help you get started on your mission to greatness. If you have not a clue what Activator is check out my Scala for human beings article.

Okay , first lets fire up activator:

activator ui

Now browse to http://localhost:8888 and select the Seeds option in the left hand menu. Type Finatra in the search filter and select the option “Finatra v2.x HTTP Server Template Project”. Choose a path in which you will save your project and click Create app.

Once activator sets up the project you should be able to browse the code and even run it. Lets do the running part first , select the run button in the side menu and run your application , this may take a while the first time, but such is the nature of sbt.

Once it is up and running you can test it out by opening a new tab to http://localhost:9999/ping , you should receive the string “pong” as a response. Finatra also gives you an admin interface on port 9990 so that you can monitor your service’s vital organs.

Now that you have your project set up , you have the option of either continuing to develop in activator, or closing it up and opening up the project in your favourite editor.

Here is a list of ways to get it working in the 3 most popular editors (a stat I just made up on the spot)

I’ll wait while you get that all sorted out ...

Finatra basics

Right! Before we go ahead and make the greatest ping/pong service to “Make the world a better place” , lets first step through how the existing service works.

Let’s take a look at ExampleServer.scala:

object ExampleServerMain extends ExampleServerclass ExampleServer extends HttpServer {  override def defaultFinatraHttpPort = ":9999"  override def configureHttp(router: HttpRouter) {
router
.filter[LoggingMDCFilter[Request, Response]]
.filter[TraceIdMDCFilter[Request, Response]]
.filter[CommonFilters]
.add[PingController]
}
}

This is a server definition , the starting point for our server, here we can set up filters, add controllers which handle the requests, and do any other general server configuration. Now lets take a look at the PingController.scala file:

class PingController extends Controller {  get("/ping") { request: Request =>
info("ping")
"pong"
}
}

This elegant little piece of code does exactly what it looks like it does, it defines a get endpoint at /ping and returns the response string “pong”.

We are not going to be able to make the world a better place with strings though, no, if we are going to be truly local, mobile, social we are going to have to respond with JSON, we can do this by simply returning a case class:

case class Pong(message: String)class PingController extends Controller {  get("/ping") { request: Request =>
info("ping")
Pong("We are making the world a better place with ping")
}
}

http://localhost:9999/ping should now return the following JSON message:

{"message":"We are making the world a better place with ping"}

Finatra will also convert futures to JSON objects, the usefulness of that is beyond the scope of this tutorial , but if you start looking at the way Twitter uses Finagle , you will see why this is an awesome feature.

package com.exampleimport com.twitter.finagle.http.Request
import com.twitter.finatra.http.Controller
import com.twitter.util.Future
case class Pong(message: String)class PingController extends Controller { get("/ping") { request: Request =>
info("ping")
val pong = Pong("Result from GET /ping")
Future.value(pong)
}
}

Great ,we have JSON, but as we expand our API we are going to want to post data, we could use a tool like curl or Postman to test that, but there are nicer ways to deal with this sort of stuff and it really doesn’t take that much effort, lets add some Swagger

Adding Swagger

To do this , we are first going to add a dependency in our build.sbt, so add the following to libraryDependencies:

lazy val versions = new {
val finatra = "2.1.4"
val guice = "4.0"
val logback = "1.0.13"
val mockito = "1.9.5"
val scalatest = "2.2.3"
val specs2 = "2.3.12"
val swagger = "0.5.0"
}
libraryDependencies ++= Seq(
"com.github.xiaodongw" %% "swagger-finatra2" % versions.swagger,
// rest of file

Now lets define our swagger document, first we need to create it as an object, lets do this in a new file called PingSwaggerDocument.scala:

package com.exampleimport io.swagger.models.{Info, Swagger}object PingSwaggerDocument extends Swagger

Then we set up the info for our swagger doc in our server definition using .info() and add the swagger controller to our server configuration in ExampleServer.scala, the resulting code with imports should look like this:

package com.exampleimport com.twitter.finagle.http.{Response, Request}
import com.twitter.finatra.http.HttpServer
import com.twitter.finatra.http.filters.{CommonFilters, LoggingMDCFilter, TraceIdMDCFilter}
import com.twitter.finatra.http.routing.HttpRouter
import com.github.xiaodongw.swagger.finatra.SwaggerController
import io.swagger.models.Info
object ExampleServerMain extends ExampleServerclass ExampleServer extends HttpServer { PingSwaggerDocument.info(new Info()
.description("Ping as a service")
.version("0.0.1")
.title("Pinged Piper")
)
override def defaultFinatraHttpPort = ":9999" override def configureHttp(router: HttpRouter) {
router
.filter[LoggingMDCFilter[Request, Response]]
.filter[TraceIdMDCFilter[Request, Response]]
.filter[CommonFilters]
.add(new SwaggerController(swagger = PingSwaggerDocument))
.add[PingController]
}
}

The last step to add swagger to our service is to define our endpoint documentation. In PingController.scala we add the following import:

import com.github.xiaodongw.swagger.finatra.SwaggerSupport

We then extend the Controller to have Swagger support:

class PingController extends Controller with SwaggerSupport {

Next ,we create some implicit swag that will allow us to define some info about our endpoint in our get() method:

implicit protected val swagger = PingSwaggerDocument

The final Swagger empowered version of PingController.scala will look like this:

package com.exampleimport com.twitter.finagle.http.Request
import com.twitter.finatra.http.Controller
import com.twitter.util.Future
import com.github.xiaodongw.swagger.finatra.SwaggerSupportcase class Pong(message: String)class PingController extends Controller with SwaggerSupport {implicit protected val swagger = PingSwaggerDocument get("/ping", swagger { o =>
o.summary("Get ping")
.tag("Ping")
.responseWith[Pong](200, "The pong message")
}) { request: Request =>
info("ping")
val pong = Pong("Result from GET /ping")
Future.value(pong)
}
}

http://localhost:9999/api-docs/ui will now yield a Swagger UI in all it’s glory. Hundreds of third party developers will now flock here to build meaningful apps based on your platform.

Posting data

Our application is going well so far , we have a ping GET service and a browse-able API, but we need users to be able to post custom pings, to do this we will use the post() method, but let’s define some specifications. We want to receive our posts in JSON and we want to convert that to an object we can use in Scala. This works the same way as it did when it came to returning JSON, simply define the structure of the object with a case class:

case class PingRequest(message: String)

And then define our endpoint, replacing the request: Request with our class instead:

post("/ping", swagger { o =>
o.summary("Post ping")
.tag("Ping")
.bodyParam[PingRequest]("Custom ping request message")
.responseWith[Pong](200, "The custom pong message")

}) { pingRequest: PingRequest =>
info("post to ping")
Pong(s"${pingRequest.message} was processed")
}

We should now be able to post custom messages to a ping post method:

Closing off

And that’s it , we now have the foundation to move forward , there are many features in Finatra , and I strongly recommend going through the user guide. Another way to learn is by going through some of the examples inside the project itself, especially the Twitter clone, it gives you a bit of an idea of how to structure your app.

--

--

Divan Visagie
From The Couch

I write about tech and anything else I find interesting