=doc #19840 Introduction docs for HTTP

This commit is contained in:
Johan Andrén 2016-02-22 11:45:48 +01:00
parent 06b4d54960
commit 61ff9ba9b9
5 changed files with 382 additions and 67 deletions

View file

@ -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)`,
"<html><body>Hello world!</body></html>"))
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)`,
"<html><body>Hello world!</body></html>"))
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 {
<html>
<body>Hello world!</body>
</html>
val route =
get {
pathSingleSlash {
complete(HttpEntity(ContentTypes.`text/html(UTF-8)`,"<html><body>Hello world!</body></html>"))
} ~
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 {
<h1>Say hello to akka-http</h1>
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)
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
}
}

View file

@ -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
}
}

View file

@ -1,3 +1,5 @@
.. _akka-http-spray-json:
JSON Support
============

View file

@ -1,3 +1,5 @@
.. _akka-http-xml-marshalling:
XML Support
===========

View file

@ -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