Skip to content

Instantly share code, notes, and snippets.

@oxbowlakes
Last active November 14, 2016 12:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oxbowlakes/1e15c2d619224b0894f107b9f78c0042 to your computer and use it in GitHub Desktop.
Save oxbowlakes/1e15c2d619224b0894f107b9f78c0042 to your computer and use it in GitHub Desktop.

I spent a lot of time tryig to understand why, when I appeared to be following the minimal example from the akka HTTP server documentation, scalac was refusing to compile my code.

Here is the minimal example in its entirety:

import akka.actor.ActorSystem
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import scala.io.StdIn

object WebServer {
  def main(args: Array[String]) {

    implicit val system = ActorSystem("my-system")
    implicit val materializer = ActorMaterializer()
    // needed for the future flatMap/onComplete in the end
    implicit val executionContext = system.dispatcher

    val route =
      path("hello") {
        get {
          complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "<h1>Say hello to akka-http</h1>"))
        }
      }

    val bindingFuture = Http().bindAndHandle(route, "localhost", 8080)

    println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
    StdIn.readLine() // let it run until user presses return
    bindingFuture
      .flatMap(_.unbind()) // trigger unbinding from the port
      .onComplete(_ => system.terminate()) // and shutdown when done
  }
}

Here is a very slight re-coding of this. Note this is really just a reorganization of the code such that the entity binding the HTTP server is an Actor:

import akka.actor.{Actor, ActorSystem, Props}
import akka.http.scaladsl.Http
import akka.http.scaladsl.model._
import akka.http.scaladsl.server.Directives._
import akka.stream.ActorMaterializer
import akka.stream.scaladsl.Tcp.ServerBinding

import scala.io.StdIn

object WebServer {

  implicit val system = ActorSystem("my-system")
  implicit val materializer = ActorMaterializer()
  // needed for the future flatMap/onComplete in the end
  implicit val executionContext = system.dispatcher

  def main(args: Array[String]) {
    val ref = system.actorOf(Props[Impl])
    StdIn.readLine()
    system stop ref
  }

  class Impl extends Actor {

    val route =
      path("hello") {
        get {
          complete(HttpEntity(ContentTypes.`text/html(UTF-8)`, "<h1>Say hello to akka-http</h1>"))
        }
      }

    override def preStart() = {
      Http().bindAndHandle(route, "localhost", 8080).pipeTo(self)
    }

    override def receive = {
      case ServerBinding(_) =>
        println(s"Server online at http://localhost:8080/\nPress RETURN to stop...")
    }
  }

}

IT DOESN'T COMPILE

Error:(34, 28) type mismatch;
 found   : akka.http.scaladsl.server.Route
    (which expands to)  akka.http.scaladsl.server.RequestContext => scala.concurrent.Future[akka.http.scaladsl.server.RouteResult]
 required: akka.stream.scaladsl.Flow[akka.http.scaladsl.model.HttpRequest,akka.http.scaladsl.model.HttpResponse,Any]
      Http().bindAndHandle(route, "localhost", 8080).pipeTo(self)

Essentially sticking the Route declaration inside an Actor means that an implicit conversion from a Route to a Flow (which the bindAndHandle method actually takes) cannot be discovered. Why? I have absolutely no idea! But I would say the following:

  1. It's critical to mention this in the Spray Migration Guide because people are converting HttpServiceActor and might just assume you can replace HttpServiceActor with Actor
  2. APIs should not be this brittle
  3. Nowhere in either the documentation on bindAndHandle, the declaration of the Route type or the Flow class is there any indication as to how a Route gets turned into a Flow
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment