Akka HTTP: Getting Started

by
Tags: , ,
Category:

In this article we’ll create a very simple web app using Akka HTTP. Akka HTTP is effectively the next major release of the Spray library. Even if you’re familiar with Spray, many things have changed in Akka HTTP. And they may change some more as the current release is a milestone (1.0-M3). But we’ll take a look at some basics as things currently stand. Source code for this post is at the scala-samples/akka-http GitHub repository in the akka-http folder.

Getting Started

We’ll start by creating a build.sbt file.

name := "akka-http-sample"
version := "1.0"
organization := "com.chariotsolutions"
libraryDependencies ++= Seq(
  "com.typesafe.akka" %% "akka-http-experimental" % "1.0-M3",
  "org.scalatest" %% "scalatest" % "2.2.1" % "test"
)
scalaVersion := "2.11.4"

Here we are adding the akka-http module and scalatest as library dependencies. You will notice that Akka HTTP is currently marked as an “experimental” module. It is premature to use this module in production, but it’s certainly time to start learning about it.

Next, let’s create an HTML page at src/main/resources/web/index.html. This is the page that we’ll be serving up from this simple app.

Testing

We’ll write our test first, as we always do in real life. 🙂 In src/test/scala/com/chariotsolutions/AppSpec.scala, we can write the following test code.

class AppSpec extends FlatSpec with Matchers
 with ScalaFutures with BeforeAndAfterAll {
  implicit val testSystem =
    akka.actor.ActorSystem("test-system")
  import testSystem.dispatcher
  implicit val fm = ActorFlowMaterializer()
  val server = new SampleApp {}
  override def afterAll = testSystem.shutdown()
  def sendRequest(req: HttpRequest) =
    Source.single(req).via(
      Http().outgoingConnection(host = "localhost",
        port = 8080).flow
    ).runWith(Sink.head)
  "The app" should "return index.html on a GET to /" in {
    val request = sendRequest(HttpRequest())
    whenReady(request) { response =>
      val stringFuture = Unmarshal(response.entity).to[String]
      whenReady(stringFuture) { str =>
        str should include("Hello World!")
      }
    }
  }
  "The app" should "return 404 on a GET to /foo" in {
    val request = sendRequest(HttpRequest(uri = "/foo"))
    whenReady(request) { response =>
      response.status shouldBe StatusCodes.NotFound
    }
  }
}

We have two tests in this code: one that checks that calling the root URI returns the page content we expect, and a second verifying that requesting an invalid URL will fail with a not found error.

Let’s review the latter test: we make a call to sendRequest, passing an HttpRequest with /foo as the URI while allowing the method to default to GET. sendRequest returns a Future, which we pass to whenReady to await completion of the Future. When the Future completes our anonymous function will check that the response status code is equal to NotFound, which is a constant representing the 404 status code.

The sendRequest method is where we’re really making use of Akka HTTP. We construct a stream that describes transforming an HttpRequest to an HttpResponse via an OutgoingConnection. The first step in the processing stream is a Source that contains the HttpRequest, which is obtained by calling Source.single(). We then call via() to add the next step in the stream, which will be provided by OutgoingConnection. The call to Http().outgoingConnection() creates an OutgoingConnection instance for the host and port specified, and calling flow creates a Flow[HttpRequest, HttpResponse]. The stream is completed and executed by calling runWith, passing the value Sink.head, which will materialize to a Future[HttpResponse]. The runWith method also expects an implicit FlowMaterializer to be provided. We created that earlier in the code by setting the variable fm as an instance of ActorFlowMaterializer.

The test verifying the result of a GET to / is very similar, but it has a nested call to whenReady. Because we want to check the contents of the body returned to us, we need to get the contents of the HttpResponse.entity. In order to support streaming and transformation of the response, Akka HTTP models the entity’s contents as a Source[ByteString]. Since we really want the contents as a string, we will use Unmarshal.to() to perform the transformation, which will return a Future[String]. Again, we can pass this Future to whenReady to wait for it to complete and then test that the result contains the string that we expect.

Handling Requests

To handle the request that AppSpec is sending, we need to start up a server and provide a handler for the request. Akka HTTP inherits the routing DSL from Spray, which is composed of directives that let us match an incoming request and return a response. Basic directives include get, which matches a GET request, and path, which lets us match a portion of the request path.

Looking at the SampleApp trait in Main.scala, we’re defining a route that matches on the empty path and uses a directive called getFromResource to load the file from the classpath.

  val route =
    path("") {
      getFromResource("web/index.html");
    }

With a route defined, we need to fire up the server and tell it to handle requests with our route.

    val serverBinding = Http().bind(interface = "localhost",
      port = 8080)
    serverBinding.startHandlingWith(route)

We call Http().bind to specify an interface and port for the server to run on. The call to startHandlingWith actually materializes the requested binding and specifies how to handle connections.

Once the handler is working, we should see a successful test:

[info] AppSpec:
[info] The app
[info] - should return index.html on a GET to /
[info] The app
[info] - should return 404 on a GET to /foo
[info] Run completed in 988 milliseconds.
[info] Total number of tests run: 2
[info] Suites: completed 1, aborted 0
[info] Tests: succeeded 2, failed 0, canceled 0, ignored 0, pending 0
[info] All tests passed.