Getting started with Finagle
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.