diff --git a/akka-docs/rst/scala/code/docs/http/scaladsl/HttpServerExampleSpec.scala b/akka-docs/rst/scala/code/docs/http/scaladsl/HttpServerExampleSpec.scala index acd403ac11..4500faaa55 100644 --- a/akka-docs/rst/scala/code/docs/http/scaladsl/HttpServerExampleSpec.scala +++ b/akka-docs/rst/scala/code/docs/http/scaladsl/HttpServerExampleSpec.scala @@ -4,7 +4,7 @@ package docs.http.scaladsl -import akka.actor.{ ActorRef, ActorSystem } +import akka.actor.{ Props, ActorRef, ActorSystem } import akka.event.LoggingAdapter import akka.http.scaladsl.Http import akka.http.scaladsl.Http.ServerBinding @@ -13,6 +13,7 @@ import akka.stream.ActorMaterializer import akka.stream.scaladsl.{ Flow, Sink } import akka.testkit.TestActors import org.scalatest.{ Matchers, WordSpec } +import scala.io.StdIn import scala.language.postfixOps import scala.concurrent.{ ExecutionContext, Future } @@ -31,7 +32,7 @@ class HttpServerExampleSpec extends WordSpec with Matchers { implicit val system = ActorSystem() implicit val materializer = ActorMaterializer() - implicit val ec = system.dispatcher + implicit val executionContext = system.dispatcher val serverSource: Source[Http.IncomingConnection, Future[Http.ServerBinding]] = Http().bind(interface = "localhost", port = 8080) @@ -49,7 +50,8 @@ class HttpServerExampleSpec extends WordSpec with Matchers { implicit val system = ActorSystem() implicit val materializer = ActorMaterializer() - implicit val ec = system.dispatcher + // needed for the future onFailure in the end + implicit val executionContext = system.dispatcher val handler = get { complete("Hello world!") @@ -60,7 +62,7 @@ class HttpServerExampleSpec extends WordSpec with Matchers { val bindingFuture: Future[ServerBinding] = Http().bindAndHandle(handler, host, port) - bindingFuture onFailure { + bindingFuture.onFailure { case ex: Exception => log.error(ex, "Failed to bind to {}:{}!", host, port) } @@ -74,7 +76,8 @@ class HttpServerExampleSpec extends WordSpec with Matchers { "binding-failure-handling" in compileOnlySpec { implicit val system = ActorSystem() implicit val materializer = ActorMaterializer() - implicit val ec = system.dispatcher + // needed for the future onFailure in the end + implicit val executionContext = system.dispatcher // let's say the OS won't allow us to bind to 80. val (host, port) = ("localhost", 80) @@ -84,7 +87,7 @@ class HttpServerExampleSpec extends WordSpec with Matchers { .to(handleConnections) // Sink[Http.IncomingConnection, _] .run() - bindingFuture onFailure { + bindingFuture.onFailure { case ex: Exception => log.error(ex, "Failed to bind to {}:{}!", host, port) } @@ -97,7 +100,7 @@ class HttpServerExampleSpec extends WordSpec with Matchers { "incoming-connections-source-failure-handling" in compileOnlySpec { implicit val system = ActorSystem() implicit val materializer = ActorMaterializer() - implicit val ec = system.dispatcher + implicit val executionContext = system.dispatcher import Http._ val (host, port) = ("localhost", 8080) @@ -119,7 +122,7 @@ class HttpServerExampleSpec extends WordSpec with Matchers { "connection-stream-failure-handling" in compileOnlySpec { implicit val system = ActorSystem() implicit val materializer = ActorMaterializer() - implicit val ec = system.dispatcher + implicit val executionContext = system.dispatcher val (host, port) = ("localhost", 8080) val serverSource = Http().bind(host, port) @@ -153,6 +156,7 @@ class HttpServerExampleSpec extends WordSpec with Matchers { implicit val system = ActorSystem() implicit val materializer = ActorMaterializer() + implicit val executionContext = system.dispatcher val serverSource = Http().bind(interface = "localhost", port = 8080) @@ -186,87 +190,110 @@ class HttpServerExampleSpec extends WordSpec with Matchers { import akka.http.scaladsl.model.HttpMethods._ import akka.http.scaladsl.model._ import akka.stream.ActorMaterializer + import scala.io.StdIn - implicit val system = ActorSystem() - implicit val materializer = ActorMaterializer() + object WebServer { - val requestHandler: HttpRequest => HttpResponse = { - case HttpRequest(GET, Uri.Path("/"), _, _, _) => - HttpResponse(entity = HttpEntity(ContentTypes.`text/html(UTF-8)`, - "Hello world!")) + def main(args: Array[String]) { + implicit val system = ActorSystem() + implicit val materializer = ActorMaterializer() + // needed for the future map/flatmap in the end + implicit val executionContext = system.dispatcher - case HttpRequest(GET, Uri.Path("/ping"), _, _, _) => - HttpResponse(entity = "PONG!") + val requestHandler: HttpRequest => HttpResponse = { + case HttpRequest(GET, Uri.Path("/"), _, _, _) => + HttpResponse(entity = HttpEntity(ContentTypes.`text/html(UTF-8)`, + "Hello world!")) - case HttpRequest(GET, Uri.Path("/crash"), _, _, _) => - sys.error("BOOM!") + case HttpRequest(GET, Uri.Path("/ping"), _, _, _) => + HttpResponse(entity = "PONG!") - case _: HttpRequest => - HttpResponse(404, entity = "Unknown resource!") + case HttpRequest(GET, Uri.Path("/crash"), _, _, _) => + sys.error("BOOM!") + + case _: HttpRequest => + HttpResponse(404, entity = "Unknown resource!") + } + + val bindingFuture = Http().bindAndHandleSync(requestHandler, "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 + + } } - - Http().bindAndHandleSync(requestHandler, "localhost", 8080) } // format: OFF "high-level-server-example" in compileOnlySpec { import akka.http.scaladsl.Http - import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._ import akka.http.scaladsl.server.Directives._ import akka.stream.ActorMaterializer + import scala.io.StdIn - implicit val system = ActorSystem() - implicit val materializer = ActorMaterializer() + object WebServer { + def main(args: Array[String]) { + implicit val system = ActorSystem() + implicit val materializer = ActorMaterializer() + // needed for the future flatMap/onComplete in the end + implicit val executionContext = system.dispatcher - val route = - get { - pathSingleSlash { - complete { - - Hello world! - + val route = + get { + pathSingleSlash { + complete(HttpEntity(ContentTypes.`text/html(UTF-8)`,"Hello world!")) + } ~ + path("ping") { + complete("PONG!") + } ~ + path("crash") { + sys.error("BOOM!") + } } - } ~ - path("ping") { - complete("PONG!") - } ~ - path("crash") { - sys.error("BOOM!") - } - } - // `route` will be implicitly converted to `Flow` using `RouteResult.route2HandlerFlow` - Http().bindAndHandle(route, "localhost", 8080) + // `route` will be implicitly converted to `Flow` using `RouteResult.route2HandlerFlow` + 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 + } + } } "minimal-routing-example" in compileOnlySpec { import akka.http.scaladsl.Http - import akka.http.scaladsl.marshallers.xml.ScalaXmlSupport._ import akka.http.scaladsl.server.Directives._ import akka.stream.ActorMaterializer + import scala.io.StdIn - object Main extends App { - implicit val system = ActorSystem("my-system") - implicit val materializer = ActorMaterializer() - implicit val ec = system.dispatcher + object WebServer { + def main(args: Array[String]) { - val route = - path("hello") { - get { - complete { -

Say hello to akka-http

+ 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)`, "

Say hello to akka-http

")) } } - } - val bindingFuture = Http().bindAndHandle(route, "localhost", 8080) + val bindingFuture = Http().bindAndHandle(route, "localhost", 8080) - println(s"Server online at http://localhost:8080/\nPress RETURN to stop...") - Console.readLine() // for the future transformations - bindingFuture - .flatMap(_.unbind()) // trigger unbinding from the port - .onComplete(_ ⇒ system.terminate()) // and shutdown when done + 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 + } } } @@ -380,4 +407,121 @@ class HttpServerExampleSpec extends WordSpec with Matchers { } } } + + "stream random numbers" in compileOnlySpec { + //#stream-random-numbers + import akka.stream.scaladsl._ + import akka.util.ByteString + import akka.http.scaladsl.Http + import akka.http.scaladsl.model.{HttpEntity, ContentTypes} + import akka.http.scaladsl.server.Directives._ + import akka.stream.ActorMaterializer + import scala.util.Random + import scala.io.StdIn + + object WebServer { + + def main(args: Array[String]) { + + implicit val system = ActorSystem() + implicit val materializer = ActorMaterializer() + // needed for the future flatMap/onComplete in the end + implicit val executionContext = system.dispatcher + + // streams are re-usable so we can define it here + // and use it for every request + val numbers = Source.fromIterator(() => + Iterator.continually(Random.nextInt())) + + val route = + path("random") { + get { + complete( + HttpEntity( + ContentTypes.`text/plain(UTF-8)`, + // transform each number to a chunk of bytes + numbers.map(n => ByteString(s"$n\n")) + ) + ) + } + } + + 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 + } + } + //#stream-random-numbers + } + + + object Auction { + def props: Props = ??? + } + + "interact with an actor" in compileOnlySpec { + //#actor-interaction + import scala.concurrent.duration._ + import akka.util.Timeout + import akka.pattern.ask + import akka.actor.ActorSystem + import akka.stream.ActorMaterializer + import akka.http.scaladsl.Http + import akka.http.scaladsl.server.Directives._ + import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ + import spray.json.DefaultJsonProtocol._ + import scala.io.StdIn + + object WebServer { + + case class Bid(userId: String, bid: Int) + case object GetBids + case class Bids(bids: List[Bid]) + + // these are from spray-json + implicit val bidFormat = jsonFormat2(Bid) + implicit val bidsFormat = jsonFormat1(Bids) + + def main(args: Array[String]) { + implicit val system = ActorSystem() + implicit val materializer = ActorMaterializer() + // needed for the future flatMap/onComplete in the end + implicit val executionContext = system.dispatcher + + val auction = system.actorOf(Auction.props, "auction") + + val route = + path("auction") { + put { + parameter("bid".as[Int], "user") { (bid, user) => + // place a bid, fire-and-forget + auction ! Bid(user, bid) + complete(StatusCodes.Accepted, "bid placed") + } + } + get { + implicit val timeout: Timeout = 5.seconds + + // query the actor for the current auction state + val bids: Future[Bids] = (auction ? GetBids).mapTo[Bids] + complete(bids) + } + } + + 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 + + } + } + //#actor-interaction + } + + } diff --git a/akka-docs/rst/scala/code/docs/http/scaladsl/SprayJsonExampleSpec.scala b/akka-docs/rst/scala/code/docs/http/scaladsl/SprayJsonExampleSpec.scala index d37e944e0f..fe5208a5f2 100644 --- a/akka-docs/rst/scala/code/docs/http/scaladsl/SprayJsonExampleSpec.scala +++ b/akka-docs/rst/scala/code/docs/http/scaladsl/SprayJsonExampleSpec.scala @@ -8,9 +8,13 @@ import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport import akka.http.scaladsl.server.Directives import org.scalatest.{ Matchers, WordSpec } +import scala.concurrent.Future + class SprayJsonExampleSpec extends WordSpec with Matchers { - "spray-json example" in { + def compileOnlySpec(body: => Unit) = () + + "spray-json example" in compileOnlySpec { //#example import spray.json._ @@ -31,9 +35,7 @@ class SprayJsonExampleSpec extends WordSpec with Matchers { val route = get { pathSingleSlash { - complete { - Item("thing", 42) // will render as JSON - } + complete(Item("thing", 42)) // will render as JSON } } ~ post { @@ -47,4 +49,63 @@ class SprayJsonExampleSpec extends WordSpec with Matchers { //# } } + + "second-spray-json-example" in compileOnlySpec { + //#second-spray-json-example + import akka.actor.ActorSystem + import akka.stream.ActorMaterializer + import akka.Done + import akka.http.scaladsl.server.Route + import akka.http.scaladsl.server.Directives._ + import akka.http.scaladsl.model.StatusCodes + import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport._ + import spray.json.DefaultJsonProtocol._ + + object WebServer { + + // domain model + final case class Item(name: String, id: Long) + final case class Order(items: List[Item]) + + // formats for unmarshalling and marshalling + implicit val itemFormat = jsonFormat2(Item) + implicit val orderFormat = jsonFormat1(Order) + + // (fake) async database query api + def fetchItem(itemId: Long): Future[Option[Item]] = ??? + def saveOrder(order: Order): Future[Done] = ??? + + def main(args: Array[String]) { + + // needed to run the route + implicit val system = ActorSystem() + implicit val materializer = ActorMaterializer() + + val route: Route = + get { + pathPrefix("item" / LongNumber) { id => + // there might be no item for a given id + val maybeItem: Future[Option[Item]] = fetchItem(id) + + onSuccess(maybeItem) { + case Some(item) => complete(item) + case None => complete(StatusCodes.NotFound) + } + } + } ~ + post { + path("create-order") { + entity(as[Order]) { order => + val saved: Future[Done] = saveOrder(order) + onComplete(saved) { done => + complete("order created") + } + } + } + } + + } + } + //#second-spray-json-example + } } \ No newline at end of file diff --git a/akka-docs/rst/scala/http/common/json-support.rst b/akka-docs/rst/scala/http/common/json-support.rst index d6ae1235ee..893c3fd8d1 100644 --- a/akka-docs/rst/scala/http/common/json-support.rst +++ b/akka-docs/rst/scala/http/common/json-support.rst @@ -1,3 +1,5 @@ +.. _akka-http-spray-json: + JSON Support ============ diff --git a/akka-docs/rst/scala/http/common/xml-support.rst b/akka-docs/rst/scala/http/common/xml-support.rst index 6183889999..32622c4905 100644 --- a/akka-docs/rst/scala/http/common/xml-support.rst +++ b/akka-docs/rst/scala/http/common/xml-support.rst @@ -1,3 +1,5 @@ +.. _akka-http-xml-marshalling: + XML Support =========== diff --git a/akka-docs/rst/scala/http/introduction.rst b/akka-docs/rst/scala/http/introduction.rst index c21cffbbbf..b95f7fcfc7 100644 --- a/akka-docs/rst/scala/http/introduction.rst +++ b/akka-docs/rst/scala/http/introduction.rst @@ -10,23 +10,129 @@ You get to pick the API level of abstraction that is most suitable for your appl This means that, if you have trouble achieving something using a high-level API, there's a good chance that you can get it done with a low-level API, which offers more flexibility but might require you to write more application code. -Akka HTTP is structured into several modules: +Using Akka HTTP +--------------- +Akka HTTP is provided in separate jar files, to use it make sure to include the following dependencies:: -akka-http-core - A complete, mostly low-level, server- and client-side implementation of HTTP (incl. WebSockets) + "com.typesafe.akka" %% "akka-http-core" % "@version@" @crossString@ + "com.typesafe.akka" %% "akka-http-experimental" % "@version@" @crossString@ + + +Routing DSL for HTTP servers +---------------------------- +The high-level, routing API of Akka HTTP provides a DSL to describe HTTP "routes" and how they should be handled. +Each route is composed of one or more level of ``Directive`` s that narrows down to handling one specific type of +request. + +For example one route might start with matching the ``path`` of the request, only matching if it is "/hello", then +narrowing it down to only handle HTTP ``get`` requests and then ``complete`` those with a string literal, which +will be sent back as a HTTP OK with the string as response body. + +Transforming request and response bodies between over-the-wire formats and objects to be used in your application is +done separately from the route declarations, in marshallers, which are pulled in implicitly using the "magnet" pattern. +This means that you can ``complete`` a request with any kind of object a as long as there is an implicit marshaller +available in scope. + +Default marshallers are provided for simple objects like String or ByteString, and you can define your own for example +for JSON. An additional module provides JSON serialization using the spray-json library (see :ref:`akka-http-spray-json` +for details). + +The ``Route`` created using the Route DSL is then "bound" to a port to start serving HTTP requests: + +.. includecode2:: ../code/docs/http/scaladsl/HttpServerExampleSpec.scala + :snippet: minimal-routing-example + +A common use case is to reply to a request using a model object having the marshaller transform it into JSON. In +this case shown by two separate routes. The first route queries an asynchronous database and marshalls the +``Future[Option[Item]]`` result into a JSON response. The second unmarshalls an ``Order`` from the incoming request +saves it to the database and replies with an OK when done. + +.. includecode2:: ../code/docs/http/scaladsl/SprayJsonExampleSpec.scala + :snippet: second-spray-json-example + +The logic for the marshalling and unmarshalling JSON in this example is provided by the "spray-json" library +(details on how to use that here: :ref:`akka-http-spray-json`). + +One of the strengths of Akka HTTP is that streaming data is at its heart meaning that both request and response bodies +can be streamed through the server achieving constant memory usage even for very large requests or responses. Streaming +responses will be backpressured by the remote client so that the server will not push data faster than the client can +handle, streaming requests means that the server decides how fast the remote client can push the data of the request +body. + +Example that streams random numbers as long as the client accepts them: + +.. includecode:: ../code/docs/http/scaladsl/HttpServerExampleSpec.scala + :include: stream-random-numbers + +Connecting to this service with a slow HTTP client would backpressure so that the next random number is produced on +demand with constant memory usage on the server. This can be seen using curl and limiting the rate +``curl --limit-rate 50b 127.0.0.1:8080/random`` + + +Akka HTTP routes easily interacts with actors. In this example one route allows for placing bids in a fire-and-forget +style while the second route contains a request-response interaction with an actor. The resulting response is rendered +as json and returned when the response arrives from the actor. + +.. includecode:: ../code/docs/http/scaladsl/HttpServerExampleSpec.scala + :include: actor-interaction + +Again the logic for the marshalling and unmarshalling JSON in this example is provided by the "spray-json" library +(details on how to use that here: :ref:`akka-http-spray-json`) + + +Read more about the details of the high level APIs in the section :ref:`http-high-level-server-side-api`. + +Low-level HTTP server APIs +-------------------------- +The low-level Akka HTTP server APIs allows for handling connections or individual requests by accepting +``HttpRequest`` s and answering them by producing ``HttpResponse`` s. This is provided by the ``akka-http-core`` module. +APIs for handling such request-responses as function calls and as a ``Flow[HttpRequest, HttpResponse, _]`` are available. + +.. includecode2:: ../code/docs/http/scaladsl/HttpServerExampleSpec.scala + :snippet: low-level-server-example + +Read more details about the low level APIs in the section :ref:`http-low-level-server-side-api`. + + +HTTP client API +--------------- +The client APIs provide methods for calling a HTTP server using the same ``HttpRequest`` and ``HttpResponse`` abstractions +that Akka HTTP server uses but adds the concept of connection pools to allow multiple requests to the same server to be +handled more performantly by re-using TCP connections to the server. + +Example simple request: + +.. includecode:: ../code/docs/http/scaladsl/HttpClientExampleSpec.scala + :include: single-request-example + + +Read more about the details of the client APIs in the section :ref:`http-client-side`. + + + +The modules that make up Akka HTTP +---------------------------------- +Akka HTTP is structured into several modules: akka-http Higher-level functionality, like (un)marshalling, (de)compression as well as a powerful DSL - for defining HTTP-based APIs on the server-side + for defining HTTP-based APIs on the server-side, this is the recommended way to write HTTP servers + with Akka HTTP. Details can be found in the section :ref:`http-high-level-server-side-api` + +akka-http-core + A complete, mostly low-level, server- and client-side implementation of HTTP (incl. WebSockets) + Details can be found in sections :ref:`http-low-level-server-side-api` and :ref:`http-client-side` akka-http-testkit A test harness and set of utilities for verifying server-side service implementations akka-http-spray-json Predefined glue-code for (de)serializing custom types from/to JSON with spray-json_ + Details can be found here: :ref:`akka-http-spray-json` akka-http-xml Predefined glue-code for (de)serializing custom types from/to XML with scala-xml_ + Details can be found here: :ref:`akka-http-xml-marshalling` .. _spray-json: https://github.com/spray/spray-json .. _scala-xml: https://github.com/scala/scala-xml \ No newline at end of file