RESTServices with Finagle and Finch

Jos Dirksen

November 2015

In this article by Jos Dirksen, the author of RESTful Web Services with Scala, we'll only be talking about Finch. Note, though, that most of the concepts provided by Finch are based on the underlying Finagle ideas. Finch just provides a very nice REST-based set of functions to make working with Finagle very easy and intuitive.

(For more resources related to this topic, see here.)

Finagle and Finch are two different frameworks that work closely together. Finagle is an RPC framework, created by Twitter, which you can use to easily create different types of services. On the website (https://github.com/twitter/finagle), the team behind Finagle explains it like this:

Finagle is an extensible RPC system for the JVM, used to construct high-concurrency servers. Finagle implements uniform client and server APIs for several protocols, and is designed for high performance and concurrency. Most of Finagle's code is protocol agnostic, simplifying the implementation of new protocols.

So, while Finagle provides the plumbing required to create highly scalable services, it doesn't provide direct support for specific protocols. This is where Finch comes in.

Finch (https://github.com/finagle/finch) provides an HTTP REST layer on top of Finagle. On their website, you can find a nice quote that summarizes what Finch aims to do:

Finch is a thin layer of purely functional basic blocks atop of Finagle for building composable REST APIs. Its mission is to provide the developers simple and robust REST API primitives being as close as possible to the bare metal Finagle API.

Your first Finagle and Finch REST service

Let's start by building a minimal Finch REST service. The first thing we need to do is to make sure we have the correct dependencies. To use Finch, all you have to do is to add the following dependency to your SBT file:

"com.github.finagle" %% "finch-core" % "0.7.0"

With this dependency added, we can start coding our very first Finch service. The next code fragment shows a minimal Finch service, which just responds with a Hello, Finch! message:

package org.restwithscala.chapter2.gettingstarted

import io.finch.route._
import com.twitter.finagle.Httpx

object HelloFinch extends App {

Httpx.serve(":8080", (Get / "hello" />"Hello, Finch!").toService)

println("Press <enter> to exit.")
Console.in.read.toChar
}

When this service receives a GET request on the URL path hello, it will respond with a Hello, Finch! message. Finch does this by creating a service (using the toService function) from a route (more on what a route is will be explained in the next section) and using the Httpx.serve function to host the created service. When you run this example, you'll see an output as follows:

 [info] Loading project definition from /Users/jos/dev/git/rest-with-scala/project
[info] Set current project to rest-with-scala (in build file:/Users/jos/dev/git/rest-with-scala/)
[info] Running org.restwithscala.chapter2.gettingstarted.HelloFinch
Jun 26, 2015 9:38:00 AM com.twitter.finagle.Init$$anonfun$1 apply$mcV$sp
INFO: Finagle version 6.25.0 (rev=78909170b7cc97044481274e297805d770465110) built at 20150423-135046

Press <enter> to exit.

At this point, we have an HTTP server running on port 8080. When we make a call to http://localhost:8080/hello, this server will respond with the Hello, Finch! message. To test this service, you can make a HTTP requests in Postman like this:

If you don't want to use a GUI to make the requests, you can also use the following Curl command:

curl'http://localhost:8080/hello'

HTTP verb and URL matching

An important part of every REST framework is the ability to easily match HTTP verbs and the various path segments of the URL. In this section, we'll look at the tools Finch provides us with. Let's look at the code required to do this (the full source code for this example can be found at https://github.com/josdirksen/rest-with-scala/blob/master/chapter-02/src/main/scala/org/restwithscala/chapter2/steps/FinchStep1.scala):

package org.restwithscala.chapter2.steps

import com.twitter.finagle.Httpx
import io.finch.request._
import io.finch.route._
import io.finch.{Endpoint => _}

object FinchStep1 extends App {

// handle a single post using a RequestReader
valtaskCreateAPI = Post / "tasks" /> (
for {
bodyContent<- body
   } yield s"created task with: $bodyContent")

// Use matchers and extractors to determine which route to call
// For more examples see the source file.
valtaskAPI =
     Get / "tasks" />
"Get a list of all the tasks" |
     Get / "tasks" / long />
( id =>s"Get a single task with id: $id" ) |
     Put / "tasks" / long />
( id =>s"Update an existing task with id $id to " ) |
     Delete / "tasks" / long />
( id =>s"Delete an existing task with $id" )

// simple server that combines the two routes and creates a
val server = Httpx.serve(":8080",
           (taskAPI :+: taskCreateAPI).toService )

println("Press <enter> to exit.")
Console.in.read.toChar

server.close()
}

In this code fragment, we created a number of Router instances that process the requests, which we sent from Postman. Let's start by looking at one of the routes of the taskAPI router: Get / "tasks" / long /> (id =>s"Get a single task with id: $id"). The following table explains the various parts of the route:

Part

Description

Get

While writing routers, usually the first thing you do is determine which HTTP verb you want to match. In this case, this route will only match the GET verb. Besides the Get matcher, Finch also provides the following matchers: Post, Patch, Delete, Head, Options, Put, Connect, and Trace.

"tasks"

The next part of the route is a matcher that matches a URL path segment. In this case, we match the following URL: http://localhost:8080/tasks. Finch will use an implicit conversion to convert this String object to a finch Matcher object. Finch also has two wildcard Matchers: * and **. The * matcher allows any value for a single path segment, and the ** matcher allows any value for multiple path segments.

long

The next part in the route is called an Extractor. With an extractor, you turn part of the URL into a value, which you can use to create the response (for example, retrieve an object from the database using the extracted ID). The long extractor, as the name implies, converts the matching path segment to a long value. Finch also provides an int, string, and Boolean extractor.

long =>B

The last part of the route is used to create the response message. Finch provides different ways of creating the response, which we'll show in the other parts of this article. In this case, we need to provide Finch with a function that transforms the long value we extracted, and return a value Finch can convert to a response (more on this later). In this example, we just return a String.

 If you've looked closely at the source code, you would have probably noticed that Finch uses custom operators to combine the various parts of a route. Let's look a bit closer at those. With Finch, we get the following operators (also called combinators in Finch terms):

  • / or andThen: With this combinatory, you sequentially combine various matchers and extractors together. Whenever the first part matches, the next one is called. For instance: Get / "path" / long.
  • | or orElse: This combinator allows you to combine two routers (or parts thereof) together as long as they are of the same type. So, we could do (Get | Post) to create a matcher, which matches the GET and POST HTTP verbs. In the code sample, we've also used this to combine all the routes that returned a simple String into the taskAPI router.
  • /> or map: With this combinatory, we pass the request and any extracted values from the path to a function for further processing. The result of the function that is called is returned as the HTTP response. As you'll see in the rest of the article, there are different ways of processing the HTTP request and creating a response.
  • :+:: The final combinator allows you to combine two routers together of different types. In the example, we have two routers. A taskAPI, that returns a simple String, and a taskCreateAPI, which uses a RequestReader (through the body function) to create the response. We can't combine these with | since the result is created using two different approaches, so we use the :+:combinator.

We just return simple Strings whenever we get a request. In the next section, we'll look at how you can use RequestReader to convert the incoming HTTP requests to case classes and use those to create a HTTP response.

When you run this service, you'll see an output as follows:

[info] Loading project definition from /Users/jos/dev/git/rest-with-scala/project
[info] Set current project to rest-with-scala (in build file:/Users/jos/dev/git/rest-with-scala/)
[info] Running org.restwithscala.chapter2.steps.FinchStep1
Jun 26, 2015 10:19:11 AM com.twitter.finagle.Init$$anonfun$1 apply$mcV$sp
INFO: Finagle version 6.25.0 (rev=78909170b7cc97044481274e297805d770465110) built at 20150423-135046
Press <enter> to exit.

Once the server is started, you can once again use Postman(or any other REST client) to make requests to this service (example requests can be found at https://github.com/josdirksen/rest-with-scala/tree/master/common):

And once again, you don't have to use a GUI to make the requests. You can test the service with Curl as follows:

# Create task
curl 'http://localhost:8080/tasks' -H 'Content-Type: text/plain;charset=UTF-8' --data-binary $'{\ntaskdata\n}'

# Update task
curl 'http://localhost:8080/tasks/1' -X PUT -H 'Content-Type: text/plain;charset=UTF-8' --data-binary $'{\ntaskdata\n}'
 
# Get all tasks
curl'http://localhost:8080/tasks'

# Get single task
curl'http://localhost:8080/tasks/1'

Summary

This article only showed a couple of the features Finch provides. But it should give you a good head start toward working with Finch.

Resources for Article:


Further resources on this subject:


You've been reading an excerpt of:

RESTful Web Services with Scala

Explore Title