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:
- It's critical to mention this in the Spray Migration Guide because people are converting
HttpServiceActor
and might just assume you can replaceHttpServiceActor
withActor
- APIs should not be this brittle
- Nowhere in either the documentation on
bindAndHandle
, the declaration of theRoute
type or theFlow
class is there any indication as to how aRoute
gets turned into aFlow