Skip to main content

Getting started with Finagle

·4 mins
Cover image

I’ve been looking at using Finagle as a web endpoint for a side project app. It seems perfect for it - very simple, very fast - so I will definitely be using it for this project, and likely others in future.

Below is a walkthrough of a simple example project I used to get me started. You can get the complete code from GitHub.

A simple HTTP endpoint

To create the structure for our new project, we can use giter8 and this Finagle template.

g8 durgeshm/finagle-project

In Server.scala, we start of by importing a few things into our namespace.

import com.twitter.finagle.builder.ServerBuilder
import com.twitter.finagle.http.{ Http, RichHttp, Request, Response, Version, Status }
import com.twitter.finagle.Service
import com.twitter.util.Future
import java.net.InetSocketAddress

We’re building an HTTP server, so we have a few imports from com.twitter.finagle.http, including the Request and the Response.

We also import Service. Services in Finagle are used to construct both clients and servers.

Next we need to define our Service to serve our HTTP requests.

val service = new Service[Request, Response] {
  def apply(request: Request): Future[Response] = {
    val response = Response()
    response.setContentString("Hello from Finagle\n")
    Future.value(response)
  }
}

In the code above, our service takes the HTTP Request, and returns a Future of a HTTP Response. Finagle uses futures for coordinating asynchronous processes, such as these HTTP responses.

Now our service is defined, we can use the ServerBuilder to construct our server.

val address = new InetSocketAddress(10000)

def start() = ServerBuilder()
    .codec(new RichHttp[Request](Http()))
    .name("HttpServer")
    .bindTo(address)
    .build(service)

That’s pretty self-explanatory, and will start the server on the specified port.

Finally, let’s add a main method to this object, so we can run the code with sbt run 'com.andrewjones.Server'. The full code looks like this.

object Server {

  val service = new Service[Request, Response] {
    def apply(request: Request): Future[Response] = {
      val response = Response()
      response.setContentString("Hello from Finagle\n")
      Future.value(response)
    }
  }

  val address = new InetSocketAddress(10000)
  
  def start() = ServerBuilder()
      .codec(new RichHttp[Request](Http()))
      .name("HttpServer")
      .bindTo(address)
      .build(service)

  def main(args: Array[String]){
    println("Start HTTP server on port 10000")
    val server = start()
  }
}

Once started, you can use curl -D - localhost:10000 to make a call to the server, and you should get a response that looks like this.

HTTP/1.1 200 OK
Content-Length: 19

Hello from Finagle

Writing tests

A getting started guide shouldn’t be complete without discussing testing. For this example, I’ll be using ScalaTest with the FunSuite.

Writing a test for our service is pretty simple. First, we need to start the server, and set up a client to make the requests. I’m doing this in a beforeEach method that is run before each test, and closing the client and server is done in the afterEach method.

class ServerTest extends FunSuite with BeforeAndAfterEach {
  var server: com.twitter.finagle.builder.Server = _
  var client: Service[HttpRequest, HttpResponse] = _
  override def beforeEach() {
    server = Server.start()
    client = ClientBuilder()
      .codec(Http())
      .hosts(Seq(server.localAddress))
      .hostConnectionLimit(1)
      .build()
  }
  override def afterEach() {
    Closable.all(server, client).close()
  }
}

You’ll notice creating a client is very similar to creating a server. This is because of the Service abstraction used by Finagle, as mentioned earlier.

Let’s add a simple test to make a HTTP GET request and ensure we get a 200 OK response.

test("GET Ok") {
  val request = new DefaultHttpRequest(Version.Http11, Method.Get, "/")
  val responseFuture = client(request)
  val response = Response(Await.result(responseFuture))

  assert(response.getStatus() === Status.Ok)
}

Again, fairly straightforward. Only thing worth pointing out here is the need to use Await to wait for the Future to complete. Otherwise the test will complete before we get the response.

Run the tests with sbt test and you should see something like this.

[info] ServerTest:
[info] - GET Ok
[info] Run completed in 1 second, 490 milliseconds.
[info] Total number of tests run: 1
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 1, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.
[success] Total time: 5 s, completed 12-Apr-2015 15:04:19

Going forward

Now we have a simple HTTP server, we probably want to extend it to accept and respond with JSON documents. It might also make calls to other web services, which should be done asynchronously using futures.

The complete code for this example is available on GitHub.

Cover image from Death to the Stock Photo.


Want great, practical advice on implementing data mesh, data products and data contracts?

In my weekly newsletter I share with you an original post and links to what's new and cool in the world of data mesh, data products, and data contracts.

I also include a little pun, because why not? 😅

(Don’t worry—I hate spam, too, and I’ll NEVER share your email address with anyone!)


Andrew Jones
Author
Andrew Jones
I build data platforms that reduce risk and drive revenue.